diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 9a60e07..91d2c50 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -1561,6 +1561,142 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) + def get_client_default_client_scopes(self, client_id): + """Get all default client scopes from client. + + :param client_id: id of the client in which the new default client scope should be added + :type client_id: str + + :return: list of client scopes with id and name + :rtype: list + """ + params_path = {"realm-name": self.realm_name, "id": client_id} + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path) + ) + return raise_error_from_response(data_raw, KeycloakGetError) + + def add_client_default_client_scope(self, client_id, client_scope_id, payload): + """Add a client scope to the default client scopes from client. + + Payload example:: + + payload={ + "realm":"testrealm", + "client":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "clientScopeId":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + } + + :param client_id: id of the client in which the new default client scope should be added + :type client_id: str + :param client_scope_id: id of the new client scope that should be added + :type client_scope_id: str + :param payload: dictionary with realm, client and clientScopeId + :type payload: dict + + :return: Http response + :rtype: bytes + """ + params_path = { + "realm-name": self.realm_name, + "id": client_id, + "client_scope_id": client_scope_id, + } + data_raw = self.raw_put( + urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakPutError) + + def delete_client_default_client_scope(self, client_id, client_scope_id): + """Delete a client scope from the default client scopes of the client. + + :param client_id: id of the client in which the default client scope should be deleted + :type client_id: str + :param client_scope_id: id of the client scope that should be deleted + :type client_scope_id: str + + :return: list of client scopes with id and name + :rtype: list + """ + params_path = { + "realm-name": self.realm_name, + "id": client_id, + "client_scope_id": client_scope_id, + } + data_raw = self.raw_delete( + urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path) + ) + return raise_error_from_response(data_raw, KeycloakDeleteError) + + def get_client_optional_client_scopes(self, client_id): + """Get all optional client scopes from client. + + :param client_id: id of the client in which the new optional client scope should be added + :type client_id: str + + :return: list of client scopes with id and name + :rtype: list + """ + params_path = {"realm-name": self.realm_name, "id": client_id} + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path) + ) + return raise_error_from_response(data_raw, KeycloakGetError) + + def add_client_optional_client_scope(self, client_id, client_scope_id, payload): + """Add a client scope to the optional client scopes from client. + + Payload example:: + + payload={ + "realm":"testrealm", + "client":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "clientScopeId":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + } + + :param client_id: id of the client in which the new optional client scope should be added + :type client_id: str + :param client_scope_id: id of the new client scope that should be added + :type client_scope_id: str + :param payload: dictionary with realm, client and clientScopeId + :type payload: dict + + :return: Http response + :rtype: bytes + """ + params_path = { + "realm-name": self.realm_name, + "id": client_id, + "client_scope_id": client_scope_id, + } + data_raw = self.raw_put( + urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakPutError) + + def delete_client_optional_client_scope(self, client_id, client_scope_id): + """Delete a client scope from the optional client scopes of the client. + + :param client_id: id of the client in which the optional client scope should be deleted + :type client_id: str + :param client_scope_id: id of the client scope that should be deleted + :type client_scope_id: str + + :return: list of client scopes with id and name + :rtype: list + """ + params_path = { + "realm-name": self.realm_name, + "id": client_id, + "client_scope_id": client_scope_id, + } + data_raw = self.raw_delete( + urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path) + ) + return raise_error_from_response(data_raw, KeycloakDeleteError) + def create_client(self, payload, skip_exists=False): """Create a client. diff --git a/src/keycloak/urls_patterns.py b/src/keycloak/urls_patterns.py index 4ea0449..d8c5f0f 100644 --- a/src/keycloak/urls_patterns.py +++ b/src/keycloak/urls_patterns.py @@ -95,6 +95,14 @@ URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES = URL_ADMIN_CLIENT + "/scope-mapping URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES = ( URL_ADMIN_CLIENT + "/scope-mappings/clients/{client}" ) +URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES = URL_ADMIN_CLIENT + "/optional-client-scopes" +URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE = ( + URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES + "/{client_scope_id}" +) +URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES = URL_ADMIN_CLIENT + "/default-client-scopes" +URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE = ( + URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES + "/{client_scope_id}" +) 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 2ec7f8b..5b1cf06 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -1327,6 +1327,98 @@ def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str assert len(roles) == 0 +def test_client_default_client_scopes(admin: KeycloakAdmin, realm: str, client: str): + """Test client assignment of default client scopes. + + :param admin: Keycloak admin + :type admin: KeycloakAdmin + :param realm: Keycloak realm + :type realm: str + :param client: Keycloak client + :type client: str + """ + admin.realm_name = realm + + client_id = admin.create_client( + payload={"name": "role-testing-client", "clientId": "role-testing-client"} + ) + # Test get client default scopes + # keycloak default roles: web-origins, acr, profile, roles, email + default_client_scopes = admin.get_client_default_client_scopes(client_id) + assert len(default_client_scopes) == 5, default_client_scopes + + # Test add a client scope to client default scopes + default_client_scope = "test-client-default-scope" + new_client_scope = { + "name": default_client_scope, + "description": f"Test Client Scope: {default_client_scope}", + "protocol": "openid-connect", + "attributes": {}, + } + new_client_scope_id = admin.create_client_scope(new_client_scope, skip_exists=False) + new_default_client_scope_data = { + "realm": realm, + "client": client_id, + "clientScopeId": new_client_scope_id, + } + admin.add_client_default_client_scope( + client_id, new_client_scope_id, new_default_client_scope_data + ) + default_client_scopes = admin.get_client_default_client_scopes(client_id) + assert len(default_client_scopes) == 6, default_client_scopes + + # Test remove a client default scope + admin.delete_client_default_client_scope(client_id, new_client_scope_id) + default_client_scopes = admin.get_client_default_client_scopes(client_id) + assert len(default_client_scopes) == 5, default_client_scopes + + +def test_client_optional_client_scopes(admin: KeycloakAdmin, realm: str, client: str): + """Test client assignment of optional client scopes. + + :param admin: Keycloak admin + :type admin: KeycloakAdmin + :param realm: Keycloak realm + :type realm: str + :param client: Keycloak client + :type client: str + """ + admin.realm_name = realm + + client_id = admin.create_client( + payload={"name": "role-testing-client", "clientId": "role-testing-client"} + ) + # Test get client optional scopes + # keycloak optional roles: microprofile-jwt, offline_access, address, phone + optional_client_scopes = admin.get_client_optional_client_scopes(client_id) + assert len(optional_client_scopes) == 4, optional_client_scopes + + # Test add a client scope to client optional scopes + optional_client_scope = "test-client-optional-scope" + new_client_scope = { + "name": optional_client_scope, + "description": f"Test Client Scope: {optional_client_scope}", + "protocol": "openid-connect", + "attributes": {}, + } + new_client_scope_id = admin.create_client_scope(new_client_scope, skip_exists=False) + new_optional_client_scope_data = { + "realm": realm, + "client": client_id, + "clientScopeId": new_client_scope_id, + } + admin.add_client_optional_client_scope( + client_id, new_client_scope_id, new_optional_client_scope_data + ) + optional_client_scopes = admin.get_client_optional_client_scopes(client_id) + assert len(optional_client_scopes) == 5, optional_client_scopes + + # Test remove a client optional scope + admin.delete_client_optional_client_scope(client_id, new_client_scope_id) + optional_client_scopes = admin.get_client_optional_client_scopes(client_id) + assert len(optional_client_scopes) == 4, optional_client_scopes + + def test_client_roles(admin: KeycloakAdmin, client: str): """Test client roles.