diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 7e6e626..17b8cad 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -532,6 +532,29 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) + def partial_import_realm(self, realm_name, payload): + """Partial import realm configuration from PartialImportRepresentation. + + Realm partialImport is used for modifying configuration of existing realm. + + PartialImportRepresentation + https://www.keycloak.org/docs-api/18.0/rest-api/#_partialimportrepresentation + + :param realm_name: Realm name (not the realm id) + :type realm_name: str + :param payload: PartialImportRepresentation + :type payload: dict + + :return: PartialImportResponse + :rtype: dict + """ + params_path = {"realm-name": realm_name} + data_raw = self.connection.raw_post( + urls_patterns.URL_ADMIN_REALM_PARTIAL_IMPORT.format(**params_path), + data=json.dumps(payload), + ) + return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200]) + def export_realm(self, export_clients=False, export_groups_and_role=False): """Export the realm configurations in the json format. diff --git a/src/keycloak/urls_patterns.py b/src/keycloak/urls_patterns.py index 94186a8..b2d013d 100644 --- a/src/keycloak/urls_patterns.py +++ b/src/keycloak/urls_patterns.py @@ -163,6 +163,8 @@ URL_ADMIN_REALM_EXPORT = ( + "exportGroupsAndRoles={export-groups-and-roles}" ) +URL_ADMIN_REALM_PARTIAL_IMPORT = "admin/realms/{realm-name}/partialImport" + URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES = URL_ADMIN_REALM + "/default-default-client-scopes" URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE = URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES + "/{id}" URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES = URL_ADMIN_REALM + "/default-optional-client-scopes" diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index f4e3f47..5814462 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -216,6 +216,53 @@ def test_import_export_realms(admin: KeycloakAdmin, realm: str): assert err.match('500: b\'{"error":"unknown_error"}\'') +def test_partial_import_realm(admin: KeycloakAdmin, realm: str): + """Test partial import of realm configuration. + + :param admin: Keycloak Admin client + :type admin: KeycloakAdmin + :param realm: Keycloak realm + :type realm: str + """ + test_realm_role = str(uuid.uuid4()) + test_user = str(uuid.uuid4()) + test_client = str(uuid.uuid4()) + + admin.realm_name = realm + client_id = admin.create_client(payload={"name": test_client, "clientId": test_client}) + + realm_export = admin.export_realm(export_clients=True, export_groups_and_role=False) + + client_config = [ + client_entry for client_entry in realm_export["clients"] if client_entry["id"] == client_id + ][0] + + # delete before partial import + admin.delete_client(client_id) + + payload = { + "ifResourceExists": "SKIP", + "id": realm_export["id"], + "realm": realm, + "clients": [client_config], + "roles": {"realm": [{"name": test_realm_role}]}, + "users": [{"username": test_user, "email": f"{test_user}@test.test"}], + } + + # check add + res = admin.partial_import_realm(realm_name=realm, payload=payload) + assert res["added"] == 3 + + # check skip + res = admin.partial_import_realm(realm_name=realm, payload=payload) + assert res["skipped"] == 3 + + # check overwrite + payload["ifResourceExists"] = "OVERWRITE" + res = admin.partial_import_realm(realm_name=realm, payload=payload) + assert res["overwritten"] == 3 + + def test_users(admin: KeycloakAdmin, realm: str): """Test users.