diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 04db3628bc9..0e9332d1d3d 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -486,6 +486,21 @@ 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 = idp_auto_refresh +option = idmap_range_min +option = idmap_range_max +option = idmap_range_size + # IPA provider specific options option = ipa_access_order option = ipa_anchor_uuid diff --git a/src/db/sysdb.h b/src/db/sysdb.h index 80cf1ada0b8..7882794ca64 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/man/sssd-idp.5.xml b/src/man/sssd-idp.5.xml index 14f4536576c..6989c1eff49 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/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index deb818efcda..438f8d81509 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) { @@ -205,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; } @@ -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)) @@ -489,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) { @@ -499,6 +562,20 @@ 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) { + 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"))); + 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); @@ -547,7 +624,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) { @@ -584,7 +662,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, @@ -633,19 +713,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) { @@ -666,10 +781,20 @@ 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); - 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); @@ -694,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, @@ -704,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, @@ -740,12 +865,21 @@ 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); } 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/oidc_child/oidc_child_curl.c b/src/oidc_child/oidc_child_curl.c index e4a5fc399d9..52b8a9f9ec2 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 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, "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; } } + /* 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,75 @@ 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; +} + +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; + const char *scope = dc_ctx->scope != NULL ? dc_ctx->scope : DEFAULT_SCOPE; + + post_data = talloc_strdup(mem_ctx, "grant_type=refresh_token"); + 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, "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 = 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; + } + } + + 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(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_json.c b/src/oidc_child/oidc_child_json.c index 7781931bb4d..89b984691ee 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); @@ -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; @@ -390,10 +407,18 @@ 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; } +#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) { @@ -443,13 +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); + dc_ctx->td->refresh_token_str = get_json_string(dc_ctx->td, + dc_ctx->td->result, + REFRESH_TOKEN); return EOK; } @@ -516,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, @@ -991,3 +1021,76 @@ 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; + } + } + + 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: + json_decref(obj); + return NULL; +} diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index eb64a14c58b..4e66608c57a 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 }; @@ -54,6 +55,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; }; @@ -110,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); @@ -173,6 +182,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.c b/src/providers/idp/idp_auth.c index f451263eb11..ffc81e982aa 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,152 @@ 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, + 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"); + 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); + 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; + + 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; + } + + pd->cmd = SSS_CMD_RENEW; + refresh_data->pd = pd; + + refresh_data->dom = find_domain_by_name(auth_ctx->be_ctx->domain, + domain, + true); + if (refresh_data->dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain %s\n", + refresh_data->pd->domain); + 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 e8c2501eb42..2bb55381186 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,13 @@ 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, + const char *domain, + const char *user_name, + 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 9c9444da0b3..39012b6c99d 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; } @@ -108,6 +108,125 @@ 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 *add_attrs = NULL; + struct sysdb_attrs *del_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, s?I, s?I}", + "access_token", &access_token, + "id_token", &id_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"); + ret = EINVAL; + goto done; + } + + add_attrs = sysdb_new_attrs(idp_auth_ctx); + if (add_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to allocate memory for attributes to be added/replaced.\n"); + ret = ENOMEM; + goto done; + } + + 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; + } + + 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; + } + + 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, add_attrs, SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + goto done; + } + + if (refresh_token != NULL) { + 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) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create timer to refresh token.\n"); + ret = EOK; + goto done; + } + } + +done: + talloc_free(add_attrs); + talloc_free(del_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 +235,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 +296,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, uuid, 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; diff --git a/src/providers/idp/idp_common.h b/src/providers/idp/idp_common.h index c6955d381ef..f274036238e 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 5094edd0f9b..31dc260234c 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,19 @@ errno_t sssm_idp_auth_init(TALLOC_CTX *mem_ctx, goto done; } + 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; + } + /* 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, diff --git a/src/providers/idp/idp_opts.c b/src/providers/idp/idp_opts.c index ee6c77b8bca..f81dd6581ee 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 }, diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 217731aa9d4..d4349ed9c5f 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)