From 4ee9ca32f2bd420a448e01c3eaf5f9c38aa54cf9 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Tue, 9 Dec 2025 14:15:21 +0100 Subject: [PATCH 01/12] config: add missing rules for idp options --- src/config/cfg_rules.ini | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 04db3628bc..7092c937a4 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -486,6 +486,20 @@ option = ad_site option = ad_update_samba_machine_account_password option = ad_use_ldaps +# IdP specific options +option = idp_request_timeout +option = idp_type +option = idp_client_id +option = idp_client_secret +option = idp_token_endpoint +option = idp_device_auth_endpoint +option = idp_userinfo_endpoint +option = idp_id_scope +option = idp_auth_scope +option = idmap_range_min +option = idmap_range_max +option = idmap_range_size + # IPA provider specific options option = ipa_access_order option = ipa_anchor_uuid From b6749f3a714ca57df353430e75ad55cc78cba811 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Thu, 4 Dec 2025 11:06:55 +0100 Subject: [PATCH 02/12] oidc_child: get refresh_token for later --- src/oidc_child/oidc_child.c | 19 ++++++++++++++++++- src/oidc_child/oidc_child_json.c | 22 ++++++++++++++++++++++ src/oidc_child/oidc_child_util.h | 3 +++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index deb818efcd..af3fd115c7 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -501,6 +501,16 @@ void trace_tokens(struct devicecode_ctx *dc_ctx) DEBUG(SSSDBG_TRACE_ALL, "User sub: [%s].\n", json_string_value(json_object_get(dc_ctx->td->id_token_payload, "sub"))); } + if (dc_ctx->td->refresh_token_payload != NULL) { + tmp = json_dumps(dc_ctx->td->refresh_token_payload, 0); + DEBUG(SSSDBG_TRACE_ALL, "refresh_token payload: [%s].\n", tmp); + free(tmp); + + DEBUG(SSSDBG_TRACE_ALL, "User Principal: [%s].\n", json_string_value(json_object_get(dc_ctx->td->refresh_token_payload, "upn"))); + DEBUG(SSSDBG_TRACE_ALL, "User oid: [%s].\n", json_string_value(json_object_get(dc_ctx->td->refresh_token_payload, "oid"))); + DEBUG(SSSDBG_TRACE_ALL, "User sub: [%s].\n", json_string_value(json_object_get(dc_ctx->td->refresh_token_payload, "sub"))); + } + tmp = json_dumps(dc_ctx->td->userinfo, 0); DEBUG(SSSDBG_TRACE_ALL, "userinfo: [%s].\n", tmp); free(tmp); @@ -669,7 +679,14 @@ int main(int argc, const char *argv[]) if (opts.oidc_cmd == GET_ACCESS_TOKEN) { DEBUG(SSSDBG_TRACE_ALL, "access_token: [%s].\n", dc_ctx->td->access_token_str); - DEBUG(SSSDBG_TRACE_ALL, "id_token: [%s].\n", dc_ctx->td->id_token_str); + if (dc_ctx->td->id_token_str != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "id_token: [%s].\n", + dc_ctx->td->id_token_str); + } + if (dc_ctx->td->refresh_token_str != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "refresh_token: [%s].\n", + dc_ctx->td->refresh_token_str); + } if (dc_ctx->jwks_uri != NULL) { ret = decode_token(dc_ctx, true); diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index 7781931bb4..cfdddf0d48 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -300,6 +300,23 @@ errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify) "payload")); json_decref(jws); } + if (dc_ctx->td->refresh_token_str != NULL) { + ret = str_to_jws(dc_ctx, dc_ctx->td->refresh_token_str, &jws); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to convert refresh_token into jws.\n"); + dc_ctx->td->refresh_token_payload = NULL; + ret = EOK; + goto done; + } + if (verify && !jose_jws_ver(NULL, jws, NULL, keys, false)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to verify refresh_token.\n"); + } + + dc_ctx->td->refresh_token_payload = jose_b64_dec_load(json_object_get(jws, + "payload")); + json_decref(jws); + } ret = EOK; @@ -450,6 +467,11 @@ errno_t parse_token_result(struct devicecode_ctx *dc_ctx, dc_ctx->td->id_token = json_object_get(dc_ctx->td->result, "id_token"); dc_ctx->td->id_token_str = get_json_string(dc_ctx->td, dc_ctx->td->result, "id_token"); + dc_ctx->td->refresh_token = json_object_get(dc_ctx->td->result, + "refresh_token"); + dc_ctx->td->refresh_token_str = get_json_string(dc_ctx->td, + dc_ctx->td->result, + "refresh_token"); return EOK; } diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index eb64a14c58..21ef283de0 100644 --- a/src/oidc_child/oidc_child_util.h +++ b/src/oidc_child/oidc_child_util.h @@ -54,6 +54,9 @@ struct token_data { json_t *id_token; json_t *id_token_payload; char *id_token_str; + json_t *refresh_token; + json_t *refresh_token_payload; + char *refresh_token_str; json_t *userinfo; }; From a508466c2f97e07fbd4d6afcd9288777d6d11a5f Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Fri, 13 Mar 2026 00:03:41 +0100 Subject: [PATCH 03/12] oidc_child: store tokens in cache --- src/db/sysdb.h | 4 ++ src/oidc_child/oidc_child.c | 11 ++- src/oidc_child/oidc_child_json.c | 43 +++++++++++ src/oidc_child/oidc_child_util.h | 2 + src/providers/idp/idp_auth_eval.c | 115 ++++++++++++++++++++++++++++-- 5 files changed, 169 insertions(+), 6 deletions(-) diff --git a/src/db/sysdb.h b/src/db/sysdb.h index 80cf1ada0b..7882794ca6 100644 --- a/src/db/sysdb.h +++ b/src/db/sysdb.h @@ -136,6 +136,10 @@ #define SYSDB_DN_FOR_MEMBER_HASH_TABLE "dnForMemberHashTable" #define SYSDB_AD_SAMACCOUNTNAME "adSAMAccountName" +#define SYSDB_ACCESS_TOKEN "accessToken" +#define SYSDB_ID_TOKEN "idToken" +#define SYSDB_REFRESH_TOKEN "refreshToken" + #define SYSDB_ORIG_DN "originalDN" #define SYSDB_ORIG_OBJECTCLASS "originalObjectClass" #define SYSDB_ORIG_MODSTAMP "originalModifyTimestamp" diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index af3fd115c7..85cc22c7df 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -757,7 +757,16 @@ int main(int argc, const char *argv[]) DEBUG(SSSDBG_CONF_SETTINGS, "User identifier: [%s].\n", user_identifier); - fprintf(stdout,"%s", user_identifier); + tmp = token_data_to_json(dc_ctx); + if (tmp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to pack token data into JSON.\n"); + goto done; + } + + json_dumpf(tmp, stdout, JSON_COMPACT); + json_decref(tmp); + + fprintf(stdout, "\n%s", user_identifier); fflush(stdout); } diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index cfdddf0d48..9d039fe5ee 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -1013,3 +1013,46 @@ errno_t add_posix_to_json_string_array(TALLOC_CTX *mem_ctx, return ret; } + +json_t *token_data_to_json(struct devicecode_ctx *dc_ctx) +{ + json_t *obj; + int ret; + + obj = json_object(); + if (obj == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create JSON object.\n"); + return NULL; + } + + ret = json_object_set(obj, "access_token", dc_ctx->td->access_token); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add access token to JSON object.\n"); + goto fail; + } + + if (dc_ctx->td->id_token != NULL) { + ret = json_object_set(obj, "id_token", dc_ctx->td->id_token); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add ID token to JSON object.\n"); + goto fail; + } + } + + if (dc_ctx->td->refresh_token != NULL) { + ret = json_object_set(obj, "refresh_token", dc_ctx->td->refresh_token); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add refresh token to JSON object.\n"); + goto fail; + } + } + + return obj; + +fail: + json_decref(obj); + return NULL; +} diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index 21ef283de0..96dcd9b8ac 100644 --- a/src/oidc_child/oidc_child_util.h +++ b/src/oidc_child/oidc_child_util.h @@ -176,6 +176,8 @@ errno_t add_posix_to_json_string_array(TALLOC_CTX *mem_ctx, const char *in, char **out); +json_t *token_data_to_json(struct devicecode_ctx *dc_ctx); + /* oidc_child_id.c */ errno_t oidc_get_id(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, char *idp_type, diff --git a/src/providers/idp/idp_auth_eval.c b/src/providers/idp/idp_auth_eval.c index 9c9444da0b..065194f8d6 100644 --- a/src/providers/idp/idp_auth_eval.c +++ b/src/providers/idp/idp_auth_eval.c @@ -108,6 +108,73 @@ errno_t eval_device_auth_buf(struct idp_auth_ctx *idp_auth_ctx, return ret; } +static errno_t store_json_tokens(struct idp_auth_ctx *idp_auth_ctx, + struct pam_data *pd, json_t *token_data) +{ + errno_t ret; + struct sysdb_attrs *attrs = NULL; + char *access_token = NULL; + char *id_token = NULL; + char *refresh_token = NULL; + + struct sss_domain_info *dom = idp_auth_ctx->be_ctx->domain; + + ret = json_unpack(token_data, "{s:s, s?s, s?s}", + "access_token", &access_token, + "id_token", &id_token, + "refresh_token", &refresh_token); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed getting token strings from JSON object.\n"); + ret = EINVAL; + goto done; + } + + attrs = sysdb_new_attrs(idp_auth_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to allocate memory for attributes.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_ACCESS_TOKEN, access_token); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", + SYSDB_ACCESS_TOKEN); + goto done; + } + + if (id_token != NULL) { + ret = sysdb_attrs_add_string(attrs, SYSDB_ID_TOKEN, id_token); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", + SYSDB_ID_TOKEN); + goto done; + } + } + + if (refresh_token != NULL) { + ret = sysdb_attrs_add_string(attrs, SYSDB_REFRESH_TOKEN, refresh_token); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", + SYSDB_REFRESH_TOKEN); + goto done; + } + } + + ret = sysdb_set_user_attr(dom, pd->user, attrs, SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + goto done; + } + +done: + talloc_free(attrs); + + return ret; +} + errno_t eval_access_token_buf(struct idp_auth_ctx *idp_auth_ctx, struct pam_data *pd, struct sss_domain_info *dom, uint8_t *buf, ssize_t buflen) @@ -116,14 +183,46 @@ errno_t eval_access_token_buf(struct idp_auth_ctx *idp_auth_ctx, const char *attrs[] = {SYSDB_UUID, NULL}; struct ldb_result *res = NULL; const char *uuid; - - /* TODO: expect access token as well */ + uint8_t *user_reply; + size_t user_reply_len; + json_error_t json_error; + json_t *token_data = NULL; + size_t token_buflen; if (buf == NULL || buflen == 0) { DEBUG(SSSDBG_OP_FAILURE, "Missing input.\n"); return EINVAL; } + user_reply = memchr(buf, '\n', buflen); + if (user_reply == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing separator in access token reply.\n"); + return EINVAL; + } + token_buflen = user_reply - buf; + + user_reply_len = buflen - token_buflen - 1; + user_reply++; + + DEBUG(SSSDBG_TRACE_ALL, "Got user_reply=[%.*s] token_buf=[%.*s].\n", + (int) user_reply_len, user_reply, + (int) token_buflen, buf); + + token_data = json_loadb((const char *) buf, token_buflen, 0, &json_error); + if (token_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to parse token data on line [%d]: [%s].\n", + json_error.line, json_error.text); + ret = EINVAL; + goto done; + } + + if (!json_is_object(token_data)) { + DEBUG(SSSDBG_OP_FAILURE, "Object expected.\n"); + ret = EINVAL; + goto done; + } + ret = sysdb_get_user_attr(idp_auth_ctx, dom, pd->user, attrs, &res); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to read cache data for user [%s].\n", @@ -145,17 +244,23 @@ errno_t eval_access_token_buf(struct idp_auth_ctx *idp_auth_ctx, goto done; } - if (strncmp(uuid, (char *) buf, buflen) != 0) { + if (strncmp(uuid, (char *) user_reply, user_reply_len) != 0) { DEBUG(SSSDBG_OP_FAILURE, "UUID [%s] of user [%s] and input [%.*s] do not match.\n", - uuid, pd->user, (int) buflen, buf); + uuid, pd->user, (int) user_reply_len, user_reply); ret = ENOENT; goto done; } - ret = EOK; + ret = store_json_tokens(idp_auth_ctx, pd, token_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store tokens in cache for user [%s].\n", pd->user); + goto done; + } done: + json_decref(token_data); talloc_free(res); return ret; From 08d5704eac5a001eaf7232acafec64375a262e3f Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Tue, 9 Dec 2025 16:36:18 +0100 Subject: [PATCH 04/12] oidc_child: add --refresh-access-token flag --- src/oidc_child/oidc_child.c | 130 +++++++++++++++++++++++++++---- src/oidc_child/oidc_child_curl.c | 59 ++++++++++++++ src/oidc_child/oidc_child_util.h | 6 ++ 3 files changed, 181 insertions(+), 14 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 85cc22c7df..ebea3dc19f 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -45,6 +45,7 @@ const char *oidc_cmd_str[] = { "get-user-groups", "get-group", "get-group-members", + "refresh-access-token", NULL }; @@ -147,6 +148,63 @@ static errno_t read_device_code_from_stdin(struct devicecode_ctx *dc_ctx, return EOK; } +static errno_t read_refresh_token_from_stdin(struct devicecode_ctx *dc_ctx, + char **token_out, + char **secret_out) +{ + char *str; + errno_t ret; + char *sep; + char *end; + char *tmp; + + ret = read_from_stdin(dc_ctx, &str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "read_from_stdin failed.\n"); + return ret; + } + + if (secret_out != NULL) { + /* expect the client secret in the first line */ + sep = strchr(str, '\n'); + if (sep == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Format error, expecting client secret and refresh token.\n"); + ret = EINVAL; + goto fail; + } + *sep = '\0'; + *secret_out = str; + sep++; + } else { + sep = str; + } + + /* NULL-terminate the token */ + end = strchr(sep, '\n'); + if (end != NULL) { + *end = '\0'; + } + + tmp = talloc_strdup(dc_ctx, sep); + if (tmp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to copy refresh token.\n"); + ret = ENOMEM; + goto fail; + } + talloc_set_destructor((void *) tmp, sss_erase_talloc_mem_securely); + *token_out = tmp; + + DEBUG(SSSDBG_TRACE_ALL, "Refresh token read from stdin: [%s].\n", tmp); + + return EOK; + +fail: + talloc_free(str); + return ret; +} + static errno_t read_client_secret_from_stdin(TALLOC_CTX *mem_ctx, char **out) { @@ -337,6 +395,8 @@ static int parse_cli(int argc, const char *argv[], struct cli_opts *opts) _("Lookup a group"), NULL}, {"get-group-members", 0, POPT_ARG_VAL, &opts->oidc_cmd, GET_GROUP_MEMBERS, _("Lookup members of a group"), NULL}, + {"refresh-access-token", 0, POPT_ARG_VAL, &opts->oidc_cmd, REFRESH_ACCESS_TOKEN, + _("Refresh access token"), NULL}, {"issuer-url", 0, POPT_ARG_STRING, &opts->issuer_url, 0, _("URL of Issuer IdP"), NULL}, {"device-auth-endpoint", 0, POPT_ARG_STRING, &opts->device_auth_endpoint, 0, @@ -400,7 +460,8 @@ static int parse_cli(int argc, const char *argv[], struct cli_opts *opts) } if (opts->oidc_cmd == GET_ACCESS_TOKEN - || opts->oidc_cmd == GET_DEVICE_CODE) { + || opts->oidc_cmd == GET_DEVICE_CODE + || opts->oidc_cmd == REFRESH_ACCESS_TOKEN) { if (!( ((opts->issuer_url != NULL) != (opts->device_auth_endpoint != NULL)) && ((opts->issuer_url != NULL) != (opts->token_endpoint != NULL)) @@ -557,7 +618,8 @@ int main(int argc, const char *argv[]) } talloc_steal(main_ctx, debug_prg_name); - if (opts.oidc_cmd == GET_DEVICE_CODE || IS_ID_CMD(opts.oidc_cmd)) { + if (opts.oidc_cmd == GET_DEVICE_CODE + || IS_ID_CMD(opts.oidc_cmd)) { if (opts.client_secret_stdin) { ret = read_client_secret_from_stdin(main_ctx, &client_secret_tmp); if (ret != EOK || client_secret_tmp == NULL) { @@ -594,7 +656,9 @@ int main(int argc, const char *argv[]) goto success; } - if (opts.oidc_cmd == GET_DEVICE_CODE || opts.oidc_cmd == GET_ACCESS_TOKEN) { + if (opts.oidc_cmd == GET_DEVICE_CODE + || opts.oidc_cmd == GET_ACCESS_TOKEN + || opts.oidc_cmd == REFRESH_ACCESS_TOKEN) { dc_ctx = get_dc_ctx(main_ctx, opts.libcurl_debug, opts.ca_db, opts.issuer_url, opts.device_auth_endpoint, opts.token_endpoint, @@ -643,19 +707,54 @@ int main(int argc, const char *argv[]) } } - ret = parse_result(dc_ctx); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to parse device code reply.\n"); - goto done; + if (opts.oidc_cmd == GET_DEVICE_CODE || opts.oidc_cmd == GET_ACCESS_TOKEN) { + ret = parse_result(dc_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse device code reply.\n"); + goto done; + } + + trace_device_code(dc_ctx, (opts.oidc_cmd == GET_DEVICE_CODE)); + + ret = get_token(main_ctx, dc_ctx, opts.client_id, opts.client_secret, + (opts.oidc_cmd == GET_DEVICE_CODE)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get user token.\n"); + goto done; + } } - trace_device_code(dc_ctx, (opts.oidc_cmd == GET_DEVICE_CODE)); + if (opts.oidc_cmd == REFRESH_ACCESS_TOKEN) { + char *token = NULL; - ret = get_token(main_ctx, dc_ctx, opts.client_id, opts.client_secret, - (opts.oidc_cmd == GET_DEVICE_CODE)); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to get user token.\n"); - goto done; + ret = read_refresh_token_from_stdin(dc_ctx, &token, + opts.client_secret_stdin + ? &client_secret_tmp + : NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read refresh token from stdin.\n"); + goto done; + } + + if (opts.client_secret_stdin) { + opts.client_secret = strdup(client_secret_tmp); + sss_erase_mem_securely(client_secret_tmp, strlen(client_secret_tmp)); + if (opts.client_secret == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to copy client secret.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = refresh_token(main_ctx, dc_ctx, opts.client_id, opts.client_secret, token); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to refresh user token.\n"); + goto done; + } + + talloc_free(token); } if (opts.oidc_cmd == GET_DEVICE_CODE) { @@ -676,7 +775,10 @@ int main(int argc, const char *argv[]) fflush(stdout); } - if (opts.oidc_cmd == GET_ACCESS_TOKEN) { + if (opts.oidc_cmd == GET_ACCESS_TOKEN + || opts.oidc_cmd == REFRESH_ACCESS_TOKEN) { + json_t *tmp; + DEBUG(SSSDBG_TRACE_ALL, "access_token: [%s].\n", dc_ctx->td->access_token_str); if (dc_ctx->td->id_token_str != NULL) { diff --git a/src/oidc_child/oidc_child_curl.c b/src/oidc_child/oidc_child_curl.c index e4a5fc399d..4a973ee721 100644 --- a/src/oidc_child/oidc_child_curl.c +++ b/src/oidc_child/oidc_child_curl.c @@ -695,3 +695,62 @@ errno_t client_credentials_grant(struct rest_ctx *rest_ctx, talloc_free(post_data); return ret; } + +errno_t refresh_token(TALLOC_CTX *mem_ctx, + struct devicecode_ctx *dc_ctx, const char *client_id, + const char *client_secret, + const char *token) +{ + int ret; + char *error_description = NULL; + char *post_data = NULL; + + post_data = talloc_asprintf(mem_ctx, "grant_type=refresh_token&refresh_token=%s&client_id=%s", + token, client_id); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to generate POST data.\n"); + ret = ENOMEM; + goto done; + } + + if (client_secret != NULL) { + post_data = talloc_asprintf_append(post_data, "&client_secret=%s", + client_secret); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add client secret to POST data.\n"); + ret = ENOMEM; + goto done; + } + } + + post_data = talloc_asprintf_append(post_data, "&scope=%s", + dc_ctx->scope != NULL + ? dc_ctx->scope + : DEFAULT_SCOPE); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add scope to POST data.\n"); + ret = ENOMEM; + goto done; + } + + clean_http_data(dc_ctx->rest_ctx); + + ret = do_http_request(dc_ctx->rest_ctx, dc_ctx->token_endpoint, post_data, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "http request failed.\n"); + goto done; + } + + ret = parse_token_result(dc_ctx, &error_description); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get token.\n"); + goto done; + } + +done: + talloc_free(post_data); + talloc_free(error_description); + return ret; +} diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index 96dcd9b8ac..4e66608c57 100644 --- a/src/oidc_child/oidc_child_util.h +++ b/src/oidc_child/oidc_child_util.h @@ -36,6 +36,7 @@ enum oidc_cmd { GET_USER_GROUPS, GET_GROUP, GET_GROUP_MEMBERS, + REFRESH_ACCESS_TOKEN, CMD_SENTINEL }; @@ -113,6 +114,11 @@ errno_t get_token(TALLOC_CTX *mem_ctx, const char *client_secret, bool get_device_code); +errno_t refresh_token(TALLOC_CTX *mem_ctx, + struct devicecode_ctx *dc_ctx, const char *client_id, + const char *client_secret, + const char *token); + errno_t get_userinfo(struct devicecode_ctx *dc_ctx); From 3f2a630da69eaa0a27e27bbdd194a95d98d48d18 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Thu, 11 Dec 2025 17:17:56 +0100 Subject: [PATCH 05/12] idp: automatically refresh tokens --- src/oidc_child/oidc_child.c | 24 ++-- src/oidc_child/oidc_child_json.c | 30 +++++ src/providers/idp/idp_auth.c | 192 ++++++++++++++++++++++++++++++ src/providers/idp/idp_auth.h | 18 +++ src/providers/idp/idp_auth_eval.c | 24 +++- src/providers/idp/idp_init.c | 26 ++++ 6 files changed, 301 insertions(+), 13 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index ebea3dc19f..75c47d699e 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -550,6 +550,8 @@ void trace_tokens(struct devicecode_ctx *dc_ctx) DEBUG(SSSDBG_TRACE_ALL, "User Principal: [%s].\n", json_string_value(json_object_get(dc_ctx->td->access_token_payload, "upn"))); DEBUG(SSSDBG_TRACE_ALL, "User oid: [%s].\n", json_string_value(json_object_get(dc_ctx->td->access_token_payload, "oid"))); DEBUG(SSSDBG_TRACE_ALL, "User sub: [%s].\n", json_string_value(json_object_get(dc_ctx->td->access_token_payload, "sub"))); + DEBUG(SSSDBG_TRACE_ALL, "Issued at: [%lld].\n", (long long) json_integer_value(json_object_get(dc_ctx->td->access_token_payload, "iat"))); + DEBUG(SSSDBG_TRACE_ALL, "Expires at: [%lld].\n", (long long) json_integer_value(json_object_get(dc_ctx->td->access_token_payload, "exp"))); } if (dc_ctx->td->id_token_payload != NULL) { @@ -560,6 +562,8 @@ void trace_tokens(struct devicecode_ctx *dc_ctx) DEBUG(SSSDBG_TRACE_ALL, "User Principal: [%s].\n", json_string_value(json_object_get(dc_ctx->td->id_token_payload, "upn"))); DEBUG(SSSDBG_TRACE_ALL, "User oid: [%s].\n", json_string_value(json_object_get(dc_ctx->td->id_token_payload, "oid"))); DEBUG(SSSDBG_TRACE_ALL, "User sub: [%s].\n", json_string_value(json_object_get(dc_ctx->td->id_token_payload, "sub"))); + DEBUG(SSSDBG_TRACE_ALL, "Issued at: [%lld].\n", (long long) json_integer_value(json_object_get(dc_ctx->td->id_token_payload, "iat"))); + DEBUG(SSSDBG_TRACE_ALL, "Expires at: [%lld].\n", (long long) json_integer_value(json_object_get(dc_ctx->td->id_token_payload, "exp"))); } if (dc_ctx->td->refresh_token_payload != NULL) { @@ -570,6 +574,8 @@ void trace_tokens(struct devicecode_ctx *dc_ctx) DEBUG(SSSDBG_TRACE_ALL, "User Principal: [%s].\n", json_string_value(json_object_get(dc_ctx->td->refresh_token_payload, "upn"))); DEBUG(SSSDBG_TRACE_ALL, "User oid: [%s].\n", json_string_value(json_object_get(dc_ctx->td->refresh_token_payload, "oid"))); DEBUG(SSSDBG_TRACE_ALL, "User sub: [%s].\n", json_string_value(json_object_get(dc_ctx->td->refresh_token_payload, "sub"))); + DEBUG(SSSDBG_TRACE_ALL, "Issued at: [%lld].\n", (long long) json_integer_value(json_object_get(dc_ctx->td->refresh_token_payload, "iat"))); + DEBUG(SSSDBG_TRACE_ALL, "Expires at: [%lld].\n", (long long) json_integer_value(json_object_get(dc_ctx->td->refresh_token_payload, "exp"))); } tmp = json_dumps(dc_ctx->td->userinfo, 0); @@ -813,6 +819,15 @@ int main(int argc, const char *argv[]) goto done; } + if (dc_ctx->jwks_uri == NULL) { + /* Up to here the tokens are only decoded into JSON if + * verification keys were provided. */ + ret = decode_token(dc_ctx, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode tokens, ignored.\n"); + } + } + trace_tokens(dc_ctx); user_identifier = get_user_identifier(dc_ctx, dc_ctx->td->userinfo, @@ -823,15 +838,6 @@ int main(int argc, const char *argv[]) "User identifier not found in user info data, " "checking id token.\n"); - if (dc_ctx->jwks_uri == NULL) { - /* Up to here the tokens are only decoded into JSON if - * verification keys were provided. */ - ret = decode_token(dc_ctx, false); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to decode tokens, ignored.\n"); - } - } - if (dc_ctx->td->id_token_payload != NULL) { user_identifier = get_user_identifier(dc_ctx, dc_ctx->td->id_token_payload, opts.user_identifier_attr, diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index 9d039fe5ee..aad2d9b40d 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -1050,6 +1050,36 @@ json_t *token_data_to_json(struct devicecode_ctx *dc_ctx) } } + if (dc_ctx->td->access_token_payload != NULL) { + json_t *issued_at_obj, *expires_at_obj; + + issued_at_obj = json_object_get(dc_ctx->td->access_token_payload, "iat"); + if (issued_at_obj == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get issuance timestamp from JWT payload.\n"); + goto fail; + } + ret = json_object_set(obj, "issued_at", issued_at_obj); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add issuance timestamp to JSON object.\n"); + goto fail; + } + + expires_at_obj = json_object_get(dc_ctx->td->access_token_payload, "exp"); + if (expires_at_obj == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get expiration timestamp from JWT payload.\n"); + goto fail; + } + ret = json_object_set(obj, "expires_at", expires_at_obj); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add expiration timestamp to JSON object.\n"); + goto fail; + } + } + return obj; fail: diff --git a/src/providers/idp/idp_auth.c b/src/providers/idp/idp_auth.c index f451263eb1..ea3f3199a0 100644 --- a/src/providers/idp/idp_auth.c +++ b/src/providers/idp/idp_auth.c @@ -57,6 +57,9 @@ set_oidc_auth_extra_args(TALLOC_CTX *mem_ctx, struct idp_auth_ctx *idp_auth_ctx, case SSS_PAM_AUTHENTICATE: extra_args[c] = talloc_strdup(extra_args, "--get-access-token"); break; + case SSS_CMD_RENEW: + extra_args[c] = talloc_strdup(extra_args, "--refresh-access-token"); + break; default: DEBUG(SSSDBG_OP_FAILURE, "Unsupported pam task [%d][%s].\n", pd->cmd, sss_cmd2str(pd->cmd)); @@ -193,6 +196,59 @@ static const char *get_stored_request_data(TALLOC_CTX *mem_ctx, return send_data; } +static const char *get_refresh_request_data(TALLOC_CTX *mem_ctx, + struct idp_auth_ctx *idp_auth_ctx, + struct pam_data *pd) +{ + int ret; + const char *attrs[] = {SYSDB_REFRESH_TOKEN, NULL}; + struct ldb_result *res = NULL; + const char *send_data = NULL; + const char *token = NULL; + struct sss_domain_info *dom = NULL; + + dom = find_domain_by_name(idp_auth_ctx->be_ctx->domain, + pd->domain, + true); + if (dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain %s\n", pd->domain); + goto done; + } + + ret = sysdb_get_user_attr(idp_auth_ctx, dom, pd->user, attrs, &res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get cached refresh token for user [%s].\n", + pd->user); + goto done; + } + if (res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, "Expected 1 user, got [%d].\n", res->count); + ret = EINVAL; + goto done; + } + + token = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_REFRESH_TOKEN, NULL); + if (token == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "User [%s] has no refresh token.\n", pd->user); + ret = EINVAL; + goto done; + } + + send_data = talloc_asprintf(mem_ctx, "%s\n%s", + dp_opt_get_cstring(idp_auth_ctx->idp_options, + IDP_CLIENT_SECRET), + token); + if (send_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to generate token refresh data.\n"); + goto done; + } + +done: + talloc_free(res); + + return send_data; +} + static errno_t create_auth_send_buffer(TALLOC_CTX *mem_ctx, struct idp_auth_ctx *idp_auth_ctx, struct pam_data *pd, @@ -225,6 +281,14 @@ static errno_t create_auth_send_buffer(TALLOC_CTX *mem_ctx, goto done; } break; + case SSS_CMD_RENEW: + send_data = get_refresh_request_data(buf, idp_auth_ctx, pd); + if (send_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get stored refresh token data.\n"); + ret = ENOENT; + goto done; + } + break; default: DEBUG(SSSDBG_OP_FAILURE, "Unsupported pam task [%d][%s].\n", pd->cmd, sss_cmd2str(pd->cmd)); @@ -353,6 +417,7 @@ static void idp_auth_done(struct tevent_req *subreq) ret = eval_device_auth_buf(state->idp_auth_ctx, state->pd, buf, buflen); break; case SSS_PAM_AUTHENTICATE: + case SSS_CMD_RENEW: ret = eval_access_token_buf(state->idp_auth_ctx, state->pd, state->dom, buf, buflen); break; @@ -425,6 +490,7 @@ idp_pam_auth_handler_send(TALLOC_CTX *mem_ctx, switch (pd->cmd) { case SSS_PAM_PREAUTH: case SSS_PAM_AUTHENTICATE: + case SSS_CMD_RENEW: subreq = idp_auth_send(state, state->ev, state->be_ctx, state->auth_ctx, state->pd, state->dom); if (subreq == NULL) { @@ -490,3 +556,129 @@ idp_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, return EOK; } + +static void refresh_token_handler_done(struct tevent_req *req); +static void refresh_token_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct idp_refresh_data *refresh_data = talloc_get_type(private_data, + struct idp_refresh_data); + struct idp_auth_ctx *auth_ctx = refresh_data->auth_ctx; + + refresh_data->te = NULL; + + DEBUG(SSSDBG_TRACE_ALL, "Sending idp auth request.\n"); + refresh_data->req = idp_auth_send(refresh_data, ev, auth_ctx->be_ctx, + auth_ctx, refresh_data->pd, + refresh_data->dom); + if (refresh_data->req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "idp_auth_send failed.\n"); + return; + } + + tevent_req_set_callback(refresh_data->req, refresh_token_handler_done, + refresh_data); +} + +static void refresh_token_handler_done(struct tevent_req *req) +{ + struct idp_refresh_data *refresh_data = tevent_req_callback_data(req, + struct idp_refresh_data); + errno_t ret; + + ret = idp_auth_recv(req, &refresh_data->pd->pam_status); + talloc_free(req); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "idp auth request failed.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "idp auth request succeeded.\n"); + + if (refresh_data->pd->pam_status != PAM_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to refresh tokens for user [%s].\n", + refresh_data->pd->user); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Successfully refreshed tokens for user [%s].\n", + refresh_data->pd->user); + +done: + talloc_free(refresh_data); +} + +errno_t +create_refresh_token_timer(struct idp_auth_ctx *auth_ctx, struct pam_data *pd, + const char *user_uuid, + time_t issued_at, time_t expires_at) +{ + int ret; + struct idp_refresh_data *refresh_data; + struct timeval refresh_timestamp = { 0 }; + + if (issued_at < 0 || expires_at < 0 || issued_at >= expires_at) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid timestamps: iat=%lld, exp=%lld\n", + (long long)issued_at, (long long)expires_at); + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_ALL, "Scheduling token refresh.\n"); + + refresh_timestamp.tv_sec = issued_at + (expires_at - issued_at) / 2; + + refresh_data = talloc_zero(auth_ctx, struct idp_refresh_data); + if (refresh_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + refresh_data->auth_ctx = auth_ctx; + + ret = copy_pam_data(refresh_data, pd, &refresh_data->pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "copy_pam_data failed.\n"); + goto fail; + } + refresh_data->pd->cmd = SSS_CMD_RENEW; + sss_authtok_set_empty(refresh_data->pd->newauthtok); + + refresh_data->dom = find_domain_by_name(auth_ctx->be_ctx->domain, + refresh_data->pd->domain, + true); + if (refresh_data->dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain %s\n", + refresh_data->pd->domain); + refresh_data->pd->pam_status = PAM_SYSTEM_ERR; + ret = EINVAL; + goto fail; + } + + refresh_data->te = tevent_add_timer(auth_ctx->be_ctx->ev, refresh_data, + refresh_timestamp, + refresh_token_handler, refresh_data); + if (refresh_data->te == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to schedule token refresh.\n"); + ret = ENOMEM; + goto fail; + } + + ret = sss_ptr_hash_add_or_override(auth_ctx->token_refresh_table, user_uuid, + refresh_data, struct idp_refresh_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add scheduled token refresh to table.\n"); + goto fail; + } + + return EOK; + +fail: + talloc_free(refresh_data); + + return ret; +} diff --git a/src/providers/idp/idp_auth.h b/src/providers/idp/idp_auth.h index e8c2501eb4..4adb7db2bf 100644 --- a/src/providers/idp/idp_auth.h +++ b/src/providers/idp/idp_auth.h @@ -42,6 +42,16 @@ struct idp_auth_ctx { const char *device_auth_endpoint; const char *userinfo_endpoint; const char *scope; + + hash_table_t *token_refresh_table; +}; + +struct idp_refresh_data { + struct idp_auth_ctx *auth_ctx; + struct pam_data *pd; + struct sss_domain_info *dom; + struct tevent_timer *te; + struct tevent_req *req; }; struct tevent_req * @@ -54,4 +64,12 @@ errno_t idp_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct pam_data **_data); + +errno_t +create_refresh_token_timer(struct idp_auth_ctx *auth_ctx, + struct pam_data *pd, + const char *user_uuid, + time_t issued_at, + time_t expires_at); + #endif /* _IDP_AUTH_H_ */ diff --git a/src/providers/idp/idp_auth_eval.c b/src/providers/idp/idp_auth_eval.c index 065194f8d6..883ed9667e 100644 --- a/src/providers/idp/idp_auth_eval.c +++ b/src/providers/idp/idp_auth_eval.c @@ -109,20 +109,25 @@ errno_t eval_device_auth_buf(struct idp_auth_ctx *idp_auth_ctx, } static errno_t store_json_tokens(struct idp_auth_ctx *idp_auth_ctx, - struct pam_data *pd, json_t *token_data) + struct pam_data *pd, const char *user_uuid, + json_t *token_data) { errno_t ret; struct sysdb_attrs *attrs = NULL; char *access_token = NULL; char *id_token = NULL; char *refresh_token = NULL; + json_int_t issued_at = -1; + json_int_t expires_at = -1; struct sss_domain_info *dom = idp_auth_ctx->be_ctx->domain; - ret = json_unpack(token_data, "{s:s, s?s, s?s}", + ret = json_unpack(token_data, "{s:s, s?s, s?s, s?I, s?I}", "access_token", &access_token, "id_token", &id_token, - "refresh_token", &refresh_token); + "refresh_token", &refresh_token, + "issued_at", &issued_at, + "expires_at", &expires_at); if (ret != 0) { DEBUG(SSSDBG_OP_FAILURE, "Failed getting token strings from JSON object.\n"); @@ -169,6 +174,17 @@ static errno_t store_json_tokens(struct idp_auth_ctx *idp_auth_ctx, goto done; } + if (refresh_token != NULL) { + ret = create_refresh_token_timer(idp_auth_ctx, pd, user_uuid, + (time_t) issued_at, + (time_t) expires_at); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create timer to refresh token.\n"); + ret = EOK; + goto done; + } + } + done: talloc_free(attrs); @@ -252,7 +268,7 @@ errno_t eval_access_token_buf(struct idp_auth_ctx *idp_auth_ctx, goto done; } - ret = store_json_tokens(idp_auth_ctx, pd, token_data); + ret = store_json_tokens(idp_auth_ctx, pd, uuid, token_data); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to store tokens in cache for user [%s].\n", pd->user); diff --git a/src/providers/idp/idp_init.c b/src/providers/idp/idp_init.c index 5094edd0f9..180f6acc72 100644 --- a/src/providers/idp/idp_init.c +++ b/src/providers/idp/idp_init.c @@ -44,6 +44,21 @@ struct idp_init_ctx { const char *scope; }; +static void token_refresh_table_delete_cb(hash_entry_t *item, + hash_destroy_enum type, + void *pvt) +{ + struct idp_refresh_data *refresh_data = talloc_get_type(item->value.ptr, + struct idp_refresh_data); + + /* If the request is already in progress, its handler will free the data. */ + if (refresh_data->req != NULL && tevent_req_is_in_progress(refresh_data->req)) { + return; + } + + talloc_free(refresh_data); +} + static errno_t idp_get_options(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, const char *conf_path, @@ -325,6 +340,17 @@ errno_t sssm_idp_auth_init(TALLOC_CTX *mem_ctx, goto done; } + auth_ctx->token_refresh_table = sss_ptr_hash_create(auth_ctx, + token_refresh_table_delete_cb, + NULL); + if (auth_ctx->token_refresh_table == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create hash table for token refreshes.\n"); + ret = ENOMEM; + goto done; + } + /* TODO: schedule refreshes for tokens that are already in cache. */ + auth_ctx->scope = dp_opt_get_cstring(init_ctx->opts, IDP_AUTH_SCOPE); if (auth_ctx->scope == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, From b67c6611c281a0546a99be852049b5daab985033 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Tue, 13 Jan 2026 16:26:52 +0100 Subject: [PATCH 06/12] idp: add option to automatically refresh tokens :feature: Tokens acquired from the IdP are now stored in the domain cache, and are automatically refreshed if the new option `idp_auto_refresh` is enabled. --- src/config/cfg_rules.ini | 1 + src/man/sssd-idp.5.xml | 16 ++++++++++++++++ src/providers/idp/idp_auth.c | 5 +++++ src/providers/idp/idp_common.h | 1 + src/providers/idp/idp_init.c | 16 +++++++++------- src/providers/idp/idp_opts.c | 1 + 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 7092c937a4..0e9332d1d3 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -496,6 +496,7 @@ option = idp_device_auth_endpoint option = idp_userinfo_endpoint option = idp_id_scope option = idp_auth_scope +option = idp_auto_refresh option = idmap_range_min option = idmap_range_max option = idmap_range_size diff --git a/src/man/sssd-idp.5.xml b/src/man/sssd-idp.5.xml index 14f4536576..6989c1eff4 100644 --- a/src/man/sssd-idp.5.xml +++ b/src/man/sssd-idp.5.xml @@ -193,6 +193,22 @@ + + idp_auto_refresh (boolean) + + + Refresh tokens automatically, after they have + reached about half their lifetime. + + + Note: Scheduled token refreshes are not preserved + across restarts of SSSD. + + + Default: false + + + idmap_range_min (integer) diff --git a/src/providers/idp/idp_auth.c b/src/providers/idp/idp_auth.c index ea3f3199a0..cdd8846e7b 100644 --- a/src/providers/idp/idp_auth.c +++ b/src/providers/idp/idp_auth.c @@ -622,6 +622,11 @@ create_refresh_token_timer(struct idp_auth_ctx *auth_ctx, struct pam_data *pd, struct idp_refresh_data *refresh_data; struct timeval refresh_timestamp = { 0 }; + if (!dp_opt_get_bool(auth_ctx->idp_options, IDP_AUTO_REFRESH)) { + DEBUG(SSSDBG_TRACE_ALL, "Not scheduling token refresh: 'idp_auto_refresh' not enabled.\n"); + return EOK; + } + if (issued_at < 0 || expires_at < 0 || issued_at >= expires_at) { DEBUG(SSSDBG_OP_FAILURE, "Invalid timestamps: iat=%lld, exp=%lld\n", (long long)issued_at, (long long)expires_at); diff --git a/src/providers/idp/idp_common.h b/src/providers/idp/idp_common.h index c6955d381e..f274036238 100644 --- a/src/providers/idp/idp_common.h +++ b/src/providers/idp/idp_common.h @@ -42,6 +42,7 @@ enum idp_opts { IDP_USERINFO_ENDPOINT, IDP_ID_SCOPE, IDP_AUTH_SCOPE, + IDP_AUTO_REFRESH, IDMAP_LOWER, IDMAP_UPPER, IDMAP_RANGESIZE, diff --git a/src/providers/idp/idp_init.c b/src/providers/idp/idp_init.c index 180f6acc72..31dc260234 100644 --- a/src/providers/idp/idp_init.c +++ b/src/providers/idp/idp_init.c @@ -340,16 +340,18 @@ errno_t sssm_idp_auth_init(TALLOC_CTX *mem_ctx, goto done; } - auth_ctx->token_refresh_table = sss_ptr_hash_create(auth_ctx, + if (dp_opt_get_bool(init_ctx->opts, IDP_AUTO_REFRESH)) { + auth_ctx->token_refresh_table = sss_ptr_hash_create(auth_ctx, token_refresh_table_delete_cb, NULL); - if (auth_ctx->token_refresh_table == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, - "Failed to create hash table for token refreshes.\n"); - ret = ENOMEM; - goto done; + if (auth_ctx->token_refresh_table == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create hash table for token refreshes.\n"); + ret = ENOMEM; + goto done; + } + /* TODO: schedule refreshes for tokens that are already in cache. */ } - /* TODO: schedule refreshes for tokens that are already in cache. */ auth_ctx->scope = dp_opt_get_cstring(init_ctx->opts, IDP_AUTH_SCOPE); if (auth_ctx->scope == NULL) { diff --git a/src/providers/idp/idp_opts.c b/src/providers/idp/idp_opts.c index ee6c77b8bc..f81dd6581e 100644 --- a/src/providers/idp/idp_opts.c +++ b/src/providers/idp/idp_opts.c @@ -34,6 +34,7 @@ struct dp_option default_idp_opts[] = { { "idp_userinfo_endpoint", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "idp_id_scope", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "idp_auth_scope", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "idp_auto_refresh", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, { "idmap_range_min", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, { "idmap_range_max", DP_OPT_NUMBER, { .number = 2000200000LL }, NULL_NUMBER }, { "idmap_range_size", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, From c3d2e36df9b5216a39fcbe8ff3bbadc745064071 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Fri, 6 Mar 2026 21:59:38 +0100 Subject: [PATCH 07/12] idp: delete non-replaced tokens from cache --- src/providers/idp/idp_auth_eval.c | 75 ++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/src/providers/idp/idp_auth_eval.c b/src/providers/idp/idp_auth_eval.c index 883ed9667e..543ced56c7 100644 --- a/src/providers/idp/idp_auth_eval.c +++ b/src/providers/idp/idp_auth_eval.c @@ -108,12 +108,34 @@ errno_t eval_device_auth_buf(struct idp_auth_ctx *idp_auth_ctx, return ret; } +static errno_t add_or_del_string_attr(struct sysdb_attrs *add_attrs, + struct sysdb_attrs *del_attrs, + const char *name, const char *value) +{ + int ret; + + if (value != NULL) { + ret = sysdb_attrs_add_string(add_attrs, name, value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute for addition/replacement.\n", name); + } + } else { + ret = sysdb_attrs_add_empty(del_attrs, name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute for deletion.\n", name); + } + } + + return ret; +} + static errno_t store_json_tokens(struct idp_auth_ctx *idp_auth_ctx, struct pam_data *pd, const char *user_uuid, json_t *token_data) { errno_t ret; - struct sysdb_attrs *attrs = NULL; + struct sysdb_attrs *add_attrs = NULL; + struct sysdb_attrs *del_attrs = NULL; char *access_token = NULL; char *id_token = NULL; char *refresh_token = NULL; @@ -135,40 +157,50 @@ static errno_t store_json_tokens(struct idp_auth_ctx *idp_auth_ctx, goto done; } - attrs = sysdb_new_attrs(idp_auth_ctx); - if (attrs == NULL) { + add_attrs = sysdb_new_attrs(idp_auth_ctx); + if (add_attrs == NULL) { DEBUG(SSSDBG_OP_FAILURE, - "Failed to allocate memory for attributes.\n"); + "Failed to allocate memory for attributes to be added/replaced.\n"); ret = ENOMEM; goto done; } - ret = sysdb_attrs_add_string(attrs, SYSDB_ACCESS_TOKEN, access_token); + del_attrs = sysdb_new_attrs(idp_auth_ctx); + if (del_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to allocate memory for attributes to be deleted.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(add_attrs, SYSDB_ACCESS_TOKEN, access_token); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", SYSDB_ACCESS_TOKEN); goto done; } - if (id_token != NULL) { - ret = sysdb_attrs_add_string(attrs, SYSDB_ID_TOKEN, id_token); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", - SYSDB_ID_TOKEN); - goto done; - } + ret = add_or_del_string_attr(add_attrs, del_attrs, SYSDB_ID_TOKEN, id_token); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", + SYSDB_ID_TOKEN); + goto done; } - if (refresh_token != NULL) { - ret = sysdb_attrs_add_string(attrs, SYSDB_REFRESH_TOKEN, refresh_token); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", - SYSDB_REFRESH_TOKEN); - goto done; - } + ret = add_or_del_string_attr(add_attrs, del_attrs, SYSDB_REFRESH_TOKEN, refresh_token); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add %s attribute.\n", + SYSDB_REFRESH_TOKEN); + goto done; + } + + ret = sysdb_set_user_attr(dom, pd->user, del_attrs, SYSDB_MOD_DEL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + goto done; } - ret = sysdb_set_user_attr(dom, pd->user, attrs, SYSDB_MOD_REP); + ret = sysdb_set_user_attr(dom, pd->user, add_attrs, SYSDB_MOD_REP); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); goto done; @@ -186,7 +218,8 @@ static errno_t store_json_tokens(struct idp_auth_ctx *idp_auth_ctx, } done: - talloc_free(attrs); + talloc_free(add_attrs); + talloc_free(del_attrs); return ret; } From 3d43309a00756d14d5da717ecfc5c3a19021e610 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Mon, 23 Feb 2026 14:20:48 +0100 Subject: [PATCH 08/12] idp: construct pam_data with timer --- src/providers/idp/idp_auth.c | 34 +++++++++++++++++++++++-------- src/providers/idp/idp_auth.h | 3 ++- src/providers/idp/idp_auth_eval.c | 5 ++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/providers/idp/idp_auth.c b/src/providers/idp/idp_auth.c index cdd8846e7b..ffc81e982a 100644 --- a/src/providers/idp/idp_auth.c +++ b/src/providers/idp/idp_auth.c @@ -614,13 +614,16 @@ static void refresh_token_handler_done(struct tevent_req *req) } errno_t -create_refresh_token_timer(struct idp_auth_ctx *auth_ctx, struct pam_data *pd, +create_refresh_token_timer(struct idp_auth_ctx *auth_ctx, + const char *domain, + const char *user_name, const char *user_uuid, time_t issued_at, time_t expires_at) { int ret; struct idp_refresh_data *refresh_data; struct timeval refresh_timestamp = { 0 }; + struct pam_data *pd; if (!dp_opt_get_bool(auth_ctx->idp_options, IDP_AUTO_REFRESH)) { DEBUG(SSSDBG_TRACE_ALL, "Not scheduling token refresh: 'idp_auto_refresh' not enabled.\n"); @@ -644,21 +647,36 @@ create_refresh_token_timer(struct idp_auth_ctx *auth_ctx, struct pam_data *pd, } refresh_data->auth_ctx = auth_ctx; - ret = copy_pam_data(refresh_data, pd, &refresh_data->pd); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "copy_pam_data failed.\n"); + pd = create_pam_data(refresh_data); + if (pd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "create_pam_data failed.\n"); + ret = ENOMEM; + goto fail; + } + + pd->domain = talloc_strdup(pd, domain); + if (pd->domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto fail; + } + + pd->user = talloc_strdup(pd, user_name); + if (pd->user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; goto fail; } - refresh_data->pd->cmd = SSS_CMD_RENEW; - sss_authtok_set_empty(refresh_data->pd->newauthtok); + + pd->cmd = SSS_CMD_RENEW; + refresh_data->pd = pd; refresh_data->dom = find_domain_by_name(auth_ctx->be_ctx->domain, - refresh_data->pd->domain, + domain, true); if (refresh_data->dom == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain %s\n", refresh_data->pd->domain); - refresh_data->pd->pam_status = PAM_SYSTEM_ERR; ret = EINVAL; goto fail; } diff --git a/src/providers/idp/idp_auth.h b/src/providers/idp/idp_auth.h index 4adb7db2bf..2bb5538118 100644 --- a/src/providers/idp/idp_auth.h +++ b/src/providers/idp/idp_auth.h @@ -67,7 +67,8 @@ idp_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, errno_t create_refresh_token_timer(struct idp_auth_ctx *auth_ctx, - struct pam_data *pd, + const char *domain, + const char *user_name, const char *user_uuid, time_t issued_at, time_t expires_at); diff --git a/src/providers/idp/idp_auth_eval.c b/src/providers/idp/idp_auth_eval.c index 543ced56c7..b37bdfeb47 100644 --- a/src/providers/idp/idp_auth_eval.c +++ b/src/providers/idp/idp_auth_eval.c @@ -207,7 +207,10 @@ static errno_t store_json_tokens(struct idp_auth_ctx *idp_auth_ctx, } if (refresh_token != NULL) { - ret = create_refresh_token_timer(idp_auth_ctx, pd, user_uuid, + ret = create_refresh_token_timer(idp_auth_ctx, + pd->domain, + pd->user, + user_uuid, (time_t) issued_at, (time_t) expires_at); if (ret != EOK) { From 623466184ccf0563f49148ea751cc58747e44ef1 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Fri, 13 Mar 2026 22:21:48 +0100 Subject: [PATCH 09/12] oidc_child: url-encode post data items --- src/oidc_child/oidc_child.c | 4 +- src/oidc_child/oidc_child_curl.c | 186 ++++++++++++++++++++++++------- src/oidc_child/oidc_child_json.c | 2 +- 3 files changed, 149 insertions(+), 43 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 75c47d699e..925cb09f1f 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -263,9 +263,9 @@ static errno_t set_endpoints(struct devicecode_ctx *dc_ctx, } if (scope != NULL && *scope != '\0') { - dc_ctx->scope = url_encode_string(dc_ctx, scope); + dc_ctx->scope = talloc_strdup(dc_ctx, scope); if (dc_ctx->scope == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "Failed to encode and copy scopes.\n"); + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy scopes.\n"); ret = ENOMEM; goto done; } diff --git a/src/oidc_child/oidc_child_curl.c b/src/oidc_child/oidc_child_curl.c index 4a973ee721..52b8a9f9ec 100644 --- a/src/oidc_child/oidc_child_curl.c +++ b/src/oidc_child/oidc_child_curl.c @@ -121,6 +121,56 @@ char *url_encode_string(TALLOC_CTX *mem_ctx, const char *inp) return (out); } +static char *append_to_post_data(char *str, const char *key, const char *val) +{ + CURL *curl_ctx = NULL; + char *key_enc = NULL; + char *val_enc = NULL; + char *out = NULL; + const char *fmt = str != NULL && *str != '\0' ? "&%s=%s" : "%s=%s"; + + if (key == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Missing key.\n"); + return NULL; + } + + if (val == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Missing value.\n"); + return NULL; + } + + curl_ctx = curl_easy_init(); + if (curl_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to initialize curl.\n"); + return NULL; + } + + key_enc = curl_easy_escape(curl_ctx, key, 0); + if (key_enc == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "curl_easy_escape failed for key [%s].\n", key); + goto done; + } + + val_enc = curl_easy_escape(curl_ctx, val, 0); + if (val_enc == NULL) { + /* Do not write secrets into logs if curl fails escaping. */ + DEBUG(SSSDBG_TRACE_ALL, "curl_easy_escape failed for value of [%s].\n", key); + goto done; + } + + out = talloc_asprintf_append(str, fmt, key_enc, val_enc); + if (out == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "talloc_asprintf_append failed.\n"); + goto done; + } + +done: + curl_free(key_enc); + curl_free(val_enc); + curl_easy_cleanup(curl_ctx); + return out; +} + /* The curl write_callback will always append the received data. To start a * new string call clean_http_data() before the curl request.*/ void clean_http_data(struct rest_ctx *rest_ctx) @@ -436,9 +486,9 @@ errno_t get_token(TALLOC_CTX *mem_ctx, size_t waiting_time = 0; char *error_description = NULL; char *post_data = NULL; - const char *post_data_tmpl = "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=%s&%s=%s"; struct curl_slist *headers = NULL; bool azure_fallback = false; + size_t device_code_sep; headers = curl_slist_append(headers, ACCEPT_JSON); if (headers == NULL) { @@ -446,25 +496,39 @@ errno_t get_token(TALLOC_CTX *mem_ctx, "Failed to create Accept header, trying without.\n"); } - post_data = talloc_asprintf(mem_ctx, post_data_tmpl, client_id, "device_code", - dc_ctx->device_code); + post_data = talloc_strdup(mem_ctx, "grant_type=urn:ietf:params:oauth:grant-type:device_code"); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate memory for POST data.\n"); + ret = ENOMEM; + goto done; + } + + post_data = append_to_post_data(post_data, "client_id", client_id); if (post_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to generate POST data.\n"); + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_id to POST data.\n"); ret = ENOMEM; goto done; } if (client_secret != NULL) { - post_data = talloc_asprintf_append(post_data, "&client_secret=%s", - client_secret); + post_data = append_to_post_data(post_data, "client_secret", client_secret); if (post_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to add client secret to POST data.\n"); + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_secret to POST data.\n"); ret = ENOMEM; goto done; } } + /* Remember the offset of the device code for the azure fallback later. */ + device_code_sep = strlen(post_data); + + post_data = append_to_post_data(post_data, "device_code", dc_ctx->device_code); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add device_code to POST data.\n"); + ret = ENOMEM; + goto done; + } + curl_ctx = curl_easy_init(); if (curl_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to initialize curl.\n"); @@ -498,11 +562,11 @@ errno_t get_token(TALLOC_CTX *mem_ctx, * conforming 'device_code', see e.g. * https://docs.microsoft.com/de-de/archive/blogs/azuredev/assisted-login-using-the-oauth-deviceprofile-flow * and search for 'request_content' in the code example. */ - talloc_free(post_data); - post_data = talloc_asprintf(mem_ctx, post_data_tmpl, client_id, "code", - dc_ctx->device_code); + post_data[device_code_sep] = '\0'; + post_data = append_to_post_data(post_data, "code", + dc_ctx->device_code); if (post_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to generate POST data.\n"); + DEBUG(SSSDBG_OP_FAILURE, "Failed to add code to POST data.\n"); ret = ENOMEM; goto done; } @@ -580,25 +644,36 @@ errno_t get_devicecode(struct devicecode_ctx *dc_ctx, const char *client_id, const char *client_secret) { int ret; - char *post_data = NULL; + const char *scope = dc_ctx->scope != NULL ? dc_ctx->scope : DEFAULT_SCOPE; - post_data = talloc_asprintf(dc_ctx, "client_id=%s&scope=%s", - client_id, - dc_ctx->scope != NULL ? dc_ctx->scope - : DEFAULT_SCOPE); + post_data = talloc_strdup(dc_ctx, ""); if (post_data == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate memory for POST data.\n"); - return ENOMEM; + ret = ENOMEM; + goto done; + } + + post_data = append_to_post_data(post_data, "client_id", client_id); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_id to POST data.\n"); + ret = ENOMEM; + goto done; + } + + post_data = append_to_post_data(post_data, "scope", scope); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add scope to POST data.\n"); + ret = ENOMEM; + goto done; } if (client_secret != NULL) { - post_data = talloc_asprintf_append(post_data, "&client_secret=%s", - client_secret); + post_data = append_to_post_data(post_data, "client_secret", client_secret); if (post_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to add client secret to POST data.\n"); - return ENOMEM; + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_secret to POST data.\n"); + ret = ENOMEM; + goto done; } } @@ -610,6 +685,7 @@ errno_t get_devicecode(struct devicecode_ctx *dc_ctx, DEBUG(SSSDBG_OP_FAILURE, "Failed to send device code request.\n"); } +done: talloc_free(post_data); return ret; } @@ -674,16 +750,36 @@ errno_t client_credentials_grant(struct rest_ctx *rest_ctx, const char *scope) { int ret; - char *post_data = NULL; - post_data = talloc_asprintf(rest_ctx, "grant_type=client_credentials&client_id=%s&&client_secret=%s%s%s", - client_id, client_secret, - scope != NULL ? "&scope=" : "", - scope != NULL ? scope : ""); + post_data = talloc_strdup(rest_ctx, "grant_type=client_credentials"); if (post_data == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate memory for POST data.\n"); - return ENOMEM; + ret = ENOMEM; + goto done; + } + + post_data = append_to_post_data(post_data, "client_id", client_id); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_id to POST data.\n"); + ret = ENOMEM; + goto done; + } + + post_data = append_to_post_data(post_data, "client_secret", client_secret); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_secret to POST data.\n"); + ret = ENOMEM; + goto done; + } + + if (scope != NULL) { + post_data = append_to_post_data(post_data, "scope", scope); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add scope to POST data.\n"); + ret = ENOMEM; + goto done; + } } clean_http_data(rest_ctx); @@ -692,6 +788,7 @@ errno_t client_credentials_grant(struct rest_ctx *rest_ctx, DEBUG(SSSDBG_OP_FAILURE, "Failed to send device code request.\n"); } +done: talloc_free(post_data); return ret; } @@ -704,30 +801,39 @@ errno_t refresh_token(TALLOC_CTX *mem_ctx, int ret; char *error_description = NULL; char *post_data = NULL; + const char *scope = dc_ctx->scope != NULL ? dc_ctx->scope : DEFAULT_SCOPE; - post_data = talloc_asprintf(mem_ctx, "grant_type=refresh_token&refresh_token=%s&client_id=%s", - token, client_id); + post_data = talloc_strdup(mem_ctx, "grant_type=refresh_token"); if (post_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to generate POST data.\n"); + DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate memory for POST data.\n"); + ret = ENOMEM; + goto done; + } + + post_data = append_to_post_data(post_data, "refresh_token", token); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add refresh_token to POST data.\n"); + ret = ENOMEM; + goto done; + } + + post_data = append_to_post_data(post_data, "client_id", client_id); + if (post_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_id to POST data.\n"); ret = ENOMEM; goto done; } if (client_secret != NULL) { - post_data = talloc_asprintf_append(post_data, "&client_secret=%s", - client_secret); + post_data = append_to_post_data(post_data, "client_secret", client_secret); if (post_data == NULL) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to add client secret to POST data.\n"); + DEBUG(SSSDBG_OP_FAILURE, "Failed to add client_secret to POST data.\n"); ret = ENOMEM; goto done; } } - post_data = talloc_asprintf_append(post_data, "&scope=%s", - dc_ctx->scope != NULL - ? dc_ctx->scope - : DEFAULT_SCOPE); + post_data = append_to_post_data(post_data, "scope", scope); if (post_data == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to add scope to POST data.\n"); ret = ENOMEM; diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index aad2d9b40d..249fd77ce9 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -113,7 +113,7 @@ static char *get_json_scope(TALLOC_CTX *mem_ctx, const json_t *root, if (str == NULL) { str = talloc_strdup(mem_ctx, json_string_value(s)); } else { - str = talloc_asprintf_append(str, "%%20%s", json_string_value(s)); + str = talloc_asprintf_append(str, " %s", json_string_value(s)); } if (str == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to copy '%s' string.\n", attr); From 3fdefcdf40e3681cafd7ec1ed91bc4c5ce00e373 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Fri, 13 Mar 2026 22:35:14 +0100 Subject: [PATCH 10/12] oidc_child: free json objects properly --- src/oidc_child/oidc_child_json.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index 249fd77ce9..3eba513479 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -407,6 +407,10 @@ static int token_destructor(void *p) struct token_data *td = talloc_get_type(p, struct token_data); json_decref(td->result); + json_decref(td->userinfo); + json_decref(td->access_token_payload); + json_decref(td->id_token_payload); + json_decref(td->refresh_token_payload); return 0; } From 2663e910a74f816718094b19b76cc8da270b6a81 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Fri, 13 Mar 2026 22:56:40 +0100 Subject: [PATCH 11/12] oidc_child: add macros for token names --- src/oidc_child/oidc_child_json.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index 3eba513479..89b984691e 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -415,6 +415,10 @@ static int token_destructor(void *p) return 0; } +#define ACCESS_TOKEN "access_token" +#define ID_TOKEN "id_token" +#define REFRESH_TOKEN "refresh_token" + errno_t parse_token_result(struct devicecode_ctx *dc_ctx, char **error_description) { @@ -464,18 +468,18 @@ errno_t parse_token_result(struct devicecode_ctx *dc_ctx, talloc_set_destructor((void *) dc_ctx->td, token_destructor); dc_ctx->td->result = result; dc_ctx->td->access_token = json_object_get(dc_ctx->td->result, - "access_token"); + ACCESS_TOKEN); dc_ctx->td->access_token_str = get_json_string(dc_ctx->td, dc_ctx->td->result, - "access_token"); - dc_ctx->td->id_token = json_object_get(dc_ctx->td->result, "id_token"); + ACCESS_TOKEN); + dc_ctx->td->id_token = json_object_get(dc_ctx->td->result, ID_TOKEN); dc_ctx->td->id_token_str = get_json_string(dc_ctx->td, dc_ctx->td->result, - "id_token"); + ID_TOKEN); dc_ctx->td->refresh_token = json_object_get(dc_ctx->td->result, - "refresh_token"); + REFRESH_TOKEN); dc_ctx->td->refresh_token_str = get_json_string(dc_ctx->td, dc_ctx->td->result, - "refresh_token"); + REFRESH_TOKEN); return EOK; } @@ -542,7 +546,7 @@ const char *get_user_identifier(TALLOC_CTX *mem_ctx, json_t *userinfo, const char *get_bearer_token(TALLOC_CTX *mem_ctx, const char *json_inp) { - return get_str_attr_from_json_string(mem_ctx, json_inp, "access_token"); + return get_str_attr_from_json_string(mem_ctx, json_inp, ACCESS_TOKEN); } const char *get_str_attr_from_json_string(TALLOC_CTX *mem_ctx, From b9e9cda8a5be26adf3b02a78e2f809b63d18a6c3 Mon Sep 17 00:00:00 2001 From: Timo Eisenmann Date: Thu, 11 Dec 2025 17:17:13 +0100 Subject: [PATCH 12/12] fix typos --- src/oidc_child/oidc_child.c | 2 +- src/providers/idp/idp_auth_eval.c | 4 ++-- src/providers/krb5/krb5_child.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 925cb09f1f..438f8d8150 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -879,7 +879,7 @@ int main(int argc, const char *argv[]) } success: - DEBUG(SSSDBG_IMPORTANT_INFO, "oidc_child finished successful!\n"); + DEBUG(SSSDBG_IMPORTANT_INFO, "oidc_child finished successfully!\n"); exit_status = EXIT_SUCCESS; done: diff --git a/src/providers/idp/idp_auth_eval.c b/src/providers/idp/idp_auth_eval.c index b37bdfeb47..39012b6c99 100644 --- a/src/providers/idp/idp_auth_eval.c +++ b/src/providers/idp/idp_auth_eval.c @@ -43,7 +43,7 @@ errno_t eval_device_auth_buf(struct idp_auth_ctx *idp_auth_ctx, user_reply = memchr(buf, '\n', buflen); if (user_reply == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Missing seperator in device auth reply.\n"); + DEBUG(SSSDBG_OP_FAILURE, "Missing separator in device auth reply.\n"); return EINVAL; } @@ -53,7 +53,7 @@ errno_t eval_device_auth_buf(struct idp_auth_ctx *idp_auth_ctx, end = memchr(user_reply, '\n', buflen - (user_reply - buf)); if (end == NULL) { DEBUG(SSSDBG_OP_FAILURE, - "Missing second seperator in device auth reply.\n"); + "Missing second separator in device auth reply.\n"); return EINVAL; } diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 217731aa9d..d4349ed9c5 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -1977,7 +1977,7 @@ static errno_t get_pkinit_identity(TALLOC_CTX *mem_ctx, module_name = "p11-kit-proxy.so"; } - /* The ':' character is used as a seperator and libkrb5 currently does not + /* The ':' character is used as a separator and libkrb5 currently does not * allow to escape it in names. So we have to error out if any of the * names contains a ':' */ if ((token_name != NULL && strchr(token_name, ':') != NULL)