Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/config/cfg_rules.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/db/sysdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 16 additions & 0 deletions src/man/sssd-idp.5.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,22 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>idp_auto_refresh (boolean)</term>
<listitem>
<para>
Refresh tokens automatically, after they have
reached about half their lifetime.
</para>
<para>
Note: Scheduled token refreshes are not preserved
across restarts of SSSD.
</para>
<para>
Default: false
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>idmap_range_min (integer)</term>
<listitem>
Expand Down
190 changes: 162 additions & 28 deletions src/oidc_child/oidc_child.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const char *oidc_cmd_str[] = {
"get-user-groups",
"get-group",
"get-group-members",
"refresh-access-token",
NULL
};

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this require either issuer_url or both device_auth_endpoint and token_endpoint for REFRESH_ACCESS_TOKEN?

Copy link
Contributor Author

@eisenmann-b1 eisenmann-b1 Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly as you say. This just checks for illegal combinations of options.
A simpler (although more verbose) way to write this would be (ignoring the negation at the beginning):

    (issuer_url && !device_auth_endpoint && !token_endpoint)
|| (!issuer_url &&  device_auth_endpoint &&  token_endpoint)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, even though device_auth_endpoint is technically not needed for REFRESH_ACCESS_TOKEN, neither is it for GET_ACCESS_TOKEN, so I check here as well for consistency.

Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
Loading
Loading