From d9de70206baa98aece3e31d653aab977dc529d54 Mon Sep 17 00:00:00 2001 From: Logi Date: Thu, 17 Jul 2025 18:40:36 +0200 Subject: [PATCH] feat: add `get_composite_client_roles_of_role` (#660) * feat: add `get_composite_client_roles_of_role` * chore: formatting * chore: more formatting --- src/keycloak/keycloak_admin.py | 42 ++++++++++++++++++++++++++++++++++ test_keycloak_init.sh | 2 +- tests/test_keycloak_admin.py | 31 +++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 6135d4d..7cc603f 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -3629,6 +3629,27 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) + def get_composite_client_roles_of_role(self, client_id: str, role_name: str) -> list: + """ + Get composite roles of the client role. + + :param client_id: The id of the client + :type client_id: str + :param role_name: The name of the role + :type role_name: str + :return: Keycloak server response (array RoleRepresentation) + :rtype: list + """ + params_path = { + "realm-name": self.connection.realm_name, + "id": client_id, + "role-name": role_name, + } + data_raw = self.connection.raw_get( + urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), + ) + return raise_error_from_response(data_raw, KeycloakGetError) + def assign_realm_roles_to_client_scope(self, client_id: str, roles: str | list) -> bytes: """ Assign realm roles to a client's scope. @@ -8894,6 +8915,27 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) + async def a_get_composite_client_roles_of_role(self, client_id: str, role_name: str) -> list: + """ + Get composite roles of the client role. + + :param client_id: The id of the client + :type client_id: str + :param role_name: The name of the role + :type role_name: str + :return: Keycloak server response (array RoleRepresentation) + :rtype: list + """ + params_path = { + "realm-name": self.connection.realm_name, + "id": client_id, + "role-name": role_name, + } + data_raw = await self.connection.a_raw_get( + urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), + ) + return raise_error_from_response(data_raw, KeycloakGetError) + async def a_assign_realm_roles_to_client_scope( self, client_id: str, diff --git a/test_keycloak_init.sh b/test_keycloak_init.sh index 2350c64..74e15fd 100755 --- a/test_keycloak_init.sh +++ b/test_keycloak_init.sh @@ -20,7 +20,7 @@ function keycloak_start() { else KEYCLOAK_FEATURES="admin-fine-grained-authz:v1,token-exchange:v1" fi - docker run -d --name unittest_keycloak -e KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN}" -e KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}" -p "${KEYCLOAK_PORT}:8080" -v $PWD/tests/providers:/opt/keycloak/providers "${KEYCLOAK_DOCKER_IMAGE}" start-dev --features="${KEYCLOAK_FEATURES}" + docker run --rm -d --name unittest_keycloak -e KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN}" -e KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}" -p "${KEYCLOAK_PORT}:8080" -v $PWD/tests/providers:/opt/keycloak/providers "${KEYCLOAK_DOCKER_IMAGE}" start-dev --features="${KEYCLOAK_FEATURES}" SECONDS=0 until curl --silent --output /dev/null localhost:$KEYCLOAK_PORT; do sleep 5; diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index bcde668..4bad412 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -2246,7 +2246,13 @@ def test_client_roles(admin: KeycloakAdmin, client: str) -> None: ) assert res == {} - # Test composite client roles + # Test get composite client roles of role before adding + res = admin.get_composite_client_roles_of_role( + client_id=client, role_name="client-role-test-update" + ) + assert len(res) == 0 + + # Test add composite client roles to role with pytest.raises(KeycloakPostError) as err: admin.add_composite_client_roles_to_role( client_role_id=client, @@ -2264,6 +2270,15 @@ def test_client_roles(admin: KeycloakAdmin, client: str) -> None: "composite" ] + # Test get composite client roles of role after adding + res = admin.get_composite_client_roles_of_role( + client_id=client, role_name="client-role-test-update" + ) + assert len(res) == 1 + with pytest.raises(KeycloakGetError) as err: + admin.get_composite_client_roles_of_role(client_id=client, role_name="bad") + assert err.match(COULD_NOT_FIND_ROLE_REGEX) + # Test removal of composite client roles with pytest.raises(KeycloakDeleteError) as err: admin.remove_composite_client_roles_from_role( @@ -5856,7 +5871,13 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str) -> None: ) assert res == {} - # Test composite client roles + # Test get composite client roles of role before adding + res = await admin.a_get_composite_client_roles_of_role( + client_id=client, role_name="client-role-test-update" + ) + assert len(res) == 0 + + # Test add composite client roles to role with pytest.raises(KeycloakPostError) as err: await admin.a_add_composite_client_roles_to_role( client_role_id=client, @@ -5874,6 +5895,12 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str) -> None: "composite" ] + # Test get composite client roles of role after adding + res = await admin.a_get_composite_client_roles_of_role( + client_id=client, role_name="client-role-test-update" + ) + assert len(res) == 1 + # Test removal of composite client roles with pytest.raises(KeycloakDeleteError) as err: await admin.a_remove_composite_client_roles_from_role(