From aa207286f05372e2d59aafe72db4b4b6aee57c70 Mon Sep 17 00:00:00 2001 From: Richard Nemeth Date: Wed, 11 Jan 2023 10:56:44 +0000 Subject: [PATCH] feat: added default realm roles handlers --- src/keycloak/keycloak_admin.py | 62 +++++++++++++++++++++++++++-- src/keycloak/urls_patterns.py | 6 +-- tests/test_keycloak_admin.py | 73 ++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 6d74a8c..26792f5 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -988,7 +988,7 @@ class KeycloakAdmin: data_raw = self.raw_put( urls_patterns.URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path), data=json.dumps(payload), - **params_query + **params_query, ) return raise_error_from_response(data_raw, KeycloakPutError) @@ -1012,7 +1012,7 @@ class KeycloakAdmin: data_raw = self.raw_put( urls_patterns.URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path), data={}, - **params_query + **params_query, ) return raise_error_from_response(data_raw, KeycloakPutError) @@ -1657,6 +1657,62 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query ) + def get_default_realm_role_id(self): + """Get the ID of the default realm role. + + :return: Realm role ID + :rtype: str + """ + all_realm_roles = self.get_realm_roles() + default_realm_roles = [ + realm_role + for realm_role in all_realm_roles + if realm_role["name"] == f"default-roles-{self.realm_name}" + ] + return default_realm_roles[0]["id"] + + def get_realm_default_roles(self): + """Get all the default realm roles. + + :return: Keycloak Server Response (UserRepresentation) + :rtype: list + """ + params_path = {"realm-name": self.realm_name, "role-id": self.get_default_realm_role_id()} + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path) + ) + return raise_error_from_response(data_raw, KeycloakGetError) + + def remove_realm_default_roles(self, payload): + """Remove a set of default realm roles. + + :param payload: List of RoleRepresentations + :type payload: list + :return: Keycloak Server Response + :rtype: dict + """ + params_path = {"realm-name": self.realm_name, "role-id": self.get_default_realm_role_id()} + data_raw = self.raw_delete( + urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakDeleteError) + + def add_realm_default_roles(self, payload): + """Add a set of default realm roles. + + :param payload: List of RoleRepresentations + :type payload: list + :return: Keycloak Server Response + :rtype: dict + """ + params_path = {"realm-name": self.realm_name, "role-id": self.get_default_realm_role_id()} + data_raw = self.raw_post( + urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakPostError) + def get_client_roles(self, client_id, brief_representation=True): """Get all roles for the client. @@ -2664,7 +2720,7 @@ class KeycloakAdmin: data_raw = self.raw_post( urls_patterns.URL_ADMIN_USER_STORAGE.format(**params_path), data=json.dumps(data), - **params_query + **params_query, ) return raise_error_from_response(data_raw, KeycloakPostError) diff --git a/src/keycloak/urls_patterns.py b/src/keycloak/urls_patterns.py index b5f3277..4ea0449 100644 --- a/src/keycloak/urls_patterns.py +++ b/src/keycloak/urls_patterns.py @@ -189,9 +189,9 @@ URL_ADMIN_EVENTS_CONFIG = URL_ADMIN_EVENTS + "/config" URL_ADMIN_CLIENT_SESSION_STATS = "admin/realms/{realm-name}/client-session-stats" URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE = URL_ADMIN_GROUPS_CLIENT_ROLES + "/composite" -URL_ADMIN_CLIENT_ROLE_CHILDREN = ( - "admin/realms/{realm-name}/roles-by-id/{role-id}/composites/clients/{client-id}" -) +URL_ADMIN_REALM_ROLE_COMPOSITES = "admin/realms/{realm-name}/roles-by-id/{role-id}/composites" +URL_ADMIN_REALM_ROLE_COMPOSITES_REALM = URL_ADMIN_REALM_ROLE_COMPOSITES + "/realm" +URL_ADMIN_CLIENT_ROLE_CHILDREN = URL_ADMIN_REALM_ROLE_COMPOSITES + "/clients/{client-id}" URL_ADMIN_CLIENT_CERT_UPLOAD = URL_ADMIN_CLIENT_CERTS + "/upload-certificate" URL_ADMIN_REQUIRED_ACTIONS = URL_ADMIN_REALM + "/authentication/required-actions" URL_ADMIN_REQUIRED_ACTIONS_ALIAS = URL_ADMIN_REQUIRED_ACTIONS + "/{action-alias}" diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index 9dd6b51..f6d34d0 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -2421,3 +2421,76 @@ def test_clear_bruteforce_attempts_for_all_users( res = admin.update_realm(realm_name=realm, payload={"bruteForceProtected": False}) res = admin.get_realm(realm_name=realm) assert res["bruteForceProtected"] is False + + +def test_default_realm_role_present(realm: str, admin: KeycloakAdmin) -> None: + """Test that the default realm role is present in a brand new realm. + + :param realm: Realm name + :type realm: str + :param admin: Keycloak admin + :type admin: KeycloakAdmin + """ + admin.realm_name = realm + assert f"default-roles-{realm}" in [x["name"] for x in admin.get_realm_roles()] + assert ( + len([x["name"] for x in admin.get_realm_roles() if x["name"] == f"default-roles-{realm}"]) + == 1 + ) + + +def test_get_default_realm_role_id(realm: str, admin: KeycloakAdmin) -> None: + """Test getter for the ID of the default realm role. + + :param realm: Realm name + :type realm: str + :param admin: Keycloak admin + :type admin: KeycloakAdmin + """ + admin.realm_name = realm + assert ( + admin.get_default_realm_role_id() + == [x["id"] for x in admin.get_realm_roles() if x["name"] == f"default-roles-{realm}"][0] + ) + + +def test_realm_default_roles(admin: KeycloakAdmin, realm: str) -> None: + """Test getting, adding and deleting default realm roles. + + :param realm: Realm name + :type realm: str + :param admin: Keycloak admin + :type admin: KeycloakAdmin + """ + admin.realm_name = realm + + # Test listing all default realm roles + roles = admin.get_realm_default_roles() + assert len(roles) == 2 + assert {x["name"] for x in roles} == {"offline_access", "uma_authorization"} + + with pytest.raises(KeycloakGetError) as err: + admin.realm_name = "doesnotexist" + admin.get_realm_default_roles() + assert err.match('404: b\'{"error":"Realm not found."}\'') + admin.realm_name = realm + + # Test removing a default realm role + res = admin.remove_realm_default_roles(payload=[roles[0]]) + assert res == {} + assert roles[0] not in admin.get_realm_default_roles() + assert len(admin.get_realm_default_roles()) == 1 + + with pytest.raises(KeycloakDeleteError) as err: + admin.remove_realm_default_roles(payload=[{"id": "bad id"}]) + assert err.match('404: b\'{"error":"Could not find composite role"}\'') + + # Test adding a default realm role + res = admin.add_realm_default_roles(payload=[roles[0]]) + assert res == {} + assert roles[0] in admin.get_realm_default_roles() + assert len(admin.get_realm_default_roles()) == 2 + + with pytest.raises(KeycloakPostError) as err: + admin.add_realm_default_roles(payload=[{"id": "bad id"}]) + assert err.match('404: b\'{"error":"Could not find composite role"}\'')