From ad73928dd1889a767866f12b58eea82bf640ca04 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Tue, 26 May 2026 12:55:58 +0000 Subject: [PATCH 01/21] init keycloak user info --- plugins/module_utils/_keycloak.py | 48 ++-- plugins/modules/keycloak_realm_user_info.py | 279 ++++++++++++++++++++ 2 files changed, 310 insertions(+), 17 deletions(-) create mode 100644 plugins/modules/keycloak_realm_user_info.py diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index a452734e84..77ea275594 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1085,6 +1085,35 @@ class KeycloakAPI: except Exception as e: self.fail_request(e, msg=f"Could not fetch effective rolemappings for user {uid}, realm {realm}: {e}") + def get_realm_users(self, realm: str = "master") -> dict[str, t.Any] | None: + """Obtain list of users from the realm + + :param realm: realm id + :return: list of user representations + """ + return self._get_users(username=None, realm=realm) + + def _get_users(self, username, realm: str = "master"): + """Get the user with the given username or all users if username=None""" + users_url = URL_USERS.format(url=self.baseurl, realm=realm) + if username is not None: + users_url += f"?username={quote(username, safe='')}&exact=true" + try: + users = self._request_and_deserialize(users_url, method="GET") + if username is None: + return users + + for user in users: + if user["username"] == username: + return user + return None + except ValueError as e: + self.module.fail_json( + msg=f"API returned incorrect JSON when trying to obtain the user for realm {realm} and username {username}: {e}" + ) + except Exception as e: + self.fail_request(e, msg=f"Could not obtain the user for realm {realm} and username {username}: {e}") + def get_user_by_username(self, username, realm: str = "master"): """Fetch a keycloak user within a realm based on its username. @@ -1092,23 +1121,8 @@ class KeycloakAPI: :param username: Username of the user to fetch. :param realm: Realm in which the user resides; default 'master' """ - users_url = URL_USERS.format(url=self.baseurl, realm=realm) - users_url += f"?username={quote(username, safe='')}&exact=true" - try: - userrep = None - users = self._request_and_deserialize(users_url, method="GET") - for user in users: - if user["username"] == username: - userrep = user - break - return userrep - - except ValueError as e: - self.module.fail_json( - msg=f"API returned incorrect JSON when trying to obtain the user for realm {realm} and username {username}: {e}" - ) - except Exception as e: - self.fail_request(e, msg=f"Could not obtain the user for realm {realm} and username {username}: {e}") + self._get_users(username=username, realm=realm) + def get_service_account_user_by_client_id(self, client_id, realm: str = "master"): """Fetch a keycloak service account user within a realm based on its client_id. diff --git a/plugins/modules/keycloak_realm_user_info.py b/plugins/modules/keycloak_realm_user_info.py new file mode 100644 index 0000000000..5a7edb2357 --- /dev/null +++ b/plugins/modules/keycloak_realm_user_info.py @@ -0,0 +1,279 @@ +#!/usr/bin/python + +# Copyright (c) Ansible project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import annotations + +DOCUMENTATION = r""" +module: keycloak_realm_rolemapping + +short_description: Allows administration of Keycloak realm role mappings into groups with the Keycloak API + +version_added: 8.2.0 + +description: + - This module allows you to add, remove or modify Keycloak realm role mappings into groups with the Keycloak REST API. It + requires access to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite + access rights. In a default Keycloak installation, admin-cli and an admin user would work, as would a separate client + definition with the scope tailored to your needs and a user having the expected roles. + - The names of module options are snake_cased versions of the camelCase ones found in the Keycloak API and its documentation + at U(https://www.keycloak.org/docs-api/18.0/rest-api/index.html). + - Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and are returned that way + by this module. You may pass single values for attributes when calling the module, and this is translated into a list + suitable for the API. + - When updating a group_rolemapping, where possible provide the role ID to the module. This removes a lookup to the API + to translate the name into the role ID. +attributes: + check_mode: + support: full + diff_mode: + support: full + action_group: + version_added: 10.2.0 + +options: + state: + description: + - State of the realm_rolemapping. + - On C(present), the realm_rolemapping is created if it does not yet exist, or updated with the parameters you provide. + - On C(absent), the realm_rolemapping is removed if it exists. + default: 'present' + type: str + choices: + - present + - absent + + realm: + type: str + description: + - They Keycloak realm under which this role_representation resides. + default: 'master' + + group_name: + type: str + description: + - Name of the group to be mapped. + - This parameter is required (can be replaced by gid for less API call). + parents: + type: list + description: + - List of parent groups for the group to handle sorted top to bottom. + - Set this if your group is a subgroup and you do not provide the GID in O(gid). + elements: dict + suboptions: + id: + type: str + description: + - Identify parent by ID. + - Needs less API calls than using O(parents[].name). + - A deep parent chain can be started at any point when first given parent is given as ID. + - Note that in principle both ID and name can be specified at the same time but current implementation only always + use just one of them, with ID being preferred. + name: + type: str + description: + - Identify parent by name. + - Needs more internal API calls than using O(parents[].id) to map names to ID's under the hood. + - When giving a parent chain with only names it must be complete up to the top. + - Note that in principle both ID and name can be specified at the same time but current implementation only always + use just one of them, with ID being preferred. + gid: + type: str + description: + - ID of the group to be mapped. + - This parameter is not required for updating or deleting the rolemapping but providing it reduces the number of API + calls required. + roles: + description: + - Roles to be mapped to the group. + type: list + elements: dict + suboptions: + name: + type: str + description: + - Name of the role_representation. + - This parameter is required only when creating or updating the role_representation. + id: + type: str + description: + - The unique identifier for this role_representation. + - This parameter is not required for updating or deleting a role_representation but providing it reduces the number + of API calls required. +extends_documentation_fragment: + - community.general._keycloak + - community.general._keycloak.actiongroup_keycloak + - community.general._attributes + +author: + - Gaëtan Daubresse (@Gaetan2907) + - Marius Huysamen (@mhuysamen) + - Alexander Groß (@agross) +""" + +EXAMPLES = r""" +- name: Map a client role to a group, authentication with credentials + community.general.keycloak_realm_rolemapping: + realm: MyCustomRealm + auth_client_id: admin-cli + auth_keycloak_url: https://auth.example.com/auth + auth_realm: master + auth_username: USERNAME + auth_password: PASSWORD + state: present + group_name: group1 + roles: + - name: role_name1 + id: role_id1 + - name: role_name2 + id: role_id2 + delegate_to: localhost + +- name: Map a client role to a group, authentication with token + community.general.keycloak_realm_rolemapping: + realm: MyCustomRealm + auth_client_id: admin-cli + auth_keycloak_url: https://auth.example.com/auth + token: TOKEN + state: present + group_name: group1 + roles: + - name: role_name1 + id: role_id1 + - name: role_name2 + id: role_id2 + delegate_to: localhost + +- name: Map a client role to a subgroup, authentication with token + community.general.keycloak_realm_rolemapping: + realm: MyCustomRealm + auth_client_id: admin-cli + auth_keycloak_url: https://auth.example.com/auth + token: TOKEN + state: present + group_name: subgroup1 + parents: + - name: parent-group + roles: + - name: role_name1 + id: role_id1 + - name: role_name2 + id: role_id2 + delegate_to: localhost + +- name: Unmap realm role from a group + community.general.keycloak_realm_rolemapping: + realm: MyCustomRealm + auth_client_id: admin-cli + auth_keycloak_url: https://auth.example.com/auth + auth_realm: master + auth_username: USERNAME + auth_password: PASSWORD + state: absent + group_name: group1 + roles: + - name: role_name1 + id: role_id1 + - name: role_name2 + id: role_id2 + delegate_to: localhost +""" + +RETURN = r""" +msg: + description: Message as to what action was taken. + returned: always + type: str + sample: "Role role1 assigned to group group1." + +proposed: + description: Representation of proposed client role mapping. + returned: always + type: dict + sample: {"clientId": "test"} + +existing: + description: + - Representation of existing client role mapping. + - The sample is truncated. + returned: always + type: dict + sample: + { + "adminUrl": "http://www.example.com/admin_url", + "attributes": { + "request.object.signature.alg": "RS256" + } + } + +end_state: + description: + - Representation of client role mapping after module execution. + - The sample is truncated. + returned: on success + type: dict + sample: + { + "adminUrl": "http://www.example.com/admin_url", + "attributes": { + "request.object.signature.alg": "RS256" + } + } +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.general.plugins.module_utils._keycloak import ( + KeycloakAPI, + KeycloakError, + get_token, + keycloak_argument_spec, +) + + +def main(): + """ + Module execution + + :return: + """ + argument_spec = keycloak_argument_spec() + meta_args = dict( + realm=dict(default="master"), + ) + + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=( + [["token", "auth_realm", "auth_username", "auth_password", "auth_client_id", "auth_client_secret"]] + ), + required_together=([["auth_username", "auth_password"]]), + required_by={"refresh_token": "auth_realm"}, + ) + + result = dict(changed=False, msg="", users="") + + # Obtain access token, initialize API + try: + connection_header = get_token(module.params) + except KeycloakError as e: + module.fail_json(msg=str(e)) + + kc = KeycloakAPI(module, connection_header) + + realm = module.params.get("realm") + + users = kc.get_realm_users(realm=realm) + + result["users"] = users + result["msg"] = f"Got users in realm {realm}" + module.exit_json(**result) + + +if __name__ == "__main__": + main() From e569a921f3cec9d966515baf2447e1cc6142115a Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Tue, 26 May 2026 13:01:09 +0000 Subject: [PATCH 02/21] fix docs --- plugins/modules/keycloak_realm_user_info.py | 197 +++----------------- 1 file changed, 31 insertions(+), 166 deletions(-) diff --git a/plugins/modules/keycloak_realm_user_info.py b/plugins/modules/keycloak_realm_user_info.py index 5a7edb2357..1ba47ef9f2 100644 --- a/plugins/modules/keycloak_realm_user_info.py +++ b/plugins/modules/keycloak_realm_user_info.py @@ -7,220 +7,85 @@ from __future__ import annotations DOCUMENTATION = r""" -module: keycloak_realm_rolemapping +module: keycloak_realm_users -short_description: Allows administration of Keycloak realm role mappings into groups with the Keycloak API +short_description: Retrieve users from a Keycloak realm using the Keycloak API version_added: 8.2.0 description: - - This module allows you to add, remove or modify Keycloak realm role mappings into groups with the Keycloak REST API. It - requires access to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite - access rights. In a default Keycloak installation, admin-cli and an admin user would work, as would a separate client - definition with the scope tailored to your needs and a user having the expected roles. - - The names of module options are snake_cased versions of the camelCase ones found in the Keycloak API and its documentation - at U(https://www.keycloak.org/docs-api/18.0/rest-api/index.html). - - Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and are returned that way - by this module. You may pass single values for attributes when calling the module, and this is translated into a list - suitable for the API. - - When updating a group_rolemapping, where possible provide the role ID to the module. This removes a lookup to the API - to translate the name into the role ID. + - This module retrieves all users from a specified Keycloak realm using the Keycloak REST API. + - Access to the REST API is performed via OpenID Connect. The user and client used must have the necessary permissions. + - Authentication can be performed either with username/password or with a token. + - The names of module options are snake_case versions of the camelCase ones found in the Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/18.0/rest-api/index.html). + attributes: check_mode: support: full diff_mode: - support: full - action_group: - version_added: 10.2.0 + support: none options: - state: - description: - - State of the realm_rolemapping. - - On C(present), the realm_rolemapping is created if it does not yet exist, or updated with the parameters you provide. - - On C(absent), the realm_rolemapping is removed if it exists. - default: 'present' - type: str - choices: - - present - - absent - realm: type: str description: - - They Keycloak realm under which this role_representation resides. + - The Keycloak realm from which users should be retrieved. default: 'master' - group_name: - type: str - description: - - Name of the group to be mapped. - - This parameter is required (can be replaced by gid for less API call). - parents: - type: list - description: - - List of parent groups for the group to handle sorted top to bottom. - - Set this if your group is a subgroup and you do not provide the GID in O(gid). - elements: dict - suboptions: - id: - type: str - description: - - Identify parent by ID. - - Needs less API calls than using O(parents[].name). - - A deep parent chain can be started at any point when first given parent is given as ID. - - Note that in principle both ID and name can be specified at the same time but current implementation only always - use just one of them, with ID being preferred. - name: - type: str - description: - - Identify parent by name. - - Needs more internal API calls than using O(parents[].id) to map names to ID's under the hood. - - When giving a parent chain with only names it must be complete up to the top. - - Note that in principle both ID and name can be specified at the same time but current implementation only always - use just one of them, with ID being preferred. - gid: - type: str - description: - - ID of the group to be mapped. - - This parameter is not required for updating or deleting the rolemapping but providing it reduces the number of API - calls required. - roles: - description: - - Roles to be mapped to the group. - type: list - elements: dict - suboptions: - name: - type: str - description: - - Name of the role_representation. - - This parameter is required only when creating or updating the role_representation. - id: - type: str - description: - - The unique identifier for this role_representation. - - This parameter is not required for updating or deleting a role_representation but providing it reduces the number - of API calls required. extends_documentation_fragment: - community.general._keycloak - community.general._keycloak.actiongroup_keycloak - community.general._attributes author: - - Gaëtan Daubresse (@Gaetan2907) - - Marius Huysamen (@mhuysamen) - - Alexander Groß (@agross) + - Your Name (@yourgithub) """ EXAMPLES = r""" -- name: Map a client role to a group, authentication with credentials - community.general.keycloak_realm_rolemapping: +- name: List all users in the "MyCustomRealm" realm using username/password authentication + community.general.keycloak_realm_users: realm: MyCustomRealm auth_client_id: admin-cli auth_keycloak_url: https://auth.example.com/auth auth_realm: master auth_username: USERNAME auth_password: PASSWORD - state: present - group_name: group1 - roles: - - name: role_name1 - id: role_id1 - - name: role_name2 - id: role_id2 delegate_to: localhost -- name: Map a client role to a group, authentication with token - community.general.keycloak_realm_rolemapping: +- name: List all users in the "MyCustomRealm" realm using a token + community.general.keycloak_realm_users: realm: MyCustomRealm auth_client_id: admin-cli auth_keycloak_url: https://auth.example.com/auth token: TOKEN - state: present - group_name: group1 - roles: - - name: role_name1 - id: role_id1 - - name: role_name2 - id: role_id2 - delegate_to: localhost - -- name: Map a client role to a subgroup, authentication with token - community.general.keycloak_realm_rolemapping: - realm: MyCustomRealm - auth_client_id: admin-cli - auth_keycloak_url: https://auth.example.com/auth - token: TOKEN - state: present - group_name: subgroup1 - parents: - - name: parent-group - roles: - - name: role_name1 - id: role_id1 - - name: role_name2 - id: role_id2 - delegate_to: localhost - -- name: Unmap realm role from a group - community.general.keycloak_realm_rolemapping: - realm: MyCustomRealm - auth_client_id: admin-cli - auth_keycloak_url: https://auth.example.com/auth - auth_realm: master - auth_username: USERNAME - auth_password: PASSWORD - state: absent - group_name: group1 - roles: - - name: role_name1 - id: role_id1 - - name: role_name2 - id: role_id2 delegate_to: localhost """ RETURN = r""" msg: - description: Message as to what action was taken. + description: Status message about the action performed. returned: always type: str - sample: "Role role1 assigned to group group1." + sample: "Got users in realm MyCustomRealm" -proposed: - description: Representation of proposed client role mapping. +users: + description: List of users in the specified realm. returned: always - type: dict - sample: {"clientId": "test"} + type: list + elements: dict + sample: + - id: "1234-5678-90" + username: "user1" + email: "user1@example.com" + - id: "2345-6789-01" + username: "user2" + email: "user2@example.com" -existing: - description: - - Representation of existing client role mapping. - - The sample is truncated. +changed: + description: Indicates if any changes were made (always False, as this is a read-only operation). returned: always - type: dict - sample: - { - "adminUrl": "http://www.example.com/admin_url", - "attributes": { - "request.object.signature.alg": "RS256" - } - } - -end_state: - description: - - Representation of client role mapping after module execution. - - The sample is truncated. - returned: on success - type: dict - sample: - { - "adminUrl": "http://www.example.com/admin_url", - "attributes": { - "request.object.signature.alg": "RS256" - } - } + type: bool + sample: false """ from ansible.module_utils.basic import AnsibleModule From 30e5e3ddce9e89408c4a5e54d34526718ec9d830 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Tue, 26 May 2026 13:01:31 +0000 Subject: [PATCH 03/21] rename --- .../{keycloak_realm_user_info.py => keycloak_realm_users.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/modules/{keycloak_realm_user_info.py => keycloak_realm_users.py} (100%) diff --git a/plugins/modules/keycloak_realm_user_info.py b/plugins/modules/keycloak_realm_users.py similarity index 100% rename from plugins/modules/keycloak_realm_user_info.py rename to plugins/modules/keycloak_realm_users.py From 04cdd7d55a41e711deabe193d996b1b451c2aaf1 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Tue, 26 May 2026 13:09:06 +0000 Subject: [PATCH 04/21] nox -Re formatters --- plugins/module_utils/_keycloak.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index 77ea275594..4b2b371c68 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1122,7 +1122,6 @@ class KeycloakAPI: :param realm: Realm in which the user resides; default 'master' """ self._get_users(username=username, realm=realm) - def get_service_account_user_by_client_id(self, client_id, realm: str = "master"): """Fetch a keycloak service account user within a realm based on its client_id. From ea826a48d30f0d8c1cfaf81203d0ddc91a490dcb Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 27 May 2026 07:25:09 +0000 Subject: [PATCH 05/21] botmeta --- .github/BOTMETA.yml | 2 ++ plugins/modules/keycloak_realm_users.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 251bb71bff..565ab4f1f4 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -887,6 +887,8 @@ files: maintainers: danekja $modules/keycloak_realm_rolemapping.py: maintainers: agross mhuysamen Gaetan2907 + $modules/keycloak_realm_users.py: + maintainers: felix-grzelka $modules/keycloak_role.py: maintainers: laurpaum $modules/keycloak_user.py: diff --git a/plugins/modules/keycloak_realm_users.py b/plugins/modules/keycloak_realm_users.py index 1ba47ef9f2..0748282515 100644 --- a/plugins/modules/keycloak_realm_users.py +++ b/plugins/modules/keycloak_realm_users.py @@ -38,7 +38,7 @@ extends_documentation_fragment: - community.general._attributes author: - - Your Name (@yourgithub) + - Felix Grzelka (@felix-grzelka) """ EXAMPLES = r""" From 39ee02acbc497fb1bb511d5d0b6c07567fc4823e Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 27 May 2026 07:26:15 +0000 Subject: [PATCH 06/21] update runtime.yml --- meta/runtime.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index bc1814aa3e..9072fff6e0 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -44,6 +44,7 @@ action_groups: - keycloak_realm_keys_metadata_info - keycloak_realm_localization - keycloak_realm_rolemapping + - keycloak_realm_users - keycloak_role - keycloak_user - keycloak_user_federation From 1a4757a7ab7838ac1fcdd5d60c89b93eca473192 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Thu, 28 May 2026 08:41:23 +0000 Subject: [PATCH 07/21] fix line too long --- plugins/modules/keycloak_realm_users.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/keycloak_realm_users.py b/plugins/modules/keycloak_realm_users.py index 0748282515..7f26e413e8 100644 --- a/plugins/modules/keycloak_realm_users.py +++ b/plugins/modules/keycloak_realm_users.py @@ -17,7 +17,8 @@ description: - This module retrieves all users from a specified Keycloak realm using the Keycloak REST API. - Access to the REST API is performed via OpenID Connect. The user and client used must have the necessary permissions. - Authentication can be performed either with username/password or with a token. - - The names of module options are snake_case versions of the camelCase ones found in the Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/18.0/rest-api/index.html). + - The names of module options are snake_case versions of the camelCase ones found in the Keycloak API + and its documentation at U(https://www.keycloak.org/docs-api/18.0/rest-api/index.html). attributes: check_mode: From 39bc413623a8fff90bc4558edaba93f135cf6d5c Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Thu, 28 May 2026 09:39:48 +0000 Subject: [PATCH 08/21] fix stupid --- plugins/module_utils/_keycloak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index 4b2b371c68..0cbef872a4 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1121,7 +1121,7 @@ class KeycloakAPI: :param username: Username of the user to fetch. :param realm: Realm in which the user resides; default 'master' """ - self._get_users(username=username, realm=realm) + return self._get_users(username=username, realm=realm) def get_service_account_user_by_client_id(self, client_id, realm: str = "master"): """Fetch a keycloak service account user within a realm based on its client_id. From 0ef0adab7ad7a6ec7a10babf185df6dad9eaa5eb Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Fri, 29 May 2026 09:29:42 +0000 Subject: [PATCH 09/21] mv keycloak_realm_users.py keycloak_realm_users_info.py --- .github/BOTMETA.yml | 2 +- meta/runtime.yml | 2 +- ...keycloak_realm_users.py => keycloak_realm_users_info.py} | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename plugins/modules/{keycloak_realm_users.py => keycloak_realm_users_info.py} (96%) diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 565ab4f1f4..19025f6f45 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -887,7 +887,7 @@ files: maintainers: danekja $modules/keycloak_realm_rolemapping.py: maintainers: agross mhuysamen Gaetan2907 - $modules/keycloak_realm_users.py: + $modules/keycloak_realm_users_info.py: maintainers: felix-grzelka $modules/keycloak_role.py: maintainers: laurpaum diff --git a/meta/runtime.yml b/meta/runtime.yml index 9072fff6e0..6bc6896c82 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -44,7 +44,7 @@ action_groups: - keycloak_realm_keys_metadata_info - keycloak_realm_localization - keycloak_realm_rolemapping - - keycloak_realm_users + - keycloak_realm_users_info - keycloak_role - keycloak_user - keycloak_user_federation diff --git a/plugins/modules/keycloak_realm_users.py b/plugins/modules/keycloak_realm_users_info.py similarity index 96% rename from plugins/modules/keycloak_realm_users.py rename to plugins/modules/keycloak_realm_users_info.py index 7f26e413e8..0600229f83 100644 --- a/plugins/modules/keycloak_realm_users.py +++ b/plugins/modules/keycloak_realm_users_info.py @@ -7,7 +7,7 @@ from __future__ import annotations DOCUMENTATION = r""" -module: keycloak_realm_users +module: keycloak_realm_users_info short_description: Retrieve users from a Keycloak realm using the Keycloak API @@ -44,7 +44,7 @@ author: EXAMPLES = r""" - name: List all users in the "MyCustomRealm" realm using username/password authentication - community.general.keycloak_realm_users: + community.general.keycloak_realm_users_info: realm: MyCustomRealm auth_client_id: admin-cli auth_keycloak_url: https://auth.example.com/auth @@ -54,7 +54,7 @@ EXAMPLES = r""" delegate_to: localhost - name: List all users in the "MyCustomRealm" realm using a token - community.general.keycloak_realm_users: + community.general.keycloak_realm_users_info: realm: MyCustomRealm auth_client_id: admin-cli auth_keycloak_url: https://auth.example.com/auth From 3262224b62453451ef6e8d3eda3760dd8012455a Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Fri, 29 May 2026 09:32:22 +0000 Subject: [PATCH 10/21] add integration test --- .../keycloak_realm_users_info/README.md | 21 ++++ .../targets/keycloak_realm_users_info/aliases | 4 + .../keycloak_realm_users_info/tasks/main.yml | 101 ++++++++++++++++++ .../keycloak_realm_users_info/vars/main.yml | 46 ++++++++ 4 files changed, 172 insertions(+) create mode 100644 tests/integration/targets/keycloak_realm_users_info/README.md create mode 100644 tests/integration/targets/keycloak_realm_users_info/aliases create mode 100644 tests/integration/targets/keycloak_realm_users_info/tasks/main.yml create mode 100644 tests/integration/targets/keycloak_realm_users_info/vars/main.yml diff --git a/tests/integration/targets/keycloak_realm_users_info/README.md b/tests/integration/targets/keycloak_realm_users_info/README.md new file mode 100644 index 0000000000..39ffce670f --- /dev/null +++ b/tests/integration/targets/keycloak_realm_users_info/README.md @@ -0,0 +1,21 @@ + +# Running keycloak_realm_users_info module integration test + +To run Keycloak user module's integration test, start a keycloak server using Docker or Podman: + + podman|docker run -d --rm --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Source Ansible env-setup from ansible github repository + +Run integration tests: + + ansible-test integration keycloak_realm_users_info --allow-unsupported + +Cleanup: + + podman|docker stop mykeycloak + diff --git a/tests/integration/targets/keycloak_realm_users_info/aliases b/tests/integration/targets/keycloak_realm_users_info/aliases new file mode 100644 index 0000000000..0abc6a4671 --- /dev/null +++ b/tests/integration/targets/keycloak_realm_users_info/aliases @@ -0,0 +1,4 @@ +# Copyright (c) 2023, INSPQ Philippe Gauthier (@elfelip) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +unsupported diff --git a/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml new file mode 100644 index 0000000000..f0378da458 --- /dev/null +++ b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml @@ -0,0 +1,101 @@ +# Copyright (c) 2022, Dušan Marković (@bratwurzt) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Delete realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: absent + +- name: Create realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: present + +- name: Create new groups + community.general.keycloak_group: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ item.name }}" + state: present + with_items: "{{ keycloak_user_groups }}" + +- name: Create user + community.general.keycloak_user: + auth_keycloak_url: "{{ url }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + auth_realm: "{{ admin_realm }}" + username: "{{ keycloak_username }}" + realm: "{{ realm }}" + first_name: Ceciestes + last_name: Untestes + email: ceciestuntestes@test.com + groups: "{{ keycloak_user_groups }}" + state: present + register: create_result + +- name: Assert user is created + assert: + that: + - create_result.changed + - create_result.end_state.username == 'test' + - create_result.end_state.groups | length == 2 + +- name: Create another user + community.general.keycloak_user: + auth_keycloak_url: "{{ url }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + auth_realm: "{{ admin_realm }}" + username: "testuser2" + realm: "{{ realm }}" + first_name: Ceciestes2 + last_name: Untestes2 + email: ceciestuntestes2@test.com + state: present + register: create_result2 + +- name: Get user info + community.general.keycloak_realm_users_info: + auth_keycloak_url: "{{ url }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + auth_realm: "{{ admin_realm }}" + realm: "{{ realm }}" + register: user_infos + +- name: debug + debug: + var: user_infos + +- name: add missing group info for comparison + set_fact: + user_info_with_groups: "{{ [user_infos.users[0] | combine({'groups': ['test', 'test2']}), user_infos.users[1]] | combine({'groups': []})}}" + +- name: debug + debug: + var: user_info_with_groups + +- name: debug + debug: + msg: "{{ [create_result.end_state, create_result2.end_state] }}" + +- name: Assert user info is returned correctly + assert: + that: + - not user_infos.changed + - " user_info_with_groups == [create_result.end_state, create_result2.end_state]" \ No newline at end of file diff --git a/tests/integration/targets/keycloak_realm_users_info/vars/main.yml b/tests/integration/targets/keycloak_realm_users_info/vars/main.yml new file mode 100644 index 0000000000..9962aba548 --- /dev/null +++ b/tests/integration/targets/keycloak_realm_users_info/vars/main.yml @@ -0,0 +1,46 @@ +--- +# Copyright (c) 2022, Dušan Marković (@bratwurzt) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: myrealm +client_id: myclient +role: myrole +description_1: desc 1 +description_2: desc 2 + +keycloak_username: test +keycloak_service_account_client_id: "{{ client_id }}" +keycloak_user_realm_roles: + - name: offline_access + - name: "{{ role }}" +keycloak_client_role: test +keycloak_user_client_roles: + - client_id: "{{ client_id }}" + roles: + - name: "{{ keycloak_client_role }}" + - client_id: "{{ realm }}-realm" + roles: + - name: view-users + - name: query-users +keycloak_user_attributes: + - name: attr1 + values: + - value1s + state: present + - name: attr2 + values: + - value2s + state: present + - name: attr3 + values: + - value3s + state: present +keycloak_user_groups: + - name: test + state: present + - name: test2 From ab08b5304cd876f54c5d427ffc9facfdbe65d828 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Fri, 29 May 2026 09:35:22 +0000 Subject: [PATCH 11/21] fix integrationtest --- .../targets/keycloak_realm_users_info/tasks/main.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml index f0378da458..94b751536e 100644 --- a/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml +++ b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml @@ -84,15 +84,7 @@ - name: add missing group info for comparison set_fact: - user_info_with_groups: "{{ [user_infos.users[0] | combine({'groups': ['test', 'test2']}), user_infos.users[1]] | combine({'groups': []})}}" - -- name: debug - debug: - var: user_info_with_groups - -- name: debug - debug: - msg: "{{ [create_result.end_state, create_result2.end_state] }}" + user_info_with_groups: "{{ [user_infos.users[0] | combine({'groups': ['test', 'test2']}), user_infos.users[1] | combine({'groups': []}) ] }}" - name: Assert user info is returned correctly assert: From 5b7d05e231ac218106d6cc7e4b572b8ae3da3aad Mon Sep 17 00:00:00 2001 From: felix-grzelka Date: Fri, 29 May 2026 11:35:57 +0200 Subject: [PATCH 12/21] fix version_added Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> --- plugins/modules/keycloak_realm_users_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/keycloak_realm_users_info.py b/plugins/modules/keycloak_realm_users_info.py index 0600229f83..637f462715 100644 --- a/plugins/modules/keycloak_realm_users_info.py +++ b/plugins/modules/keycloak_realm_users_info.py @@ -11,7 +11,7 @@ module: keycloak_realm_users_info short_description: Retrieve users from a Keycloak realm using the Keycloak API -version_added: 8.2.0 +version_added: 13.1.0 description: - This module retrieves all users from a specified Keycloak realm using the Keycloak REST API. From 7621dfaf6c68a05ea26c273d727d4812c416202d Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Fri, 29 May 2026 09:51:49 +0000 Subject: [PATCH 13/21] fix eof --- .../targets/keycloak_realm_users_info/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml index 94b751536e..5c82b78032 100644 --- a/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml +++ b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml @@ -90,4 +90,4 @@ assert: that: - not user_infos.changed - - " user_info_with_groups == [create_result.end_state, create_result2.end_state]" \ No newline at end of file + - " user_info_with_groups == [create_result.end_state, create_result2.end_state]" From cfcae5331c9debd2269c87b8d8c110da1627f321 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:05:53 +0000 Subject: [PATCH 14/21] use other file as template --- tests/integration/targets/keycloak_realm_users_info/aliases | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integration/targets/keycloak_realm_users_info/aliases b/tests/integration/targets/keycloak_realm_users_info/aliases index 0abc6a4671..bd1f024441 100644 --- a/tests/integration/targets/keycloak_realm_users_info/aliases +++ b/tests/integration/targets/keycloak_realm_users_info/aliases @@ -1,4 +1,5 @@ -# Copyright (c) 2023, INSPQ Philippe Gauthier (@elfelip) -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later + unsupported From ac232e29806d8bb82c2d48ba7169f6d86a5b176c Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:30:30 +0000 Subject: [PATCH 15/21] use keycloak_client_rolescope as basis --- .../keycloak_realm_users_info/tasks/main.yml | 17 +++++++--- .../keycloak_realm_users_info/vars/main.yml | 34 ++----------------- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml index 5c82b78032..65d4849afc 100644 --- a/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml +++ b/tests/integration/targets/keycloak_realm_users_info/tasks/main.yml @@ -1,14 +1,23 @@ -# Copyright (c) 2022, Dušan Marković (@bratwurzt) -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later +- name: Wait for Keycloak + uri: + url: "{{ url }}/admin/" + status_code: 200 + validate_certs: false + register: result + until: result.status == 200 + retries: 10 + delay: 10 -- name: Delete realm +- name: Delete realm if exists community.general.keycloak_realm: auth_keycloak_url: "{{ url }}" auth_realm: "{{ admin_realm }}" auth_username: "{{ admin_user }}" auth_password: "{{ admin_password }}" - id: "{{ realm }}" realm: "{{ realm }}" state: absent diff --git a/tests/integration/targets/keycloak_realm_users_info/vars/main.yml b/tests/integration/targets/keycloak_realm_users_info/vars/main.yml index 9962aba548..36c8b11e96 100644 --- a/tests/integration/targets/keycloak_realm_users_info/vars/main.yml +++ b/tests/integration/targets/keycloak_realm_users_info/vars/main.yml @@ -1,6 +1,6 @@ --- -# Copyright (c) 2022, Dušan Marković (@bratwurzt) -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later url: http://localhost:8080/auth @@ -8,38 +8,8 @@ admin_realm: master admin_user: admin admin_password: password realm: myrealm -client_id: myclient -role: myrole -description_1: desc 1 -description_2: desc 2 keycloak_username: test -keycloak_service_account_client_id: "{{ client_id }}" -keycloak_user_realm_roles: - - name: offline_access - - name: "{{ role }}" -keycloak_client_role: test -keycloak_user_client_roles: - - client_id: "{{ client_id }}" - roles: - - name: "{{ keycloak_client_role }}" - - client_id: "{{ realm }}-realm" - roles: - - name: view-users - - name: query-users -keycloak_user_attributes: - - name: attr1 - values: - - value1s - state: present - - name: attr2 - values: - - value2s - state: present - - name: attr3 - values: - - value3s - state: present keycloak_user_groups: - name: test state: present From 7d8cfcc720c21a2ffbbf233647611523ba0854d0 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:41:55 +0000 Subject: [PATCH 16/21] refactor functions --- plugins/module_utils/_keycloak.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index 0cbef872a4..f80cabae44 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1085,24 +1085,33 @@ class KeycloakAPI: except Exception as e: self.fail_request(e, msg=f"Could not fetch effective rolemappings for user {uid}, realm {realm}: {e}") - def get_realm_users(self, realm: str = "master") -> dict[str, t.Any] | None: + def get_realm_users(self, realm: str = "master") -> list[dict[str, t.Any]]: """Obtain list of users from the realm :param realm: realm id :return: list of user representations """ - return self._get_users(username=None, realm=realm) + users_url = URL_USERS.format(url=self.baseurl, realm=realm) + try: + return self._request_and_deserialize(users_url, method="GET") + except ValueError as e: + self.module.fail_json( + msg=f"API returned incorrect JSON when trying to obtain the users for realm {realm}: {e}" + ) + except Exception as e: + self.fail_request(e, msg=f"Could not obtain the users for realm {realm}: {e}") - def _get_users(self, username, realm: str = "master"): - """Get the user with the given username or all users if username=None""" + def get_user_by_username(self, username: str, realm: str = "master") -> dict[str, t.Any] | None: + """Fetch a keycloak user within a realm based on its username. + + If the username is not found, None is returned. + :param username: Username of the user to fetch. + :param realm: Realm in which the user resides; default 'master'""" users_url = URL_USERS.format(url=self.baseurl, realm=realm) if username is not None: users_url += f"?username={quote(username, safe='')}&exact=true" try: users = self._request_and_deserialize(users_url, method="GET") - if username is None: - return users - for user in users: if user["username"] == username: return user @@ -1114,14 +1123,6 @@ class KeycloakAPI: except Exception as e: self.fail_request(e, msg=f"Could not obtain the user for realm {realm} and username {username}: {e}") - def get_user_by_username(self, username, realm: str = "master"): - """Fetch a keycloak user within a realm based on its username. - - If the user does not exist, None is returned. - :param username: Username of the user to fetch. - :param realm: Realm in which the user resides; default 'master' - """ - return self._get_users(username=username, realm=realm) def get_service_account_user_by_client_id(self, client_id, realm: str = "master"): """Fetch a keycloak service account user within a realm based on its client_id. From efad5528874963a7940b3c21d4cef6f47d98f448 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:44:09 +0000 Subject: [PATCH 17/21] fix extends_documentation_fragment --- plugins/modules/keycloak_realm_users_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/keycloak_realm_users_info.py b/plugins/modules/keycloak_realm_users_info.py index 637f462715..51a9e12943 100644 --- a/plugins/modules/keycloak_realm_users_info.py +++ b/plugins/modules/keycloak_realm_users_info.py @@ -35,8 +35,8 @@ options: extends_documentation_fragment: - community.general._keycloak - - community.general._keycloak.actiongroup_keycloak - community.general._attributes + - community.general._attributes.info_module author: - Felix Grzelka (@felix-grzelka) From 0cf612e0ce55f85ed2cd4d29fb7e6e71d210a0d2 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:48:45 +0000 Subject: [PATCH 18/21] clean some things up --- plugins/module_utils/_keycloak.py | 3 +-- plugins/modules/keycloak_clientsecret_info.py | 5 ---- plugins/modules/keycloak_realm_users_info.py | 27 ++----------------- 3 files changed, 3 insertions(+), 32 deletions(-) diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index f80cabae44..9108e523f4 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1103,7 +1103,7 @@ class KeycloakAPI: def get_user_by_username(self, username: str, realm: str = "master") -> dict[str, t.Any] | None: """Fetch a keycloak user within a realm based on its username. - + If the username is not found, None is returned. :param username: Username of the user to fetch. :param realm: Realm in which the user resides; default 'master'""" @@ -1123,7 +1123,6 @@ class KeycloakAPI: except Exception as e: self.fail_request(e, msg=f"Could not obtain the user for realm {realm} and username {username}: {e}") - def get_service_account_user_by_client_id(self, client_id, realm: str = "master"): """Fetch a keycloak service account user within a realm based on its client_id. diff --git a/plugins/modules/keycloak_clientsecret_info.py b/plugins/modules/keycloak_clientsecret_info.py index 99e8003923..fb304cefb6 100644 --- a/plugins/modules/keycloak_clientsecret_info.py +++ b/plugins/modules/keycloak_clientsecret_info.py @@ -104,11 +104,6 @@ EXAMPLES = r""" """ RETURN = r""" -msg: - description: Textual description of whether we succeeded or failed. - returned: always - type: str - clientsecret_info: description: Representation of the client secret. returned: on success diff --git a/plugins/modules/keycloak_realm_users_info.py b/plugins/modules/keycloak_realm_users_info.py index 51a9e12943..997e63d24a 100644 --- a/plugins/modules/keycloak_realm_users_info.py +++ b/plugins/modules/keycloak_realm_users_info.py @@ -63,12 +63,6 @@ EXAMPLES = r""" """ RETURN = r""" -msg: - description: Status message about the action performed. - returned: always - type: str - sample: "Got users in realm MyCustomRealm" - users: description: List of users in the specified realm. returned: always @@ -81,12 +75,6 @@ users: - id: "2345-6789-01" username: "user2" email: "user2@example.com" - -changed: - description: Indicates if any changes were made (always False, as this is a read-only operation). - returned: always - type: bool - sample: false """ from ansible.module_utils.basic import AnsibleModule @@ -100,17 +88,9 @@ from ansible_collections.community.general.plugins.module_utils._keycloak import def main(): - """ - Module execution - - :return: - """ argument_spec = keycloak_argument_spec() - meta_args = dict( - realm=dict(default="master"), - ) - argument_spec.update(meta_args) + argument_spec["realm"] = dict(default="master") module = AnsibleModule( argument_spec=argument_spec, @@ -134,10 +114,7 @@ def main(): realm = module.params.get("realm") - users = kc.get_realm_users(realm=realm) - - result["users"] = users - result["msg"] = f"Got users in realm {realm}" + result["users"] = kc.get_realm_users(realm=realm) module.exit_json(**result) From 35c87a4a121f8c5eeae1a4eb9dd5f1c17ac44bfd Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:51:48 +0000 Subject: [PATCH 19/21] improve diff? --- plugins/module_utils/_keycloak.py | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index 9108e523f4..24c33074f1 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1085,22 +1085,6 @@ class KeycloakAPI: except Exception as e: self.fail_request(e, msg=f"Could not fetch effective rolemappings for user {uid}, realm {realm}: {e}") - def get_realm_users(self, realm: str = "master") -> list[dict[str, t.Any]]: - """Obtain list of users from the realm - - :param realm: realm id - :return: list of user representations - """ - users_url = URL_USERS.format(url=self.baseurl, realm=realm) - try: - return self._request_and_deserialize(users_url, method="GET") - except ValueError as e: - self.module.fail_json( - msg=f"API returned incorrect JSON when trying to obtain the users for realm {realm}: {e}" - ) - except Exception as e: - self.fail_request(e, msg=f"Could not obtain the users for realm {realm}: {e}") - def get_user_by_username(self, username: str, realm: str = "master") -> dict[str, t.Any] | None: """Fetch a keycloak user within a realm based on its username. @@ -1123,6 +1107,22 @@ class KeycloakAPI: except Exception as e: self.fail_request(e, msg=f"Could not obtain the user for realm {realm} and username {username}: {e}") + def get_realm_users(self, realm: str = "master") -> list[dict[str, t.Any]]: + """Obtain list of users from the realm + + :param realm: realm id + :return: list of user representations + """ + users_url = URL_USERS.format(url=self.baseurl, realm=realm) + try: + return self._request_and_deserialize(users_url, method="GET") + except ValueError as e: + self.module.fail_json( + msg=f"API returned incorrect JSON when trying to obtain the users for realm {realm}: {e}" + ) + except Exception as e: + self.fail_request(e, msg=f"Could not obtain the users for realm {realm}: {e}") + def get_service_account_user_by_client_id(self, client_id, realm: str = "master"): """Fetch a keycloak service account user within a realm based on its client_id. From b76bf6e271df96ccec8b547cb487d4046ab8e609 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:53:58 +0000 Subject: [PATCH 20/21] docstring --- plugins/module_utils/_keycloak.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index 24c33074f1..20100c1438 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1090,7 +1090,8 @@ class KeycloakAPI: If the username is not found, None is returned. :param username: Username of the user to fetch. - :param realm: Realm in which the user resides; default 'master'""" + :param realm: Realm in which the user resides; default 'master' + """ users_url = URL_USERS.format(url=self.baseurl, realm=realm) if username is not None: users_url += f"?username={quote(username, safe='')}&exact=true" From c7f29612e56245a647c4c8f59b82741aadb96614 Mon Sep 17 00:00:00 2001 From: Felix Grzelka Date: Wed, 3 Jun 2026 09:55:40 +0000 Subject: [PATCH 21/21] revert to old logic --- plugins/module_utils/_keycloak.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/module_utils/_keycloak.py b/plugins/module_utils/_keycloak.py index 20100c1438..5097c1e1e9 100644 --- a/plugins/module_utils/_keycloak.py +++ b/plugins/module_utils/_keycloak.py @@ -1093,8 +1093,7 @@ class KeycloakAPI: :param realm: Realm in which the user resides; default 'master' """ users_url = URL_USERS.format(url=self.baseurl, realm=realm) - if username is not None: - users_url += f"?username={quote(username, safe='')}&exact=true" + users_url += f"?username={quote(username, safe='')}&exact=true" try: users = self._request_and_deserialize(users_url, method="GET") for user in users: