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)