From 627093d5a784780b3ed5eff6eb4fc5cc9a89adf5 Mon Sep 17 00:00:00 2001 From: Killian Levacher Date: Fri, 4 Jul 2025 14:27:02 +0100 Subject: [PATCH 1/2] fix for issue #10342 --- .../identity/keycloak/keycloak.py | 2 +- plugins/modules/keycloak_client_rolescope.py | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index e053eca305..9a7e3e8a02 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -29,7 +29,7 @@ URL_CLIENT_ROLES = "{url}/admin/realms/{realm}/clients/{id}/roles" URL_CLIENT_ROLE = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}" URL_CLIENT_ROLE_COMPOSITES = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}/composites" -URL_CLIENT_ROLE_SCOPE_CLIENTS = "{url}/admin/realms/{realm}/clients/{id}/scope-mappings/clients/{scopeid}" +URL_CLIENT_ROLE_SCOPE_CLIENTS = "{url}/admin/realms/{realm}/client-scopes/{scopeid}/scope-mappings/clients/{id}" URL_CLIENT_ROLE_SCOPE_REALM = "{url}/admin/realms/{realm}/clients/{id}/scope-mappings/realm" URL_REALM_ROLES = "{url}/admin/realms/{realm}/roles" diff --git a/plugins/modules/keycloak_client_rolescope.py b/plugins/modules/keycloak_client_rolescope.py index db11f37098..0b243910f0 100644 --- a/plugins/modules/keycloak_client_rolescope.py +++ b/plugins/modules/keycloak_client_rolescope.py @@ -193,27 +193,27 @@ def main(): objRealm = kc.get_realm_by_id(realm) if not objRealm: - module.fail_json(msg="Failed to retrive realm '{realm}'".format(realm=realm)) + module.fail_json(msg="Failed to retrieve realm '{realm}'".format(realm=realm)) objClient = kc.get_client_by_clientid(clientid, realm) if not objClient: - module.fail_json(msg="Failed to retrive client '{realm}.{clientid}'".format(realm=realm, clientid=clientid)) + module.fail_json(msg="Failed to retrieve client '{realm}.{clientid}'".format(realm=realm, clientid=clientid)) if objClient["fullScopeAllowed"] and state == "present": module.fail_json(msg="FullScopeAllowed is active for Client '{realm}.{clientid}'".format(realm=realm, clientid=clientid)) if client_scope_id: - objClientScope = kc.get_client_by_clientid(client_scope_id, realm) + objClientScope = kc.get_clientscope_by_clientscopeid(client_scope_id, realm) if not objClientScope: - module.fail_json(msg="Failed to retrive client '{realm}.{client_scope_id}'".format(realm=realm, client_scope_id=client_scope_id)) + module.fail_json(msg="Failed to retrieve client '{realm}.{client_scope_id}'".format(realm=realm, client_scope_id=client_scope_id)) before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], objClientScope["id"], realm) else: before_role_mapping = kc.get_client_role_scope_from_realm(objClient["id"], realm) - if client_scope_id: - # retrive all role from client_scope - client_scope_roles_by_name = kc.get_client_roles_by_id(objClientScope["id"], realm) + if objClient: + # retrieve all role from client + client_scope_roles_by_name = kc.get_client_roles_by_id(objClient["id"], realm) else: - # retrive all role from realm + # retrieve all role from realm client_scope_roles_by_name = kc.get_realm_roles(realm) # convert to indexed Dict by name @@ -226,10 +226,10 @@ def main(): for role_name in role_names: if role_name not in client_scope_roles_by_name: if client_scope_id: - module.fail_json(msg="Failed to retrive role '{realm}.{client_scope_id}.{role_name}'" + module.fail_json(msg="Failed to retrieve role '{realm}.{client_scope_id}.{role_name}'" .format(realm=realm, client_scope_id=client_scope_id, role_name=role_name)) else: - module.fail_json(msg="Failed to retrive role '{realm}.{role_name}'".format(realm=realm, role_name=role_name)) + module.fail_json(msg="Failed to retrieve role '{realm}.{role_name}'".format(realm=realm, role_name=role_name)) if role_name not in role_mapping_by_name: role_mapping_to_manipulate.append(client_scope_roles_by_name[role_name]) role_mapping_by_name[role_name] = client_scope_roles_by_name[role_name] From 9457ad950b317f7b2501c83fd72fdde066493d84 Mon Sep 17 00:00:00 2001 From: Killian Levacher Date: Tue, 15 Jul 2025 15:02:59 +0100 Subject: [PATCH 2/2] reverted suggested fixes to original code and added example where integration test would fail --- .../identity/keycloak/keycloak.py | 5 +- plugins/modules/keycloak_client_rolescope.py | 16 +++- .../keycloak_client_rolescope/tasks/main.yml | 76 ++++++++++++++++++- 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 9a7e3e8a02..f4258c6418 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -29,7 +29,10 @@ URL_CLIENT_ROLES = "{url}/admin/realms/{realm}/clients/{id}/roles" URL_CLIENT_ROLE = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}" URL_CLIENT_ROLE_COMPOSITES = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}/composites" -URL_CLIENT_ROLE_SCOPE_CLIENTS = "{url}/admin/realms/{realm}/client-scopes/{scopeid}/scope-mappings/clients/{id}" +URL_CLIENT_ROLE_SCOPE_CLIENTS = "{url}/admin/realms/{realm}/clients/{id}/scope-mappings/clients/{scopeid}" + +#SUGGESTED FIX +# URL_CLIENT_ROLE_SCOPE_CLIENTS = "{url}/admin/realms/{realm}/client-scopes/{scopeid}/scope-mappings/clients/{id}" URL_CLIENT_ROLE_SCOPE_REALM = "{url}/admin/realms/{realm}/clients/{id}/scope-mappings/realm" URL_REALM_ROLES = "{url}/admin/realms/{realm}/roles" diff --git a/plugins/modules/keycloak_client_rolescope.py b/plugins/modules/keycloak_client_rolescope.py index 0b243910f0..bb9294a188 100644 --- a/plugins/modules/keycloak_client_rolescope.py +++ b/plugins/modules/keycloak_client_rolescope.py @@ -202,16 +202,24 @@ def main(): module.fail_json(msg="FullScopeAllowed is active for Client '{realm}.{clientid}'".format(realm=realm, clientid=clientid)) if client_scope_id: - objClientScope = kc.get_clientscope_by_clientscopeid(client_scope_id, realm) + objClientScope = kc.get_client_by_clientid(client_scope_id, realm) + + #SUGGESTED FIX + # objClientScope = kc.get_clientscope_by_clientscopeid(client_scope_id, realm) if not objClientScope: module.fail_json(msg="Failed to retrieve client '{realm}.{client_scope_id}'".format(realm=realm, client_scope_id=client_scope_id)) before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], objClientScope["id"], realm) else: before_role_mapping = kc.get_client_role_scope_from_realm(objClient["id"], realm) - if objClient: - # retrieve all role from client - client_scope_roles_by_name = kc.get_client_roles_by_id(objClient["id"], realm) + if client_scope_id: + # retrive all role from client_scope + client_scope_roles_by_name = kc.get_client_roles_by_id(objClientScope["id"], realm) + + #SUGGESTED FIX + # if objClient: + # # retrieve all role from client + # client_scope_roles_by_name = kc.get_client_roles_by_id(objClient["id"], realm) else: # retrieve all role from realm client_scope_roles_by_name = kc.get_realm_roles(realm) diff --git a/tests/integration/targets/keycloak_client_rolescope/tasks/main.yml b/tests/integration/targets/keycloak_client_rolescope/tasks/main.yml index d4c60d3f2e..fd3a5ebc62 100644 --- a/tests/integration/targets/keycloak_client_rolescope/tasks/main.yml +++ b/tests/integration/targets/keycloak_client_rolescope/tasks/main.yml @@ -43,6 +43,77 @@ - "{{ realm_role_admin }}" - "{{ realm_role_user }}" +############################# +### Additional Steps which lead to failure + +- name: Client private 2 + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "client_name_private_2" + state: present + redirect_uris: + - "https://my-backend-api.c.org/" + fullScopeAllowed: false + attributes: "{{client_attributes1}}" + public_client: false + +- name: Create a Keycloak client role for Client private 2 + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ item }}" + realm: "{{ realm }}" + client_id: "client_name_private_2" + with_items: + - "client_role_admin_2" + - "client_role_user_2" + +- name: Create client scope + community.general.keycloak_clientscope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + + name: client_scope_name + protocol: openid-connect + attributes: + "include.in.token.scope": "true" + "display.on.consent.screen": "false" + state: present + register: new_scope + +# This step fails - expectation was for role to be assigned to client scope - when making suggested changes this steps works +# when client_scope_id is given instead the {{ client_name_private }} (as for the task 'Map roles to public client') this step does not fail, however no role is assigned to this scope +# With the fix I am providing, when client_scope_id is provided with the scope_id (as opposed to a client name), the role is indeed assigned +# to the scope but the 'Map roles to public client' task fails probably for the reverse reasons +# Unless mistaken, somehow the underlying code seems to be confusing the notion of a client_scope_id with that of a client_id + +- name: STEP which fails - Assign admin role to client scope + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + + client_scope_id: "{{ new_scope.end_state.id}}" + # client_scope_id: "{{ client_name_private }}" + client_id: "client_name_private_2" + + role_names: + - "client_role_admin_2" + state: present + +############################# + - name: Client private community.general.keycloak_client: auth_keycloak_url: "{{ url }}" @@ -55,7 +126,7 @@ redirect_uris: - "https://my-backend-api.c.org/" fullScopeAllowed: true - attributes: '{{client_attributes1}}' + attributes: "{{client_attributes1}}" public_client: false - name: Create a Keycloak client role @@ -81,11 +152,10 @@ client_id: "{{ client_name_public }}" redirect_uris: - "https://my-onepage-app-frontend.c.org/" - attributes: '{{client_attributes1}}' + attributes: "{{client_attributes1}}" full_scope_allowed: false public_client: true - - name: Map roles to public client community.general.keycloak_client_rolescope: auth_keycloak_url: "{{ url }}"