diff --git a/README.md b/README.md index beeb79e..5aa3cac 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ All Keycloak administration modules from `community.general` are provided in thi * `keycloak_realm_keys_metadata_info`: retrieve realm keys metadata. * `keycloak_realm_localization`: manage realm localization texts. * `keycloak_realm_rolemapping`: manage realm role mappings for users and groups. +* `keycloak_realm_users_info`: retrieve users from a realm. * `keycloak_role`: manage realm and client roles. * `keycloak_user`: manage users (create/update/delete). * `keycloak_user_execute_actions_email`: trigger execute-actions emails for users. diff --git a/meta/runtime.yml b/meta/runtime.yml index 2e2fc5d..61ed853 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -29,6 +29,7 @@ action_groups: - keycloak_realm_keys_metadata_info - keycloak_realm_localization - keycloak_realm_rolemapping + - keycloak_realm_users_info - keycloak_role - keycloak_user - keycloak_user_federation diff --git a/molecule/keycloak_modules/verify.yml b/molecule/keycloak_modules/verify.yml index ca8c7e9..f4075eb 100644 --- a/molecule/keycloak_modules/verify.yml +++ b/molecule/keycloak_modules/verify.yml @@ -52,6 +52,7 @@ - keycloak_realm_keys_metadata_info - keycloak_realm_localization - keycloak_realm_rolemapping + - keycloak_realm_users_info - keycloak_role - keycloak_user - keycloak_user_execute_actions_email @@ -188,6 +189,18 @@ middleware_automation.keycloak.keycloak_component_info: realm: "{{ target_realm }}" + - name: keycloak_realm_users_info — list users in realm + middleware_automation.keycloak.keycloak_realm_users_info: + realm: "{{ target_realm }}" + register: realm_users_info_result + + - name: Assert keycloak_realm_users_info returns the molecule user + ansible.builtin.assert: + that: + - realm_users_info_result is not changed + - realm_users_info_result.users | length > 0 + - realm_users_info_result.users | selectattr('username', 'equalto', user) | list | length == 1 + - name: keycloak_realm — create ephemeral realm middleware_automation.keycloak.keycloak_realm: id: "{{ ephemeral_realm }}" diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 4ecfce8..c8afd3f 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -1108,6 +1108,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. diff --git a/plugins/modules/keycloak_realm_users_info.py b/plugins/modules/keycloak_realm_users_info.py new file mode 100644 index 0000000..db081a3 --- /dev/null +++ b/plugins/modules/keycloak_realm_users_info.py @@ -0,0 +1,123 @@ +#!/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 (absolute_import, division, print_function) +from __future__ import annotations +__metaclass__ = type + +DOCUMENTATION = r""" +module: keycloak_realm_users_info + +short_description: Retrieve users from a Keycloak realm using the Keycloak API + +# Originally added in community.general 13.1.0 +version_added: "3.1.0" + +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/latest/rest-api/index.html). + +options: + realm: + type: str + description: + - The Keycloak realm from which users should be retrieved. + default: 'master' + +extends_documentation_fragment: + - middleware_automation.keycloak.keycloak + - middleware_automation.keycloak.actiongroup_keycloak + - middleware_automation.keycloak.attributes + - middleware_automation.keycloak.attributes.info_module + +author: + - Felix Grzelka (@felix-grzelka) +""" + +EXAMPLES = r""" +- name: List all users in the "MyCustomRealm" realm using username/password authentication + middleware_automation.keycloak.keycloak_realm_users_info: + realm: MyCustomRealm + auth_client_id: admin-cli + auth_keycloak_url: https://auth.example.com + auth_realm: master + auth_username: USERNAME + auth_password: PASSWORD + delegate_to: localhost + +- name: List all users in the "MyCustomRealm" realm using a token + middleware_automation.keycloak.keycloak_realm_users_info: + realm: MyCustomRealm + auth_client_id: admin-cli + auth_keycloak_url: https://auth.example.com + token: TOKEN + delegate_to: localhost +""" + +RETURN = r""" +users: + description: List of users in the specified realm. + returned: always + 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" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import ( + KeycloakAPI, + KeycloakError, + get_token, + keycloak_argument_spec, +) + + +def main(): + 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") + + result["users"] = kc.get_realm_users(realm=realm) + module.exit_json(**result) + + +if __name__ == "__main__": + main()