From 4908549e0e9409c1dae05412801f3bd06be1c175 Mon Sep 17 00:00:00 2001 From: Igor Pronin Date: Mon, 15 Dec 2025 20:41:55 +0300 Subject: [PATCH] feat: implement authz import request --- src/keycloak/keycloak_admin.py | 54 ++++++++++++++++++++++++++++++++++ src/keycloak/urls_patterns.py | 1 + tests/test_keycloak_admin.py | 50 +++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index fab1a71..f72219b 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -2210,6 +2210,33 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) + def import_client_authz_config(self, client_id: str, payload: dict) -> dict: + """ + Import client authorization configuration. + + ResourceServerRepresentation + https://www.keycloak.org/docs-api/latest/rest-api/index.html#ResourceServerRepresentation + + :param client_id: id in ClientRepresentation + https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation + :type client_id: str + :param payload: ResourceServerRepresentation + :type payload: dict + + :return: None + """ + params_path = {"realm-name": self.connection.realm_name, "id": client_id} + data_raw = self.connection.raw_post( + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_IMPORT.format(**params_path), + data=json.dumps(payload), + ) + + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) + def create_client_authz_resource( self, client_id: str, @@ -7501,6 +7528,33 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) + async def a_import_client_authz_config(self, client_id: str, payload: dict) -> dict: + """ + Import client authorization configuration asynchronously. + + ResourceServerRepresentation + https://www.keycloak.org/docs-api/latest/rest-api/index.html#ResourceServerRepresentation + + :param client_id: id in ClientRepresentation + https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_clientrepresentation + :type client_id: str + :param payload: ResourceServerRepresentation + :type payload: dict + + :return: None + """ + params_path = {"realm-name": self.connection.realm_name, "id": client_id} + data_raw = await self.connection.a_raw_post( + urls_patterns.URL_ADMIN_CLIENT_AUTHZ_IMPORT.format(**params_path), + data=json.dumps(payload), + ) + + return raise_error_from_response( + data_raw, + KeycloakPostError, + expected_codes=[HTTP_NO_CONTENT], + ) + async def a_create_client_authz_resource( self, client_id: str, diff --git a/src/keycloak/urls_patterns.py b/src/keycloak/urls_patterns.py index daf1889..4dc3054 100644 --- a/src/keycloak/urls_patterns.py +++ b/src/keycloak/urls_patterns.py @@ -115,6 +115,7 @@ URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE = ( URL_ADMIN_CLIENT_AUTHZ = URL_ADMIN_CLIENT + "/authz/resource-server" URL_ADMIN_CLIENT_AUTHZ_SETTINGS = URL_ADMIN_CLIENT_AUTHZ + "/settings" +URL_ADMIN_CLIENT_AUTHZ_IMPORT = URL_ADMIN_CLIENT_AUTHZ + "/import" URL_ADMIN_CLIENT_AUTHZ_RESOURCE = URL_ADMIN_CLIENT_AUTHZ + "/resource/{resource-id}" URL_ADMIN_CLIENT_AUTHZ_RESOURCES = URL_ADMIN_CLIENT_AUTHZ + "/resource" URL_ADMIN_CLIENT_AUTHZ_SCOPES = URL_ADMIN_CLIENT_AUTHZ + "/scope" diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index a90b6d5..445526c 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -1432,6 +1432,31 @@ def test_clients(admin: KeycloakAdmin, realm: str) -> None: UNKOWN_ERROR_REGEX, ) + # Test import authz + authz_config = admin.get_client_authz_settings(client_id=auth_client_id) + + authz_config["resources"] = [{"name": "test-import-resource"}] + authz_config["policies"] = [ + { + "name": "test-import-policy", + "type": "time", + "config": {"hourEnd": "18", "hour": "9"}, + } + ] + admin.import_client_authz_config(client_id=auth_client_id, payload=authz_config) + exported = admin.get_client_authz_settings(client_id=auth_client_id) + assert len([ + resource + for resource in exported["resources"] + if resource["name"] == "test-import-resource"] + ) == 1 + + assert len([ + resource + for resource in exported["policies"] + if resource["name"] == "test-import-policy"] + ) == 1 + # Test delete client res = admin.delete_client(client_id=auth_client_id) assert res == {}, res @@ -4998,6 +5023,31 @@ async def test_a_clients(admin: KeycloakAdmin, realm: str) -> None: UNKOWN_ERROR_REGEX, ) + # Test async import authz + authz_config = await admin.a_get_client_authz_settings(client_id=auth_client_id) + + authz_config["resources"] = [{"name": "test-import-resource"}] + authz_config["policies"] = [ + { + "name": "test-import-policy", + "type": "time", + "config": {"hourEnd": "18", "hour": "9"}, + } + ] + await admin.a_import_client_authz_config(client_id=auth_client_id, payload=authz_config) + exported = await admin.a_get_client_authz_settings(client_id=auth_client_id) + assert len([ + resource + for resource in exported["resources"] + if resource["name"] == "test-import-resource"] + ) == 1 + + assert len([ + resource + for resource in exported["policies"] + if resource["name"] == "test-import-policy"] + ) == 1 + # Test delete client res = await admin.a_delete_client(client_id=auth_client_id) assert res == {}, res