-
Notifications
You must be signed in to change notification settings - Fork 292
Feat: Add authentik idp #8619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feat: Add authentik idp #8619
Changes from all commits
91cf935
f5fd166
bf4443d
eeff250
d10209c
3fff663
93a3c87
f1eeb1b
dfa4c00
a4374c8
71fce4c
e61173a
d75f932
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -465,7 +465,180 @@ | |
| 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, | ||
|
alexey-tikhonov marked this conversation as resolved.
Dismissed
sumit-bose marked this conversation as resolved.
|
||
| char *base_url, | ||
| char *input, enum search_str_type input_type, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(from sssd-bot' review)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment in line 471
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, yes, basically bye, |
||
| 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; | ||
| int page_count; | ||
| 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"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, please read Additionally, since you need the user's bye, |
||
| ret = EINVAL; | ||
| goto done; | ||
| } | ||
|
|
||
| switch (oidc_cmd) { | ||
| case GET_USER_GROUPS: | ||
| free(uri); | ||
| uri = talloc_asprintf(rest_ctx, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need either to use tmp context and free it at function exit, or free 'uri' explicitly here before assigning new memory allocation.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before I do this: This part is functionally identical to That would apply there as well, correct?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe so
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated this only for the Authentik for now - in d75f932 |
||
| "%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=100", | ||
| 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) { | ||
| 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); | ||
| 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, | ||
| bool libcurl_debug, const char *ca_db, | ||
|
|
@@ -521,11 +694,16 @@ | |
| 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) { | ||
| 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) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Opus' review was wrong and this fallback is not for
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Authentik API is annoyingly inconsistent with this field.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But where do you parse 'pk' for user objects?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 'pk' for both, users and groups, is used in the lookup here: https://github.com/SSSD/sssd/pull/8619/changes#diff-b444f2dc72997d5070d3f385fe295aeb12ffb0e67b34a755f7c5e5c14fcd3a91R575-R579 It significantly reduces the response size of Authentik for lookups.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code issues a search for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, for group objects - in But for user objects - in -- no fallback to 'pk' |
||
| 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could use single |
||
| 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; | ||
| } | ||
|
|
||
|
|
@@ -532,13 +543,16 @@ 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]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, what is the purpose of this change? I'm asking because it is changing the behavior of this function. If bye, |
||
| 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; | ||
|
|
||
|
alexey-tikhonov marked this conversation as resolved.
|
||
| for (c = 0; id_attr_list[c] != NULL; c++) { | ||
| id_object = json_object_get(userinfo, id_attr_list[c]); | ||
|
|
@@ -600,6 +614,26 @@ 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); | ||
| json_decref(result); | ||
| return page_count; | ||
|
alexey-tikhonov marked this conversation as resolved.
|
||
| } | ||
|
|
||
| const char *get_str_attr_from_json_array_string(TALLOC_CTX *mem_ctx, | ||
| const char *json_str, | ||
| const char *attr_name) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"); | ||
| } | ||
|
Maddosaurus marked this conversation as resolved.
Maddosaurus marked this conversation as resolved.
|
||
| 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"); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will go out of control very soon.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand your point. Given the time this PR has been open, and the fact that I'm not very familiar with the codebase (yet), I'd be glad if we could not implement the name map in the context of this PR.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure. Maybe we could have a dedicated feature branch.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, we can do that. I want to get this current PR wrapped up soon, as it has been open for 2 months now.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, there should be no changes to the general backend code. Please see my comments about setting bye, |
||
| 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; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
thank you for adding proper JSON support here. I would suggest to move the generation of the two output line to
oidc_child_json.cso that here only a singlefprintf()has to be called.bye,
Sumit