From 8fb6d43e073471ce01583760c8c8582062fad953 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Wed, 3 Apr 2024 17:23:29 -0300 Subject: [PATCH] Do not export ids when exporting authorization settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #25975 Co-authored-by: 박시준 Signed-off-by: Pedro Igor --- .../release_notes/topics/25_0_0.adoc | 7 +- .../models/utils/ModelToRepresentation.java | 15 ++- .../admin/ResourceServerManagementTest.java | 60 +++++++++ .../client-with-authz-settings.json | 117 ++++-------------- 4 files changed, 100 insertions(+), 99 deletions(-) diff --git a/docs/documentation/release_notes/topics/25_0_0.adoc b/docs/documentation/release_notes/topics/25_0_0.adoc index 622abd246e..627351b802 100644 --- a/docs/documentation/release_notes/topics/25_0_0.adoc +++ b/docs/documentation/release_notes/topics/25_0_0.adoc @@ -33,4 +33,9 @@ For users of the `keycloak-authz-client` library, calling `AuthorizationResource Previously, it would return a `List` at runtime, even though the method declaration advertised `List`. -This fix will break code that relied on casting the List or its contents to `List`. If you have used this method in any capacity, you are likely to have done this and be affected. \ No newline at end of file +This fix will break code that relied on casting the List or its contents to `List`. If you have used this method in any capacity, you are likely to have done this and be affected. + += IDs are no longer set when exporting authorization settings for a client + +When exporting the authorization settings for a client, the IDs for resources, scopes, and policies are no longer set. As a +result, you can now import the settings from a client to another client. diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 6424c818e2..2aef4765e6 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -1119,6 +1119,8 @@ public class ModelToRepresentation { .stream().map(resource -> { ResourceRepresentation rep = toRepresentation(resource, settingsModel, authorization); + rep.setId(null); + if (rep.getOwner().getId().equals(settingsModel.getClientId())) { rep.setOwner((ResourceOwnerRepresentation) null); } else { @@ -1139,16 +1141,25 @@ public class ModelToRepresentation { policies.addAll(policyStore.findByResourceServer(settingsModel) .stream().filter(policy -> !policy.getType().equals("resource") && !policy.getType().equals("scope") && policy.getOwner() == null) - .map(policy -> toRepresentation(authorization, policy)).collect(Collectors.toList())); + .map(policy -> { + PolicyRepresentation rep = toRepresentation(authorization, policy); + rep.setId(null); + return rep; + }).collect(Collectors.toList())); policies.addAll(policyStore.findByResourceServer(settingsModel) .stream().filter(policy -> (policy.getType().equals("resource") || policy.getType().equals("scope") && policy.getOwner() == null)) - .map(policy -> toRepresentation(authorization, policy)).collect(Collectors.toList())); + .map(policy -> { + PolicyRepresentation rep = toRepresentation(authorization, policy); + rep.setId(null); + return rep; + }).collect(Collectors.toList())); representation.setPolicies(policies); List scopes = storeFactory.getScopeStore().findByResourceServer(settingsModel).stream().map(scope -> { ScopeRepresentation rep = toRepresentation(scope); + rep.setId(null); rep.setPolicies(null); rep.setResources(null); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/ResourceServerManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/ResourceServerManagementTest.java index 4c0f7889e2..c9eb2b2207 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/ResourceServerManagementTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/ResourceServerManagementTest.java @@ -20,12 +20,19 @@ package org.keycloak.testsuite.authz.admin; import org.junit.Test; import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.ClientsResource; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.util.JsonSerialization; import java.util.List; +import java.util.Objects; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -96,4 +103,57 @@ public class ResourceServerManagementTest extends AbstractAuthorizationTest { // expected } } + + @Test + public void testImportSettingsToDifferentClient() throws Exception { + ClientsResource clientsResource = testRealmResource().clients(); + ClientRepresentation clientRep = JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/client-with-authz-settings.json"), ClientRepresentation.class); + clientRep.setClientId(KeycloakModelUtils.generateId()); + clientsResource.create(clientRep).close(); + List clients = clientsResource.findByClientId(clientRep.getClientId()); + assertFalse(clients.isEmpty()); + String clientId = clients.get(0).getId(); + AuthorizationResource authorization = clientsResource.get(clientId).authorization(); + ResourceServerRepresentation settings = authorization.exportSettings(); + assertEquals(PolicyEnforcementMode.PERMISSIVE, settings.getPolicyEnforcementMode()); + assertEquals(DecisionStrategy.UNANIMOUS, settings.getDecisionStrategy()); + assertFalse(authorization.resources().findByName("Resource 1").isEmpty()); + assertFalse(authorization.resources().findByName("Resource 15").isEmpty()); + assertFalse(authorization.resources().findByName("Resource 20").isEmpty()); + assertNotNull(authorization.permissions().resource().findByName("Resource 15 Permission")); + assertNotNull(authorization.policies().role().findByName("Resource 1 Policy")); + settings.getPolicies().removeIf(p -> "js".equals(p.getType())); + + ClientRepresentation anotherClientRep = ClientBuilder.create().clientId(KeycloakModelUtils.generateId()).secret("secret").authorizationServicesEnabled(true).serviceAccount().enabled(true).build(); + clientsResource.create(anotherClientRep).close(); + clients = clientsResource.findByClientId(anotherClientRep.getClientId()); + assertFalse(clients.isEmpty()); + ClientRepresentation anotherClient = clients.get(0); + authorization = clientsResource.get(anotherClient.getId()).authorization(); + authorization.importSettings(settings); + ResourceServerRepresentation anotherSettings = authorization.exportSettings(); + assertEquals(PolicyEnforcementMode.PERMISSIVE, anotherSettings.getPolicyEnforcementMode()); + assertEquals(DecisionStrategy.UNANIMOUS, anotherSettings.getDecisionStrategy()); + assertFalse(authorization.resources().findByName("Resource 1").isEmpty()); + assertFalse(authorization.resources().findByName("Resource 15").isEmpty()); + assertFalse(authorization.resources().findByName("Resource 20").isEmpty()); + assertNotNull(authorization.permissions().resource().findByName("Resource 15 Permission")); + assertNotNull(authorization.policies().role().findByName("Resource 1 Policy")); + } + + @Test + public void testExportSettings() throws Exception { + ClientsResource clientsResource = testRealmResource().clients(); + ClientRepresentation clientRep = JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/client-with-authz-settings.json"), ClientRepresentation.class); + clientRep.setClientId(KeycloakModelUtils.generateId()); + clientsResource.create(clientRep).close(); + List clients = clientsResource.findByClientId(clientRep.getClientId()); + assertFalse(clients.isEmpty()); + String clientId = clients.get(0).getId(); + AuthorizationResource authorization = clientsResource.get(clientId).authorization(); + ResourceServerRepresentation settings = authorization.exportSettings(); + assertFalse(settings.getResources().stream().map(ResourceRepresentation::getId).anyMatch(Objects::nonNull)); + assertFalse(settings.getScopes().stream().map(ScopeRepresentation::getId).anyMatch(Objects::nonNull)); + assertFalse(settings.getPolicies().stream().map(PolicyRepresentation::getId).anyMatch(Objects::nonNull)); + } } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/client-with-authz-settings.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/client-with-authz-settings.json index bbf0c1a6a7..dece731007 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/client-with-authz-settings.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/client-with-authz-settings.json @@ -447,202 +447,127 @@ "name": "Resource 1 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 2 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 3 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 4 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 5 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 6 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 7 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 8 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 9 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 10 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 11 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 12 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 13 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 14 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 15 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 16 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 17 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 18 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 19 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 20 Policy", "type": "role", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "roles": "[{\"id\":\"authz-client/uma_protection\",\"required\":false}]" - } - }, - { - "name": "Default Permission", - "description": "A permission that applies to the default resource type", - "type": "resource", - "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "defaultResourceType": "urn:authz-client:resources:default", - "applyPolicies": "[\"Default Policy\"]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 1 Permission", "type": "resource", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "resources": "[\"Resource 1\"]", - "applyPolicies": "[\"Resource 1 Policy\"]" - } + "decisionStrategy": "UNANIMOUS" }, { "name": "Resource 2 Permission",