From 91cf935155e15f81ee4ce4808110ddf48774928b Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Wed, 8 Apr 2026 14:24:44 +0000 Subject: [PATCH 01/13] idp: implement authentik idp :relnote: Add Authentik as an IDP integration option. --- src/man/sssd-idp.5.xml | 17 +++- src/oidc_child/oidc_child.c | 2 +- src/oidc_child/oidc_child_id.c | 169 +++++++++++++++++++++++++++++++ src/oidc_child/oidc_child_json.c | 47 +++++++-- src/oidc_child/oidc_child_util.h | 4 +- src/providers/idp/idp_id_eval.c | 10 +- 6 files changed, 235 insertions(+), 14 deletions(-) diff --git a/src/man/sssd-idp.5.xml b/src/man/sssd-idp.5.xml index 6166fd2fb50..7ce5912c436 100644 --- a/src/man/sssd-idp.5.xml +++ b/src/man/sssd-idp.5.xml @@ -276,7 +276,7 @@ id_provider = idp idp_type = entra_id idp_client_id = 12345678-abcd-0101-efef-ba9876543210 -idp_client_secret = YOUR-CLIENT-SCERET +idp_client_secret = YOUR-CLIENT-SECRET idp_token_endpoint = https://login.microsoftonline.com/TENNANT-ID/oauth2/v2.0/token idp_userinfo_endpoint = https://graph.microsoft.com/v1.0/me idp_device_auth_endpoint = https://login.microsoftonline.com/TENNANT-ID/oauth2/v2.0/devicecode @@ -288,13 +288,26 @@ idp_auth_scope = openid profile email idp_type = keycloak:https://master.keycloak.test:8443/auth/admin/realms/master/ id_provider = idp idp_client_id = myclient -idp_client_secret = YOUR-CLIENT-SCERET +idp_client_secret = YOUR-CLIENT-SECRET idp_token_endpoint = https://master.keycloak.test:8443/auth/realms/master/protocol/openid-connect/token idp_userinfo_endpoint = https://master.keycloak.test:8443/auth/realms/master/protocol/openid-connect/userinfo idp_device_auth_endpoint = https://master.keycloak.test:8443/auth/realms/master/protocol/openid-connect/auth/device idp_id_scope = profile idp_auth_scope = openid profile email + +[domain/authentik] +id_provider = idp +idp_type = authentik:https://authentik.company/api/v3/core/ +idp_client_id = myclient +idp_client_secret = YOUR-CLIENT-SECRET +idp_token_endpoint = https://authentik.company/application/o/token/ +idp_userinfo_endpoint = https://authentik.company/application/o/userinfo/ +idp_device_auth_endpoint = https://authentik.company/application/o/device/ +idp_id_scope = goauthentik.io/api +idp_auth_scope = openid profile email offline_access + + diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 8aa7f7ea46e..0f578dd56cd 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -894,7 +894,7 @@ int main(int argc, const char *argv[]) } if (dc_ctx->jwks_uri != NULL) { - ret = decode_token(dc_ctx, true); + ret = decode_token(dc_ctx, true, opts.idp_type); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to verify tokens.\n"); goto done; diff --git a/src/oidc_child/oidc_child_id.c b/src/oidc_child/oidc_child_id.c index 76ca1d19a6f..e206ffa626b 100644 --- a/src/oidc_child/oidc_child_id.c +++ b/src/oidc_child/oidc_child_id.c @@ -465,6 +465,170 @@ errno_t keycloak_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, return ret; } +/* The following function will lookup users and groups based on Authentik's + * REST API as described in + * https://api.goauthentik.io/ */ +errno_t authentik_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, + char *base_url, + char *input, enum search_str_type input_type, + bool libcurl_debug, const char *ca_db, + const char *client_id, const char *client_secret, + const char *token_endpoint, const char *scope, + const char *bearer_token, struct rest_ctx *rest_ctx, + char **out) +{ + errno_t ret; + char *uri; + char *filter; + char *input_enc; + const char *obj_id; + char *sep; + char *tmp; + struct name_and_type_identifier authentik_name_and_type_identifier = { + .user_identifier_attr = "username", + .group_identifier_attr = "name", + .user_name_attr = "username", + .group_name_attr = "name" }; + + if (base_url == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing base URL in IdP type [authentik].\n"); + return EINVAL; + } + + input_enc = url_encode_string(rest_ctx, input); + if (input_enc == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to encode input [%s].\n", input); + return EINVAL; + } + + switch (oidc_cmd) { + case GET_USER: + case GET_USER_GROUPS: + filter = talloc_asprintf(rest_ctx, "username=%s", input_enc); + break; + case GET_GROUP: + case GET_GROUP_MEMBERS: + sep = strrchr(input, '@'); + if (sep == NULL || sep == input) { + filter = talloc_asprintf(rest_ctx, "include_users=true&name=%s", input_enc); + } else { + filter = talloc_asprintf(rest_ctx, "include_users=true&search=%s" , input_enc); + } + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown command [%d].\n", oidc_cmd); + ret = EINVAL; + goto done; + } + + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create user search filter.\n"); + ret = ENOMEM; + goto done; + } + + switch (oidc_cmd) { + case GET_USER: + case GET_USER_GROUPS: + uri = talloc_asprintf(rest_ctx, "%s/users/?%s" ,base_url, filter); + break; + case GET_GROUP: + case GET_GROUP_MEMBERS: + uri = talloc_asprintf(rest_ctx, "%s/groups/?%s" ,base_url, filter); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown command [%d].\n", oidc_cmd); + ret = EINVAL; + goto done; + } + + if (uri == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to generate lookup URI.\n"); + ret = ENOMEM; + goto done; + } + + clean_http_data(rest_ctx); + ret = do_http_request(rest_ctx, uri, NULL, bearer_token); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "User search request failed.\n"); + goto done; + } + + if (oidc_cmd == GET_USER || oidc_cmd == GET_GROUP) { + ret = EOK; + goto done; + } + + obj_id = get_str_attr_from_embed_json_string( + rest_ctx, get_http_data(rest_ctx), "results", "pk"); + + if (obj_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read mandatory object id.\n"); + ret = EINVAL; + goto done; + } + + switch (oidc_cmd) { + case GET_USER_GROUPS: + uri = talloc_asprintf(rest_ctx, + "%s/groups/?include_users=false&members_by_pk=%s&page=1&page_size=2000", base_url, obj_id); + break; + case GET_GROUP_MEMBERS: + uri = talloc_asprintf(rest_ctx, + "%s/users/?groups_by_pk=%s&include_groups=false&include_roles=false&page=1&page_size=2000", base_url, obj_id); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown command [%d].\n", oidc_cmd); + ret = EINVAL; + goto done; + } + + if (uri == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to generate lookup URI.\n"); + ret = ENOMEM; + goto done; + } + + clean_http_data(rest_ctx); + switch (oidc_cmd) { + case GET_USER_GROUPS: + case GET_GROUP_MEMBERS: + ret = do_http_request_json_data(rest_ctx, uri, NULL, bearer_token); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown command [%d].\n", oidc_cmd); + ret = EINVAL; + goto done; + } + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Member(of) search request failed.\n"); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK && out != NULL) { + tmp = get_json_string_array_from_json_string( + mem_ctx, get_http_data(rest_ctx), "results"); + + if (tmp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy output data.\n"); + return ENOMEM; + } + ret = add_posix_to_json_string_array(mem_ctx, + &authentik_name_and_type_identifier, + 0, tmp, out); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add POSIX data.\n"); + } + } + + return ret; +} + errno_t oidc_get_id(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, char *idp_type, char *input, enum search_str_type input_type, @@ -526,6 +690,11 @@ errno_t oidc_get_id(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, libcurl_debug, ca_db, client_id, client_secret, token_endpoint, scope, bearer_token, rest_ctx, out); + } else if (idp_type != NULL && strncasecmp(idp_type, "authentik:",10) == 0) { + ret = authentik_lookup(mem_ctx, oidc_cmd, base_url, input, input_type, + libcurl_debug, ca_db, client_id, client_secret, + token_endpoint, scope, bearer_token, rest_ctx, + out); } else if (idp_type == NULL || strcasecmp(idp_type, "entra_id") == 0 || strncasecmp(idp_type, "entra_id:", 9) == 0) { diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index aa630bed44c..e2588b6f78d 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -41,8 +41,19 @@ static char *get_json_string(TALLOC_CTX *mem_ctx, const json_t *root, tmp = json_object_get(root, attr); if (!json_is_string(tmp)) { + if (json_is_integer(tmp)) { + char buffer[64]; + json_int_t i_val = json_integer_value(tmp); + snprintf(buffer, sizeof(buffer), "%" JSON_INTEGER_FORMAT, i_val); + str = talloc_strdup(mem_ctx, buffer); + if (str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy '%s' string.\n", attr); + return NULL; + } + return str; + } DEBUG(SSSDBG_OP_FAILURE, - "Result does not contain the '%s' string.\n", attr); + "Result does not contain the field '%s'.\n", attr); return NULL; } @@ -265,7 +276,7 @@ static errno_t str_to_jws(TALLOC_CTX *mem_ctx, const char *inp, json_t **jws) * understand if and how the keys based verification can be used so that we * might add new options to tune the verification for different IdPs. */ -errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify) +errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify, char *idp_type) { int ret; json_t *keys = NULL; @@ -381,11 +392,12 @@ errno_t parse_openid_configuration(struct devicecode_ctx *dc_ctx) return ret; } -errno_t parse_result(struct devicecode_ctx *dc_ctx) +errno_t parse_result(struct devicecode_ctx *dc_ctx, char *idp_type) { int ret; json_t *root = NULL; json_error_t json_error; + char *dc_enc; root = json_loads(get_http_data(dc_ctx->rest_ctx), 0, &json_error); if (root == NULL) { @@ -400,7 +412,24 @@ errno_t parse_result(struct devicecode_ctx *dc_ctx) if (dc_ctx->user_code != NULL) { talloc_set_destructor((void *) dc_ctx->user_code, sss_erase_talloc_mem_securely); } - dc_ctx->device_code = get_json_string(dc_ctx, root, "device_code"); + + /* as get_json_string() strips escape sequences, this is not guaranteed to be urlsafe anymore. + * This is relevant for Authentik, as its' device codes contain special chars. */ + dc_enc = get_json_string(dc_ctx, root, "device_code"); + if (dc_enc != NULL && dc_ctx->user_code != NULL && + (idp_type != NULL && strncasecmp(idp_type, "authentik:",10) == 0)) { + /* when loading a stored request there is no user code, + * so we skip encoding to avoid double urlencodes. */ + dc_enc = url_encode_string(dc_ctx->rest_ctx, dc_enc); + } + + if (dc_enc == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to encode device code.\n"); + ret = EINVAL; + goto done; + } + + dc_ctx->device_code = dc_enc; if (dc_ctx->device_code != NULL) { talloc_set_destructor((void *) dc_ctx->device_code, sss_erase_talloc_mem_securely); } @@ -532,13 +561,17 @@ const char *get_user_identifier(TALLOC_CTX *mem_ctx, json_t *userinfo, { json_t *id_object = NULL; const char *user_identifier = NULL; - const char *id_attr_list[] = { "sub", "id", NULL }; + const char *id_attr_list[4]; + int id_attr_index = 0; size_t c; if (user_identifier_attr != NULL) { - id_attr_list[0] = user_identifier_attr; - id_attr_list[1] = NULL; + id_attr_list[id_attr_index++] = user_identifier_attr; } + id_attr_list[id_attr_index++] = "sub"; + id_attr_list[id_attr_index++] = "id"; + id_attr_list[id_attr_index] = NULL; + for (c = 0; id_attr_list[c] != NULL; c++) { id_object = json_object_get(userinfo, id_attr_list[c]); diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index d11fb2cb1d8..ac9cebf38c4 100644 --- a/src/oidc_child/oidc_child_util.h +++ b/src/oidc_child/oidc_child_util.h @@ -150,12 +150,12 @@ const char *rest_ctx_get_key_passwd(const struct rest_ctx *rest_ctx); /* oidc_child_json.c */ errno_t parse_openid_configuration(struct devicecode_ctx *dc_ctx); -errno_t parse_result(struct devicecode_ctx *dc_ctx); +errno_t parse_result(struct devicecode_ctx *dc_ctx, char *idp_type); errno_t parse_token_result(struct devicecode_ctx *dc_ctx, char **error_description); -errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify); +errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify, char *idp_type); const char *get_user_identifier(TALLOC_CTX *mem_ctx, json_t *userinfo, const char *user_identifier_attr, diff --git a/src/providers/idp/idp_id_eval.c b/src/providers/idp/idp_id_eval.c index 808eed460ec..ff749f4f840 100644 --- a/src/providers/idp/idp_id_eval.c +++ b/src/providers/idp/idp_id_eval.c @@ -62,9 +62,12 @@ static errno_t store_json_user(struct idp_id_ctx *idp_id_ctx, json_t *user, } uuid = json_object_get(user, "id"); + if (!json_is_string(uuid)) { + uuid = json_object_get(user, "uid"); + } if (!json_is_string(uuid)) { DEBUG(SSSDBG_OP_FAILURE, - "JSON user object does not contain 'id' string.\n"); + "JSON user object does not contain 'id' or 'uid' string.\n"); ret = EINVAL; goto done; } @@ -163,9 +166,12 @@ static errno_t store_json_group(struct idp_id_ctx *idp_id_ctx, json_t *group, } uuid = json_object_get(group, "id"); + if (!json_is_string(uuid)) { + uuid = json_object_get(group, "pk"); + } if (!json_is_string(uuid)) { DEBUG(SSSDBG_OP_FAILURE, - "JSON group object does not contain 'id' string.\n"); + "JSON group object does not contain 'id' or 'pk' string.\n"); ret = EINVAL; goto done; } From f5fd166f303db5a150a06bc7af4a0c7cbc582d3d Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Mon, 20 Apr 2026 11:52:22 +0000 Subject: [PATCH 02/13] idp: manpage update for authentik idp --- src/man/sssd-idp.5.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/man/sssd-idp.5.xml b/src/man/sssd-idp.5.xml index 7ce5912c436..b474a1cd3a6 100644 --- a/src/man/sssd-idp.5.xml +++ b/src/man/sssd-idp.5.xml @@ -63,8 +63,8 @@ Required option that specifies the IdP product. - Currently Entra ID (entra_id) and Keycloak - (keycloak) are supported. + Currently Entra ID (entra_id), Keycloak + (keycloak), and Authentik (authentik) are supported. Depending on the IdP product additional platform From bf4443de214362c98af344fe202052a115aea16e Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Wed, 22 Apr 2026 13:12:00 +0000 Subject: [PATCH 03/13] remove unused variable from decode_token --- src/oidc_child/oidc_child.c | 2 +- src/oidc_child/oidc_child_json.c | 2 +- src/oidc_child/oidc_child_util.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 0f578dd56cd..8aa7f7ea46e 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -894,7 +894,7 @@ int main(int argc, const char *argv[]) } if (dc_ctx->jwks_uri != NULL) { - ret = decode_token(dc_ctx, true, opts.idp_type); + ret = decode_token(dc_ctx, true); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to verify tokens.\n"); goto done; diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index e2588b6f78d..af6b4a53a64 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -276,7 +276,7 @@ static errno_t str_to_jws(TALLOC_CTX *mem_ctx, const char *inp, json_t **jws) * understand if and how the keys based verification can be used so that we * might add new options to tune the verification for different IdPs. */ -errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify, char *idp_type) +errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify) { int ret; json_t *keys = NULL; diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index ac9cebf38c4..3c61299bc10 100644 --- a/src/oidc_child/oidc_child_util.h +++ b/src/oidc_child/oidc_child_util.h @@ -155,7 +155,7 @@ errno_t parse_result(struct devicecode_ctx *dc_ctx, char *idp_type); errno_t parse_token_result(struct devicecode_ctx *dc_ctx, char **error_description); -errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify, char *idp_type); +errno_t decode_token(struct devicecode_ctx *dc_ctx, bool verify); const char *get_user_identifier(TALLOC_CTX *mem_ctx, json_t *userinfo, const char *user_identifier_attr, From eeff2505605ca43e2e348d36162ccfb8e366d9b5 Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Thu, 23 Apr 2026 08:28:05 +0000 Subject: [PATCH 04/13] fix updated function signature --- src/oidc_child/oidc_child.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 8aa7f7ea46e..46f722452b0 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -811,7 +811,7 @@ int main(int argc, const char *argv[]) } if (opts.oidc_cmd == GET_DEVICE_CODE || opts.oidc_cmd == GET_ACCESS_TOKEN) { - ret = parse_result(dc_ctx); + ret = parse_result(dc_ctx, opts.idp_type); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to parse device code reply.\n"); goto done; From d10209ce5838c5c137a52146601d4f7b47b436fb Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Thu, 23 Apr 2026 14:36:41 +0000 Subject: [PATCH 05/13] Remove unused encoding function, add fixme --- src/oidc_child/oidc_child_json.c | 19 +------------------ src/providers/idp/idp_auth_eval.c | 2 ++ 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index af6b4a53a64..e6b00598eb8 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -397,7 +397,6 @@ errno_t parse_result(struct devicecode_ctx *dc_ctx, char *idp_type) int ret; json_t *root = NULL; json_error_t json_error; - char *dc_enc; root = json_loads(get_http_data(dc_ctx->rest_ctx), 0, &json_error); if (root == NULL) { @@ -413,23 +412,7 @@ errno_t parse_result(struct devicecode_ctx *dc_ctx, char *idp_type) talloc_set_destructor((void *) dc_ctx->user_code, sss_erase_talloc_mem_securely); } - /* as get_json_string() strips escape sequences, this is not guaranteed to be urlsafe anymore. - * This is relevant for Authentik, as its' device codes contain special chars. */ - dc_enc = get_json_string(dc_ctx, root, "device_code"); - if (dc_enc != NULL && dc_ctx->user_code != NULL && - (idp_type != NULL && strncasecmp(idp_type, "authentik:",10) == 0)) { - /* when loading a stored request there is no user code, - * so we skip encoding to avoid double urlencodes. */ - dc_enc = url_encode_string(dc_ctx->rest_ctx, dc_enc); - } - - if (dc_enc == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Failed to encode device code.\n"); - ret = EINVAL; - goto done; - } - - dc_ctx->device_code = dc_enc; + dc_ctx->device_code = get_json_string(dc_ctx, root, "device_code"); if (dc_ctx->device_code != NULL) { talloc_set_destructor((void *) dc_ctx->device_code, sss_erase_talloc_mem_securely); } diff --git a/src/providers/idp/idp_auth_eval.c b/src/providers/idp/idp_auth_eval.c index e992c07ed8b..3b0a250558e 100644 --- a/src/providers/idp/idp_auth_eval.c +++ b/src/providers/idp/idp_auth_eval.c @@ -261,6 +261,8 @@ errno_t eval_access_token_buf(struct idp_auth_ctx *idp_auth_ctx, token_data = json_loadb((const char *) buf, token_buflen, 0, &json_error); if (token_data == NULL) { + // FIXME: Authentik fails at this point, + // as the buffered device token contains special chars that let this operation fail DEBUG(SSSDBG_OP_FAILURE, "Failed to parse token data on line [%d]: [%s].\n", json_error.line, json_error.text); From 3fff66379a6a5f3101e7c72118b991305c91a96e Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Thu, 23 Apr 2026 14:43:22 +0000 Subject: [PATCH 06/13] Remove unused param to parse_result --- src/oidc_child/oidc_child.c | 2 +- src/oidc_child/oidc_child_json.c | 2 +- src/oidc_child/oidc_child_util.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 46f722452b0..8aa7f7ea46e 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -811,7 +811,7 @@ int main(int argc, const char *argv[]) } if (opts.oidc_cmd == GET_DEVICE_CODE || opts.oidc_cmd == GET_ACCESS_TOKEN) { - ret = parse_result(dc_ctx, opts.idp_type); + ret = parse_result(dc_ctx); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to parse device code reply.\n"); goto done; diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index e6b00598eb8..d68735f3ac4 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -392,7 +392,7 @@ errno_t parse_openid_configuration(struct devicecode_ctx *dc_ctx) return ret; } -errno_t parse_result(struct devicecode_ctx *dc_ctx, char *idp_type) +errno_t parse_result(struct devicecode_ctx *dc_ctx) { int ret; json_t *root = NULL; diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index 3c61299bc10..d11fb2cb1d8 100644 --- a/src/oidc_child/oidc_child_util.h +++ b/src/oidc_child/oidc_child_util.h @@ -150,7 +150,7 @@ const char *rest_ctx_get_key_passwd(const struct rest_ctx *rest_ctx); /* oidc_child_json.c */ errno_t parse_openid_configuration(struct devicecode_ctx *dc_ctx); -errno_t parse_result(struct devicecode_ctx *dc_ctx, char *idp_type); +errno_t parse_result(struct devicecode_ctx *dc_ctx); errno_t parse_token_result(struct devicecode_ctx *dc_ctx, char **error_description); From 93a3c87e9e73ea4b8bbd4752e40c154547001c1b Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Tue, 28 Apr 2026 09:36:32 +0000 Subject: [PATCH 07/13] oidc_child: update reply encoding --- src/oidc_child/oidc_child.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index 8aa7f7ea46e..ee71f93114a 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -864,18 +864,29 @@ int main(int argc, const char *argv[]) /* Currently this reply is used by ipa-otpd as RADIUS Proxy-State and * Reply-Message. */ - fprintf(stdout, - "{\"device_code\":\"%s\",\"expires_in\":%d,\"interval\":%d}\n", - dc_ctx->device_code, dc_ctx->expires_in, dc_ctx->interval); - fprintf(stdout, - "oauth2 {\"verification_uri\": \"%s\", " - "\"user_code\": \"%s%s%s\"}\n", - dc_ctx->verification_uri, dc_ctx->user_code, - dc_ctx->verification_uri_complete == NULL ? "" - : "\", \"verification_uri_complete\": \"", - dc_ctx->verification_uri_complete == NULL ? "" - : dc_ctx->verification_uri_complete); + json_t *tmp_json; + char *tmp_str; + + tmp_json = json_pack("{s:s, s:i, s:i}", + "device_code", dc_ctx->device_code, + "expires_in", dc_ctx->expires_in, + "interval", dc_ctx->interval); + tmp_str = json_dumps(tmp_json, JSON_COMPACT); + fprintf(stdout, "%s\n", tmp_str); + + tmp_json = json_pack("{s:s, s:s}", + "verification_uri", dc_ctx->verification_uri, + "user_code", dc_ctx->user_code); + if (dc_ctx->verification_uri_complete != NULL) { + json_object_set_new(tmp_json, "verification_uri_complete", + json_string(dc_ctx->verification_uri_complete)); + } + tmp_str = json_dumps(tmp_json, JSON_COMPACT); + json_decref(tmp_json); + fprintf(stdout, "oauth2 %s\n", tmp_str); + fflush(stdout); + free(tmp_str); } if (opts.oidc_cmd == GET_ACCESS_TOKEN From f1eeb1b19f9564198b61b3a41f1515323f381588 Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Mon, 4 May 2026 12:26:36 +0000 Subject: [PATCH 08/13] Remove stray whitespaces and outdated FIXME comment --- src/oidc_child/oidc_child.c | 2 +- src/oidc_child/oidc_child_json.c | 1 - src/providers/idp/idp_auth_eval.c | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index ee71f93114a..bfb2f35f8f5 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -866,7 +866,7 @@ int main(int argc, const char *argv[]) */ json_t *tmp_json; char *tmp_str; - + tmp_json = json_pack("{s:s, s:i, s:i}", "device_code", dc_ctx->device_code, "expires_in", dc_ctx->expires_in, diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index d68735f3ac4..7137d3cb002 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -411,7 +411,6 @@ errno_t parse_result(struct devicecode_ctx *dc_ctx) if (dc_ctx->user_code != NULL) { talloc_set_destructor((void *) dc_ctx->user_code, sss_erase_talloc_mem_securely); } - dc_ctx->device_code = get_json_string(dc_ctx, root, "device_code"); if (dc_ctx->device_code != NULL) { talloc_set_destructor((void *) dc_ctx->device_code, sss_erase_talloc_mem_securely); diff --git a/src/providers/idp/idp_auth_eval.c b/src/providers/idp/idp_auth_eval.c index 3b0a250558e..e992c07ed8b 100644 --- a/src/providers/idp/idp_auth_eval.c +++ b/src/providers/idp/idp_auth_eval.c @@ -261,8 +261,6 @@ errno_t eval_access_token_buf(struct idp_auth_ctx *idp_auth_ctx, token_data = json_loadb((const char *) buf, token_buflen, 0, &json_error); if (token_data == NULL) { - // FIXME: Authentik fails at this point, - // as the buffered device token contains special chars that let this operation fail DEBUG(SSSDBG_OP_FAILURE, "Failed to parse token data on line [%d]: [%s].\n", json_error.line, json_error.text); From dfa4c004b6de1c2589037fa6fd24bb13d1e65e4f Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Mon, 1 Jun 2026 12:55:18 +0000 Subject: [PATCH 09/13] fix: address feedback --- src/oidc_child/oidc_child.c | 8 ++++---- src/oidc_child/oidc_child_id.c | 9 +++++---- src/oidc_child/oidc_child_json.c | 1 - 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c index bfb2f35f8f5..46000b755e0 100644 --- a/src/oidc_child/oidc_child.c +++ b/src/oidc_child/oidc_child.c @@ -685,6 +685,8 @@ int main(int argc, const char *argv[]) int exit_status = EXIT_FAILURE; char *out = NULL; char *client_secret_tmp = NULL; + json_t *tmp_json; + char *tmp_str; ret = parse_cli(argc, argv, &opts); if (ret != EOK) { @@ -864,15 +866,14 @@ int main(int argc, const char *argv[]) /* Currently this reply is used by ipa-otpd as RADIUS Proxy-State and * Reply-Message. */ - json_t *tmp_json; - char *tmp_str; - tmp_json = json_pack("{s:s, s:i, s:i}", "device_code", dc_ctx->device_code, "expires_in", dc_ctx->expires_in, "interval", dc_ctx->interval); tmp_str = json_dumps(tmp_json, JSON_COMPACT); + json_decref(tmp_json); fprintf(stdout, "%s\n", tmp_str); + free(tmp_str); tmp_json = json_pack("{s:s, s:s}", "verification_uri", dc_ctx->verification_uri, @@ -884,7 +885,6 @@ int main(int argc, const char *argv[]) tmp_str = json_dumps(tmp_json, JSON_COMPACT); json_decref(tmp_json); fprintf(stdout, "oauth2 %s\n", tmp_str); - fflush(stdout); free(tmp_str); } diff --git a/src/oidc_child/oidc_child_id.c b/src/oidc_child/oidc_child_id.c index e206ffa626b..bd315d58668 100644 --- a/src/oidc_child/oidc_child_id.c +++ b/src/oidc_child/oidc_child_id.c @@ -512,7 +512,7 @@ errno_t authentik_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, if (sep == NULL || sep == input) { filter = talloc_asprintf(rest_ctx, "include_users=true&name=%s", input_enc); } else { - filter = talloc_asprintf(rest_ctx, "include_users=true&search=%s" , input_enc); + filter = talloc_asprintf(rest_ctx, "include_users=true&search=%s", input_enc); } break; default: @@ -576,7 +576,8 @@ errno_t authentik_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, break; case GET_GROUP_MEMBERS: uri = talloc_asprintf(rest_ctx, - "%s/users/?groups_by_pk=%s&include_groups=false&include_roles=false&page=1&page_size=2000", base_url, obj_id); + "%s/users/?groups_by_pk=%s&include_groups=false&include_roles=false&page=1&page_size=2000", + base_url, obj_id); break; default: DEBUG(SSSDBG_OP_FAILURE, "Unknown command [%d].\n", oidc_cmd); @@ -685,12 +686,12 @@ errno_t oidc_get_id(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, goto done; } - if (idp_type != NULL && strncasecmp(idp_type, "keycloak:",9) == 0) { + if (idp_type != NULL && strncasecmp(idp_type, "keycloak:", 9) == 0) { ret = keycloak_lookup(mem_ctx, oidc_cmd, base_url, input, input_type, libcurl_debug, ca_db, client_id, client_secret, token_endpoint, scope, bearer_token, rest_ctx, out); - } else if (idp_type != NULL && strncasecmp(idp_type, "authentik:",10) == 0) { + } else if (idp_type != NULL && strncasecmp(idp_type, "authentik:", 10) == 0) { ret = authentik_lookup(mem_ctx, oidc_cmd, base_url, input, input_type, libcurl_debug, ca_db, client_id, client_secret, token_endpoint, scope, bearer_token, rest_ctx, diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index 7137d3cb002..8a8a017e72b 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -554,7 +554,6 @@ const char *get_user_identifier(TALLOC_CTX *mem_ctx, json_t *userinfo, id_attr_list[id_attr_index++] = "id"; id_attr_list[id_attr_index] = NULL; - for (c = 0; id_attr_list[c] != NULL; c++) { id_object = json_object_get(userinfo, id_attr_list[c]); if (id_object != NULL) { From a4374c8538134b0d4b3729b2c32405f81b6297c3 Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Mon, 1 Jun 2026 13:05:20 +0000 Subject: [PATCH 10/13] fead: add pagination warning to authentik OIDC --- src/oidc_child/oidc_child_id.c | 17 ++++++++++++----- src/oidc_child/oidc_child_json.c | 19 +++++++++++++++++++ src/oidc_child/oidc_child_util.h | 2 ++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/oidc_child/oidc_child_id.c b/src/oidc_child/oidc_child_id.c index bd315d58668..f9f78c34e9e 100644 --- a/src/oidc_child/oidc_child_id.c +++ b/src/oidc_child/oidc_child_id.c @@ -484,6 +484,7 @@ errno_t authentik_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, const char *obj_id; char *sep; char *tmp; + int page_count; struct name_and_type_identifier authentik_name_and_type_identifier = { .user_identifier_attr = "username", .group_identifier_attr = "name", @@ -572,11 +573,12 @@ errno_t authentik_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, switch (oidc_cmd) { case GET_USER_GROUPS: uri = talloc_asprintf(rest_ctx, - "%s/groups/?include_users=false&members_by_pk=%s&page=1&page_size=2000", base_url, obj_id); + "%s/groups/?include_users=false&members_by_pk=%s&page=1&page_size=100", + base_url, obj_id); break; case GET_GROUP_MEMBERS: uri = talloc_asprintf(rest_ctx, - "%s/users/?groups_by_pk=%s&include_groups=false&include_roles=false&page=1&page_size=2000", + "%s/users/?groups_by_pk=%s&include_groups=false&include_roles=false&page=1&page_size=100", base_url, obj_id); break; default: @@ -612,16 +614,21 @@ errno_t authentik_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, done: if (ret == EOK && out != NULL) { + page_count = get_authentik_pagination(get_http_data(rest_ctx)); + if (page_count > 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "WARNING: Authentik results have been " + "truncated. User or Group information might not be complete.\n"); + } + tmp = get_json_string_array_from_json_string( mem_ctx, get_http_data(rest_ctx), "results"); - if (tmp == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to copy output data.\n"); return ENOMEM; } ret = add_posix_to_json_string_array(mem_ctx, - &authentik_name_and_type_identifier, - 0, tmp, out); + &authentik_name_and_type_identifier, + 0, tmp, out); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to add POSIX data.\n"); } diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index 8a8a017e72b..ddcbc25e977 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -614,6 +614,25 @@ const char *get_str_attr_from_json_string(TALLOC_CTX *mem_ctx, return attr; } +int get_authentik_pagination(const char *json_str) +{ + json_error_t json_error; + json_t *result = NULL; + json_t *pagination_data = NULL; + int page_count; + + result = json_loads(json_str, 0, &json_error); + if (result == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to parse json data on line [%d]: [%s].\n", + json_error.line, json_error.text); + return ENOMEM; + } + pagination_data = json_object_get(result, "pagination"); + page_count = get_json_integer(pagination_data, "total_pages", true); + return page_count; +} + const char *get_str_attr_from_json_array_string(TALLOC_CTX *mem_ctx, const char *json_str, const char *attr_name) diff --git a/src/oidc_child/oidc_child_util.h b/src/oidc_child/oidc_child_util.h index d11fb2cb1d8..2b7e08465fe 100644 --- a/src/oidc_child/oidc_child_util.h +++ b/src/oidc_child/oidc_child_util.h @@ -200,6 +200,8 @@ json_t *token_data_to_json(struct devicecode_ctx *dc_ctx); char *get_jwt(struct rest_ctx *rest_ctx, const char *token_endpoint, const char *client_id); +int get_authentik_pagination(const char *json_str); + /* oidc_child_id.c */ errno_t oidc_get_id(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, char *idp_type, From 71fce4cf6ff762ae39b0e6f8c89163b1def47bc3 Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Mon, 8 Jun 2026 13:00:42 +0000 Subject: [PATCH 11/13] oidc-child: fix memory leak --- src/oidc_child/oidc_child_json.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index ddcbc25e977..5e97533725f 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -629,6 +629,7 @@ int get_authentik_pagination(const char *json_str) return ENOMEM; } pagination_data = json_object_get(result, "pagination"); + json_decref(result); page_count = get_json_integer(pagination_data, "total_pages", true); return page_count; } From e61173ab2d865d8f0cf8ba0f27be93cfdcfbf8b1 Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Tue, 9 Jun 2026 12:33:33 +0000 Subject: [PATCH 12/13] oidc-child: move json_decref --- src/oidc_child/oidc_child_json.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidc_child/oidc_child_json.c b/src/oidc_child/oidc_child_json.c index 5e97533725f..4e79cd67914 100644 --- a/src/oidc_child/oidc_child_json.c +++ b/src/oidc_child/oidc_child_json.c @@ -629,8 +629,8 @@ int get_authentik_pagination(const char *json_str) return ENOMEM; } pagination_data = json_object_get(result, "pagination"); - json_decref(result); page_count = get_json_integer(pagination_data, "total_pages", true); + json_decref(result); return page_count; } From d75f9324b08f47856be268feec8e0c7794a59bd7 Mon Sep 17 00:00:00 2001 From: Matthias Meidinger Date: Mon, 22 Jun 2026 11:35:57 +0000 Subject: [PATCH 13/13] oidc-child: free uri before reassignment --- src/oidc_child/oidc_child_id.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/oidc_child/oidc_child_id.c b/src/oidc_child/oidc_child_id.c index f9f78c34e9e..b2bac59ee59 100644 --- a/src/oidc_child/oidc_child_id.c +++ b/src/oidc_child/oidc_child_id.c @@ -572,6 +572,7 @@ errno_t authentik_lookup(TALLOC_CTX *mem_ctx, enum oidc_cmd oidc_cmd, switch (oidc_cmd) { case GET_USER_GROUPS: + free(uri); uri = talloc_asprintf(rest_ctx, "%s/groups/?include_users=false&members_by_pk=%s&page=1&page_size=100", base_url, obj_id);