From e99b5086a846714a0918bc1bb71860d27d203ece Mon Sep 17 00:00:00 2001 From: desand01 Date: Thu, 28 Dec 2023 12:11:55 -0500 Subject: [PATCH] Keycloak components info (#7694) * Before test * first test * test * Sanity ok * Fixe readme * Refactor * Sanity * Fixe doc * version added * refactor * Copyright * Sanity * Sanity * Fixe text * Encode providerType * Test for failed ic * Update plugins/modules/keycloak_component_info.py Co-authored-by: Felix Fontein * Update plugins/modules/keycloak_component_info.py Co-authored-by: Felix Fontein * Update plugins/modules/keycloak_component_info.py Co-authored-by: Felix Fontein * Update plugins/modules/keycloak_component_info.py Co-authored-by: Felix Fontein * Update plugins/modules/keycloak_component_info.py Co-authored-by: Felix Fontein * Update plugins/modules/keycloak_component_info.py Co-authored-by: Felix Fontein * Update plugins/modules/keycloak_component_info.py Co-authored-by: Felix Fontein * Delete changelogs/fragments/7694-Keycloak-components-info.yml --------- Co-authored-by: Andre Desrosiers Co-authored-by: Felix Fontein --- .github/BOTMETA.yml | 2 + plugins/modules/keycloak_component_info.py | 169 +++++++++++ .../targets/keycloak_component_info/README.md | 20 ++ .../targets/keycloak_component_info/aliases | 5 + .../keycloak_component_info/tasks/main.yml | 266 ++++++++++++++++++ .../keycloak_component_info/vars/main.yml | 19 ++ 6 files changed, 481 insertions(+) create mode 100644 plugins/modules/keycloak_component_info.py create mode 100644 tests/integration/targets/keycloak_component_info/README.md create mode 100644 tests/integration/targets/keycloak_component_info/aliases create mode 100644 tests/integration/targets/keycloak_component_info/tasks/main.yml create mode 100644 tests/integration/targets/keycloak_component_info/vars/main.yml diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index a360a00cac..0d047b217d 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -754,6 +754,8 @@ files: maintainers: elfelip $modules/keycloak_user_federation.py: maintainers: laurpaum + $modules/keycloak_component_info.py: + maintainers: desand01 $modules/keycloak_user_rolemapping.py: maintainers: bratwurzt $modules/keycloak_realm_rolemapping.py: diff --git a/plugins/modules/keycloak_component_info.py b/plugins/modules/keycloak_component_info.py new file mode 100644 index 0000000000..a788735d98 --- /dev/null +++ b/plugins/modules/keycloak_component_info.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: keycloak_component_info + +short_description: Retrive component info in Keycloak + +version_added: 8.2.0 + +description: + - This module retrive information on component from Keycloak. +options: + realm: + description: + - The name of the realm. + required: true + type: str + name: + description: + - Name of the Component. + type: str + provider_type: + description: + - Provider type of components. + - "Example: + V(org.keycloak.storage.UserStorageProvider), + V(org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy), + V(org.keycloak.keys.KeyProvider), + V(org.keycloak.userprofile.UserProfileProvider), + V(org.keycloak.storage.ldap.mappers.LDAPStorageMapper)." + type: str + parent_id: + description: + - Container ID of the components. + type: str + + +extends_documentation_fragment: + - community.general.keycloak + - community.general.attributes + - community.general.attributes.info_module + +author: + - Andre Desrosiers (@desand01) +''' + +EXAMPLES = ''' + - name: Retrive info of a UserStorageProvider named myldap + community.general.keycloak_component_info: + auth_keycloak_url: http://localhost:8080/auth + auth_sername: admin + auth_password: password + auth_realm: master + realm: myrealm + name: myldap + provider_type: org.keycloak.storage.UserStorageProvider + + - name: Retrive key info component + community.general.keycloak_component_info: + auth_keycloak_url: http://localhost:8080/auth + auth_sername: admin + auth_password: password + auth_realm: master + realm: myrealm + name: rsa-enc-generated + provider_type: org.keycloak.keys.KeyProvider + + - name: Retrive all component from realm master + community.general.keycloak_component_info: + auth_keycloak_url: http://localhost:8080/auth + auth_sername: admin + auth_password: password + auth_realm: master + realm: myrealm + + - name: Retrive all sub components of parent component filter by type + community.general.keycloak_component_info: + auth_keycloak_url: http://localhost:8080/auth + auth_sername: admin + auth_password: password + auth_realm: master + realm: myrealm + parent_id: "075ef2fa-19fc-4a6d-bf4c-249f57365fd2" + provider_type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper" + + +''' + +RETURN = ''' +components: + description: JSON representation of components. + returned: always + type: list + elements: dict +''' + +from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \ + keycloak_argument_spec, get_token, KeycloakError +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six.moves.urllib.parse import quote + + +def main(): + """ + Module execution + + :return: + """ + argument_spec = keycloak_argument_spec() + + meta_args = dict( + name=dict(type='str'), + realm=dict(type='str', required=True), + parent_id=dict(type='str'), + provider_type=dict(type='str'), + ) + + argument_spec.update(meta_args) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = dict(changed=False, components=[]) + + # 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') + parentId = module.params.get('parent_id') + name = module.params.get('name') + providerType = module.params.get('provider_type') + + objRealm = kc.get_realm_by_id(realm) + if not objRealm: + module.fail_json(msg="Failed to retrive realm '{realm}'".format(realm=realm)) + + filters = [] + + if parentId: + filters.append("parent=%s" % (quote(parentId, safe=''))) + else: + filters.append("parent=%s" % (quote(objRealm['id'], safe=''))) + + if name: + filters.append("name=%s" % (quote(name, safe=''))) + if providerType: + filters.append("type=%s" % (quote(providerType, safe=''))) + + result['components'] = kc.get_components(filter="&".join(filters), realm=realm) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/keycloak_component_info/README.md b/tests/integration/targets/keycloak_component_info/README.md new file mode 100644 index 0000000000..cf4f222b03 --- /dev/null +++ b/tests/integration/targets/keycloak_component_info/README.md @@ -0,0 +1,20 @@ + +# Running keycloak_component_info module integration test + +To run Keycloak component info module's integration test, start a keycloak server using Docker: + + docker run -d --rm --name myldap -p 389:389 minkwe/389ds:latest + docker run -d --rm --name mykeycloak --link myldap:ldap.example.com -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Run integration tests: + ansible-test integration -v keycloak_component_info --allow-unsupported --docker fedora35 --docker-network host + +Cleanup: + + docker stop myldap mykeycloak + + diff --git a/tests/integration/targets/keycloak_component_info/aliases b/tests/integration/targets/keycloak_component_info/aliases new file mode 100644 index 0000000000..bd1f024441 --- /dev/null +++ b/tests/integration/targets/keycloak_component_info/aliases @@ -0,0 +1,5 @@ +# 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 diff --git a/tests/integration/targets/keycloak_component_info/tasks/main.yml b/tests/integration/targets/keycloak_component_info/tasks/main.yml new file mode 100644 index 0000000000..c0ca5600fc --- /dev/null +++ b/tests/integration/targets/keycloak_component_info/tasks/main.yml @@ -0,0 +1,266 @@ +--- +# 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: no + register: result + until: result.status == 200 + retries: 10 + delay: 10 + +- 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 }}" + 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: Retrive ldap info when absent + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ federation }}" + provider_type: "org.keycloak.storage.UserStorageProvider" + realm: "{{ realm }}" + register: result + +- name: Assert ldap is missing + assert: + that: + - result is not changed + - result.components | length == 0 + +- name: Create new user federation + community.general.keycloak_user_federation: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ federation }}" + state: present + provider_id: ldap + provider_type: org.keycloak.storage.UserStorageProvider + config: + enabled: true + priority: 0 + fullSyncPeriod: -1 + changedSyncPeriod: -1 + cachePolicy: DEFAULT + batchSizeForSync: 1000 + editMode: READ_ONLY + importEnabled: true + syncRegistrations: false + vendor: other + usernameLDAPAttribute: uid + rdnLDAPAttribute: uid + uuidLDAPAttribute: entryUUID + userObjectClasses: "inetOrgPerson, organizationalPerson" + connectionUrl: "ldap://ldap.example.com" + usersDn: "ou=Users,dc=example,dc=com" + authType: simple + bindDn: cn=directory reader + bindCredential: secret + searchScope: 1 + validatePasswordPolicy: false + trustEmail: false + useTruststoreSpi: "ldapsOnly" + connectionPooling: true + pagination: true + allowKerberosAuthentication: false + useKerberosForPasswordAuthentication: false + debug: false + +- name: Retrive ldap info + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ federation }}" + provider_type: "org.keycloak.storage.UserStorageProvider" + realm: "{{ realm }}" + register: result + +- name: Assert ldap exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].name == federation + +- name: Save ldap id + set_fact: + myLdapId: "{{ result.components[0].id }}" + +- name: Retrive ldap subcomponents info + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + parent_id: "{{ myLdapId }}" + register: result + +- name: Assert components exists + assert: + that: + - result is not changed + - result.components | length > 0 + +- name: Retrive ldap subcomponents filter by name + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + parent_id: "{{ myLdapId }}" + name: "email" + register: result + +- name: Assert sub component with name "email" exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].name == "email" + +- name: Retrive ldap subcomponents filter by type + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + parent_id: "{{ myLdapId }}" + provider_type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper" + register: result + +- name: Assert ldap sub components filter by type + assert: + that: + - result is not changed + - result.components | length > 0 + - result.components[0].providerType == "org.keycloak.storage.ldap.mappers.LDAPStorageMapper" + +- name: Retrive key info when absent + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ realm_key_name }}" + provider_type: "org.keycloak.keys.KeyProvider" + realm: "{{ realm }}" + register: result + +- name: Assert key is missing + assert: + that: + - result is not changed + - result.components | length == 0 + +- name: Create custom realm key + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ realm_key_name }}" + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 150 + register: result + +- name: Retrive key info + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ realm_key_name }}" + provider_type: "org.keycloak.keys.KeyProvider" + realm: "{{ realm }}" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length == 1 + +- name: Retrive all realm components + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length > 0 + +- name: Retrive all ldap in realm + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + provider_type: "org.keycloak.storage.UserStorageProvider" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].providerType == "org.keycloak.storage.UserStorageProvider" + - result.components[0].name == "myldap" + +- name: Retrive component by name only + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ realm_key_name }}" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].providerType == "org.keycloak.keys.KeyProvider" + - result.components[0].name == realm_key_name diff --git a/tests/integration/targets/keycloak_component_info/vars/main.yml b/tests/integration/targets/keycloak_component_info/vars/main.yml new file mode 100644 index 0000000000..7f18d8459f --- /dev/null +++ b/tests/integration/targets/keycloak_component_info/vars/main.yml @@ -0,0 +1,19 @@ +--- +# 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 +admin_realm: master +admin_user: admin +admin_password: password +realm: myrealm + +federation: myldap + + +realm_key_name: testkey +realm_private_key: | + -----BEGIN PRIVATE KEY----- + MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9Mi7IKXPhqGiWGwgEYEXnqc8nytG1pHbC6QYZe1gUa43jDtGYQln36It02BGw4e5XydCUj+M26X2sH+kKaV+KHEnJtcEqdAuVX1QaDVzeiOoo1/B9HC8By6NZBsOSdxpat3EvilQ+R7NP9yi53J08+vfeZSEGyPmKV1me7nJnRh3/zcRsOi92GTsBd7gApKfP8sorDjY8m9NRuPLwleK2nh/aRvj1yK8x3UAqUIbOCVaE39bSN6VUTFK2Q/+MX3vF0Zugsk7PKKmfqcEW6wj7dtSElbX4uhrfTkGMmwIWdIiLDNRA/jVRvGxUB1SyMy6kmMC8jC2QGWpZgfkSKtHlAgMBAAECggEACWkSVh7ntmjtwM+Z47vVJkt2NBS8vxPt206DYOeXbzaVUV6mkrP0LSZKL3bi1GE8fW3am9UXWF8fQt04dm3c1G4JRojtkXrBq72Y3Y3eGWyGdx8chWCOPwDdwFsbhbC6ZRo8PUDcZVekJd1Vj38XbBXQl+WAUcnTzauAF+1kz9mhJq1gpglIbB+8l7VjMXwXeaGWJQ5OL/MSsq7r3P1elVjHwprFBM7HHA5+RTu/KY/GcEutgm5uwTRqRZNC1IBXAQtBO7HQJbuLqDPTQ3RRCPEur8R+0dk5bF+8IyzQ8Bh+Dhuou9xzfS/A7lV6L/CZSpv4Bvq1H3Uxk+orXf2Q2QKBgQDBOf1nSJB0VgQdIcdtgVpVgQ2SyWAd+N8Qk7QsyVQf9f7ZqiFLejWJbaaeY9WtfZ01D8tgHJfPqsO1/Jux255mtkyk2K2c6dav1Lsd4l+iPfidsDJNWkcd59nQqwC9BLjzWK/J4rO20apm34abLaZ9oVk8Mgz8VWJWOxTgCr+COQKBgQD6qP1lm6rzlCSIEz9eCuGPkQkVo+NIP437e3i+sxtkLlMgnmfzSwSJdVF8AKH3gXi3NyWjfBVYeAZEkm1kHF8IWOiK4U1y95Vx3uud3NX4SC+cjePc+pDPQJiz9L+zq9I6WFZWmm7n/9heTxu/l0vxI4FHaBmt95BMwLJNkzbdDQKBgCHGwUUMqjOr1YxCG1pJAkFwDa9bBDI5DsUXDKfHia0Mkz/5PVi0RCeBw15slS1+h7x+xk5GsULb1to5Df5JJadOtpcaST7koWKbDRpsN8tkidEGu8RJw6S2opyXR8nCyZHALvpbZo7Ol7rj1+PIVxIe4jpjhWGWi1oHed6wAkoBAoGAJx2F5XxEUhx1EvMF+XPzPQciBsl7Z0PbsTnUXtXuWVTNThLKH/I99AFlxNcIb2o530VwzzFG13Zra/n5rhyrS88sArgj8OPn40wpMopKraL+Iw0VWN+VB3KKIdL4s14FwWsVlhAlbHjFV/o6V0yR4kBrJSx+jWJLl16etHJbpmUCgYBUWCQwcT1aw9XHWJXiNYTnQSYg88hgGYhts1qSzhfu+n1t2BlAlxM0gu2+gez21mM8uiYsqbU2OZeG2U4as6kdai8Q4tzNQt2f1r3ZewJN/QHrkx6FT94PNa0w4ILiQ9Eu7xssaHcYjHyrI1NlbMKypVy6waDG2ajLOFAVeHGpOg== + -----END PRIVATE KEY-----