From 0fb6c2058d96d2c1f194665243eef4e7f10c0ae8 Mon Sep 17 00:00:00 2001 From: Antonio Lucas Neres Date: Thu, 11 Aug 2022 10:35:53 -0300 Subject: [PATCH] feat: add client scope-mappings realm roles operations --- README.md | 56 ++++++++++++++++++-------------- src/keycloak/keycloak_admin.py | 42 ++++++++++++++++++++++++ src/keycloak/urls_patterns.py | 1 + tests/test_keycloak_admin.py | 58 ++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 99eba7d..9666ba9 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,9 @@ from keycloak import KeycloakOpenID # Configure client keycloak_openid = KeycloakOpenID(server_url="http://localhost:8080/auth/", - client_id="example_client", - realm_name="example_realm", - client_secret_key="secret") + client_id="example_client", + realm_name="example_realm", + client_secret_key="secret") # Get WellKnow config_well_known = keycloak_openid.well_known() @@ -110,7 +110,7 @@ rpt = keycloak_openid.entitlement(token['access_token'], "resource_id") # Instropect RPT token_rpt_info = keycloak_openid.introspect(keycloak_openid.introspect(token['access_token'], rpt=rpt['rpt'], - token_type_hint="requesting_party_token")) + token_type_hint="requesting_party_token")) # Introspect Token token_info = keycloak_openid.introspect(token['access_token']) @@ -153,37 +153,37 @@ keycloak_admin = KeycloakAdmin(server_url="http://localhost:8080/auth/", # Add user new_user = keycloak_admin.create_user({"email": "example@example.com", - "username": "example@example.com", - "enabled": True, - "firstName": "Example", - "lastName": "Example"}) + "username": "example@example.com", + "enabled": True, + "firstName": "Example", + "lastName": "Example"}) # Add user and raise exception if username already exists # exist_ok currently defaults to True for backwards compatibility reasons new_user = keycloak_admin.create_user({"email": "example@example.com", - "username": "example@example.com", - "enabled": True, - "firstName": "Example", - "lastName": "Example"}, - exist_ok=False) + "username": "example@example.com", + "enabled": True, + "firstName": "Example", + "lastName": "Example"}, + exist_ok=False) # Add user and set password new_user = keycloak_admin.create_user({"email": "example@example.com", - "username": "example@example.com", - "enabled": True, - "firstName": "Example", - "lastName": "Example", + "username": "example@example.com", + "enabled": True, + "firstName": "Example", + "lastName": "Example", "credentials": [{"value": "secret","type": "password",}]}) # Add user and specify a locale new_user = keycloak_admin.create_user({"email": "example@example.fr", - "username": "example@example.fr", - "enabled": True, - "firstName": "Example", - "lastName": "Example", - "attributes": { - "locale": ["fr"] - }}) + "username": "example@example.fr", + "enabled": True, + "firstName": "Example", + "lastName": "Example", + "attributes": { + "locale": ["fr"] + }}) # User counter count_users = keycloak_admin.users_count() @@ -312,6 +312,14 @@ keycloak_admin.assign_client_role(client_id=client_id, user_id=user_id, role_id= # Assign realm roles to user keycloak_admin.assign_realm_roles(user_id=user_id, roles=realm_roles) +# Assign realm roles to client's scope +keycloak_admin.assign_realm_roles_to_client_scope(client_id=client_id, roles=realm_roles) + +# Get realm roles assigned to client's scope +keycloak_admin.get_realm_roles_of_client_scope(client_id=client_id) + +# Remove realm roles assigned to client's scope +keycloak_admin.delete_realm_roles_of_client_scope(client_id=client_id, roles=realm_roles) # Get all ID Providers idps = keycloak_admin.get_idps() diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 7d16ad5..50299b6 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -1597,6 +1597,48 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) + def assign_realm_roles_to_client_scope(self, client_id, roles): + """Assign realm roles to a client's scope. + + :param client_id: id of client (not client-id) + :param roles: roles list or role (use RoleRepresentation) + :return: Keycloak server response + """ + payload = roles if isinstance(roles, list) else [roles] + params_path = {"realm-name": self.realm_name, "id": client_id} + data_raw = self.raw_post( + urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204]) + + def delete_realm_roles_of_client_scope(self, client_id, roles): + """Delete realm roles of a client's scope. + + :param client_id: id of client (not client-id) + :param roles: roles list or role (use RoleRepresentation) + :return: Keycloak server response + """ + payload = roles if isinstance(roles, list) else [roles] + params_path = {"realm-name": self.realm_name, "id": client_id} + data_raw = self.raw_delete( + urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) + + def get_realm_roles_of_client_scope(self, client_id): + """Get all realm roles for a client's scope. + + :param client_id: id of client (not client-id) + :return: Keycloak server response (array RoleRepresentation) + """ + params_path = {"realm-name": self.realm_name, "id": client_id} + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path) + ) + return raise_error_from_response(data_raw, KeycloakGetError) + def assign_realm_roles(self, user_id, roles): """Assign realm roles to a user. diff --git a/src/keycloak/urls_patterns.py b/src/keycloak/urls_patterns.py index 3f4151e..f7d4f65 100644 --- a/src/keycloak/urls_patterns.py +++ b/src/keycloak/urls_patterns.py @@ -91,6 +91,7 @@ URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE = URL_ADMIN_CLIENT_ROLE + "/composi URL_ADMIN_CLIENT_ROLE_MEMBERS = URL_ADMIN_CLIENT + "/roles/{role-name}/users" URL_ADMIN_CLIENT_ROLE_GROUPS = URL_ADMIN_CLIENT + "/roles/{role-name}/groups" URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS = URL_ADMIN_CLIENT + "/management/permissions" +URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES = URL_ADMIN_CLIENT + "/scope-mappings/realm" URL_ADMIN_CLIENT_AUTHZ_SETTINGS = URL_ADMIN_CLIENT + "/authz/resource-server/settings" URL_ADMIN_CLIENT_AUTHZ_RESOURCES = URL_ADMIN_CLIENT + "/authz/resource-server/resource?max=-1" diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index 61685af..e9b093c 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -1030,6 +1030,64 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): assert err.match('404: b\'{"error":"Could not find role"}\'') +def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): + """Test client realm roles.""" + admin.realm_name = realm + + # Test get realm roles + roles = admin.get_realm_roles() + assert len(roles) == 3, roles + role_names = [x["name"] for x in roles] + assert "uma_authorization" in role_names, role_names + assert "offline_access" in role_names, role_names + + # create realm role for test + role_id = admin.create_realm_role(payload={"name": "test-realm-role"}, skip_exists=True) + assert role_id, role_id + + # Test realm role client assignment + client_id = admin.create_client( + payload={"name": "role-testing-client", "clientId": "role-testing-client"} + ) + with pytest.raises(KeycloakPostError) as err: + admin.assign_realm_roles_to_client_scope(client_id=client_id, roles=["bad"]) + assert err.match('500: b\'{"error":"unknown_error"}\'') + res = admin.assign_realm_roles_to_client_scope( + client_id=client_id, + roles=[ + admin.get_realm_role(role_name="offline_access"), + admin.get_realm_role(role_name="test-realm-role"), + ], + ) + assert res == dict(), res + + roles = admin.get_realm_roles_of_client_scope(client_id=client_id) + assert len(roles) == 2 + client_role_names = [x["name"] for x in roles] + assert "offline_access" in client_role_names, client_role_names + assert "test-realm-role" in client_role_names, client_role_names + assert "uma_authorization" not in client_role_names, client_role_names + + # Test remove realm role of client + with pytest.raises(KeycloakDeleteError) as err: + admin.delete_realm_roles_of_client_scope(client_id=client_id, roles=["bad"]) + assert err.match('500: b\'{"error":"unknown_error"}\'') + res = admin.delete_realm_roles_of_client_scope( + client_id=client_id, roles=[admin.get_realm_role(role_name="offline_access")] + ) + assert res == dict(), res + roles = admin.get_realm_roles_of_client_scope(client_id=client_id) + assert len(roles) == 1 + assert "test-realm-role" in [x["name"] for x in roles] + + res = admin.delete_realm_roles_of_client_scope( + client_id=client_id, roles=[admin.get_realm_role(role_name="test-realm-role")] + ) + assert res == dict(), res + roles = admin.get_realm_roles_of_client_scope(client_id=client_id) + assert len(roles) == 0 + + def test_client_roles(admin: KeycloakAdmin, client: str): """Test client roles.""" # Test get client roles