diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 8415d10..3b1b00a 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -2205,6 +2205,30 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + def remove_composite_client_roles_from_role(self, client_role_id, role_name, roles): + """Remove composite roles from a client role. + + :param client_role_id: id of client (not client-id) + :type client_role_id: str + :param role_name: The name of the role + :type role_name: str + :param roles: roles list or role (use RoleRepresentation) to be removed + :type roles: list + :return: Keycloak server response + :rtype: bytes + """ + payload = roles if isinstance(roles, list) else [roles] + params_path = { + "realm-name": self.connection.realm_name, + "id": client_role_id, + "role-name": role_name, + } + data_raw = self.connection.raw_delete( + urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + def update_client_role(self, client_id, role_name, payload): """Update a client role. @@ -6395,6 +6419,30 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + async def a_remove_composite_client_roles_from_role(self, client_role_id, role_name, roles): + """Remove composite roles from a client role asynchronously. + + :param client_role_id: id of client (not client-id) + :type client_role_id: str + :param role_name: The name of the role + :type role_name: str + :param roles: roles list or role (use RoleRepresentation) to be removed + :type roles: list + :return: Keycloak server response + :rtype: bytes + """ + payload = roles if isinstance(roles, list) else [roles] + params_path = { + "realm-name": self.connection.realm_name, + "id": client_role_id, + "role-name": role_name, + } + data_raw = await self.connection.a_raw_delete( + urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + async def a_update_client_role(self, client_id, role_name, payload): """Update a client role asynchronously. diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index b87f413..f53ffaa 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -1934,6 +1934,22 @@ def test_client_roles(admin: KeycloakAdmin, client: str): "composite" ] + # Test removal of composite client roles + with pytest.raises(KeycloakDeleteError) as err: + admin.remove_composite_client_roles_from_role( + client_role_id=client, role_name="client-role-test-update", roles=["bad"] + ) + assert err.match(UNKOWN_ERROR_REGEX), err + res = admin.remove_composite_client_roles_from_role( + client_role_id=client, + role_name="client-role-test-update", + roles=[admin.get_realm_role(role_name="offline_access")], + ) + assert res == dict() + assert not admin.get_client_role(client_id=client, role_name="client-role-test-update")[ + "composite" + ] + # Test delete of client role res = admin.delete_client_role(client_role_id=client, role_name="client-role-test-update") assert res == dict() @@ -4987,6 +5003,22 @@ async def test_a_client_roles(admin: KeycloakAdmin, client: str): "composite" ] + # Test removal of composite client roles + with pytest.raises(KeycloakDeleteError) as err: + await admin.a_remove_composite_client_roles_from_role( + client_role_id=client, role_name="client-role-test-update", roles=["bad"] + ) + assert err.match(UNKOWN_ERROR_REGEX), err + res = await admin.a_remove_composite_client_roles_from_role( + client_role_id=client, + role_name="client-role-test-update", + roles=[await admin.a_get_realm_role(role_name="offline_access")], + ) + assert res == dict() + assert not ( + await admin.a_get_client_role(client_id=client, role_name="client-role-test-update") + )["composite"] + # Test delete of client role res = await admin.a_delete_client_role( client_role_id=client, role_name="client-role-test-update"