From f39fc53858b8a7e2a43f14c29f9d591b55b4e071 Mon Sep 17 00:00:00 2001
From: Philippe Moll
Date: Mon, 5 Dec 2022 16:33:44 +0100
Subject: [PATCH] feat: Add Client Scopes of Client
---
src/keycloak/keycloak_admin.py | 136 +++++++++++++++++++++++++++++++++
src/keycloak/urls_patterns.py | 8 ++
tests/test_keycloak_admin.py | 92 ++++++++++++++++++++++
3 files changed, 236 insertions(+)
diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py
index 26792f5..d9742cf 100644
--- a/src/keycloak/keycloak_admin.py
+++ b/src/keycloak/keycloak_admin.py
@@ -1539,6 +1539,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 f6d34d0..c59bb8c 100644
--- a/tests/test_keycloak_admin.py
+++ b/tests/test_keycloak_admin.py
@@ -1318,6 +1318,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.