From 421e395b2db4ceb7637d95e469c0022d74d65e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Sat, 25 Jul 2020 18:45:40 +0200 Subject: [PATCH] Add support for clients with fine-grained authorization --- CHANGELOG.md | 2 + contrib/example-config/moped.json | 221 +++++++++ pom.xml | 1 + .../config/repository/ClientRepository.java | 72 ++- .../repository/ScopeMappingRepository.java | 4 +- .../config/service/ClientImportService.java | 220 ++++++++- .../config/service/RealmImportService.java | 2 + .../keycloak/config/util/ResponseUtil.java | 1 + .../native-image/10.0.2/proxy-config.json | 28 ++ .../native-image/10.0.2/reflect-config.json | 28 ++ .../native-image/11.0.0/proxy-config.json | 28 ++ .../native-image/11.0.0/reflect-config.json | 28 ++ .../config/service/ImportClientsIT.java | 463 ++++++++++++++++++ .../10_update_realm__add_authorization.json | 246 ++++++++++ ...11_update_realm__update_authorization.json | 299 +++++++++++ ...12_update_realm__remove_authorization.json | 233 +++++++++ 16 files changed, 1865 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/import-files/clients/10_update_realm__add_authorization.json create mode 100644 src/test/resources/import-files/clients/11_update_realm__update_authorization.json create mode 100644 src/test/resources/import-files/clients/12_update_realm__remove_authorization.json diff --git a/CHANGELOG.md b/CHANGELOG.md index a59b0a5d2..68e98b9d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add support for clients with fine-grained authorization + ### Changed ### Fixed diff --git a/contrib/example-config/moped.json b/contrib/example-config/moped.json index aaa3f7c73..7e0f9f4f5 100644 --- a/contrib/example-config/moped.json +++ b/contrib/example-config/moped.json @@ -102,6 +102,18 @@ "description": "My second realm role", "composite": false, "clientRole": false + }, + { + "name": "user", + "description": "User privileges" + }, + { + "name": "admin", + "description": "Administrator privileges" + }, + { + "name": "user_premium", + "description": "User Premium privileges" } ], "client": { @@ -174,6 +186,215 @@ } ], "clients": [ + { + "clientId": "auth-moped-client", + "name": "auth-moped-client", + "description": "Auth-Moped-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "changed-special-client-secret", + "redirectUris": [ + "https://moped-client.org/redirect" + ], + "webOrigins": [ + "https://moped-client.org/webOrigin" + ], + "protocolMappers": [ + { + "name": "BranchCodeMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "false", + "user.attribute": "branch", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "branch", + "jsonType.label": "String" + } + } + ], + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "PERMISSIVE", + "decisionStrategy": "UNANIMOUS", + "resources": [ + { + "name": "Admin Resource", + "uri": "/protected/admin/*", + "type": "http://servlet-authz/protected/admin", + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access", + "iconUri": "https://www.keycloak.org/resources/favicon.ico" + } + ] + }, + { + "name": "Protected Resource", + "uris": [ + "/*" + ], + "type": "http://servlet-authz/protected/resource", + "scopes": [ + { + "name": "urn:servlet-authz:protected:resource:access" + } + ], + "attributes": {}, + "ownerManagedAccess": false + }, + { + "name": "Premium Resource", + "uri": "/protected/premium/*", + "type": "urn:servlet-authz:protected:resource", + "scopes": [ + { + "name": "urn:servlet-authz:protected:premium:access" + } + ] + }, + { + "name": "Main Page", + "type": "urn:servlet-authz:protected:resource", + "scopes": [ + { + "name": "urn:servlet-authz:page:main:actionForPremiumUser" + }, + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + }, + { + "name": "urn:servlet-authz:page:main:actionForUser" + } + ] + } + ], + "policies": [ + { + "name": "Any Admin Policy", + "description": "Defines that adminsitrators can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"admin\"}]" + } + }, + { + "name": "Any User Policy", + "description": "Defines that any user can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"user\"}]" + } + }, + { + "name": "Only Premium User Policy", + "description": "Defines that only premium users can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"user_premium\"}]" + } + }, + { + "name": "All Users Policy", + "description": "Defines that all users can do something", + "type": "aggregate", + "logic": "POSITIVE", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]" + } + }, + { + "name": "Administrative Resource Permission", + "description": "A policy that defines access to administrative resources", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Admin Resource\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Premium User Scope Permission", + "description": "A policy that defines access to a premium scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]", + "applyPolicies": "[\"Only Premium User Policy\"]" + } + }, + { + "name": "User Action Scope Permission", + "description": "A policy that defines access to a user scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForUser\"]", + "applyPolicies": "[\"Any User Policy\"]" + } + }, + { + "name": "Administrator Action Scope Permission", + "description": "A policy that defines access to an administrator scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Protected Resource Permission", + "description": "A policy that defines access to any protected resource", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Protected Resource\"]", + "applyPolicies": "[\"All Users Policy\"]" + } + } + ], + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access" + }, + { + "name": "urn:servlet-authz:protected:resource:access" + }, + { + "name": "urn:servlet-authz:protected:premium:access" + }, + { + "name": "urn:servlet-authz:page:main:actionForPremiumUser" + }, + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + }, + { + "name": "urn:servlet-authz:page:main:actionForUser", + "iconUri": "https://www.keycloak.org/resources/favicon.ico" + } + ] + } + }, { "clientId": "moped-client", "name": "moped-client", diff --git a/pom.xml b/pom.xml index ca43c5ae1..c475fa3fb 100644 --- a/pom.xml +++ b/pom.xml @@ -470,6 +470,7 @@ --initialize-at-run-time=org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver -H:+ReportExceptionStackTraces -Dspring.native.dump-config=feature-reflect-config.json + -Dspring.native.verify=true -Dspring.native.verbose=false -Dspring.native.remove-unused-autoconfig=true -Dspring.native.remove-jmx-support=true diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java index 0e512717a..3bf2b872f 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java @@ -29,6 +29,10 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; +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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -75,8 +79,12 @@ public ClientRepresentation getClientById(String realm, String id) { return loadClientById(realm, id).toRepresentation(); } + public ResourceServerRepresentation getAuthorizationConfigById(String realm, String id) { + return loadClientById(realm, id).authorization().exportSettings(); + } + public String getClientSecret(String realm, String clientId) { - ClientResource clientResource = getClientResource(realm, clientId); + ClientResource clientResource = getClientResourceByClientId(realm, clientId); return clientResource.getSecret().getValue(); } @@ -131,7 +139,7 @@ private ClientResource loadClientById(String realm, String id) { return client; } - final ClientResource getClientResource(String realm, String clientId) { + final ClientResource getClientResourceByClientId(String realm, String clientId) { ClientRepresentation client = loadClientByClientId(realm, clientId); return realmRepository.loadRealm(realm) .clients() @@ -175,8 +183,8 @@ public void removeProtocolMappers(String realm, String clientId, List protocolMappers) { - ClientResource clientResource = loadClientById(realm, clientId); + public void updateProtocolMappers(String realm, String id, List protocolMappers) { + ClientResource clientResource = loadClientById(realm, id); ProtocolMappersResource protocolMappersResource = clientResource.getProtocolMappers(); for (ProtocolMapperRepresentation protocolMapper : protocolMappers) { @@ -194,4 +202,60 @@ public void updateProtocolMappers(String realm, String clientId, List roles) { - ClientResource clientResource = clientRepository.getClientResource(realm, clientId); + ClientResource clientResource = clientRepository.getClientResourceByClientId(realm, clientId); RoleMappingResource scopeMappingsResource = clientResource.getScopeMappings(); RoleScopeResource roleScopeResource = scopeMappingsResource.realmLevel(); @@ -68,7 +68,7 @@ public void addScopeMappingRolesForClientScope(String realm, String clientScopeN } public void removeScopeMappingRolesForClient(String realm, String clientId, Collection roles) { - RoleMappingResource scopeMappingsResource = clientRepository.getClientResource(realm, clientId).getScopeMappings(); + RoleMappingResource scopeMappingsResource = clientRepository.getClientResourceByClientId(realm, clientId).getScopeMappings(); List realmRoles = roles.stream() .map(role -> roleRepository.findRealmRole(realm, role)) diff --git a/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java index eae22ba14..daf14bf60 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java @@ -29,14 +29,21 @@ import de.adorsys.keycloak.config.util.ResponseUtil; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; +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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Collectors; +import javax.ws.rs.NotFoundException; import javax.ws.rs.WebApplicationException; @Service @@ -63,6 +70,19 @@ public void doImport(RealmImport realmImport) { createOrUpdateClients(realmImport, clients); } + public void importAuthorizationSettings(RealmImport realmImport) { + List clients = realmImport.getClients(); + if (clients == null) { + return; + } + + List clientsWithAuthorization = clients.stream() + .filter(client -> client.getAuthorizationSettings() != null) + .collect(Collectors.toList()); + + updateClientAuthorizationSettings(realmImport, clientsWithAuthorization); + } + private void createOrUpdateClients(RealmImport realmImport, List clients) { Consumer loop = client -> createOrUpdateClient(realmImport, client); if (importConfigProperties.isParallel()) { @@ -82,12 +102,12 @@ private void createOrUpdateClient(RealmImport realmImport, ClientRepresentation updateClientIfNeeded(realm, client, maybeClient.get()); } else { logger.debug("Create client '{}' in realm '{}'", clientId, realm); - clientRepository.create(realm, client); + createClient(realm, client); } } private void updateClientIfNeeded(String realm, ClientRepresentation clientToUpdate, ClientRepresentation existingClient) { - ClientRepresentation patchedClient = CloneUtil.patch(existingClient, clientToUpdate, "id", "access"); + ClientRepresentation patchedClient = CloneUtil.patch(existingClient, clientToUpdate, "id", "access", "authorizationSettings"); if (!isClientEqual(realm, existingClient, patchedClient)) { logger.debug("Update client '{}' in realm '{}'", clientToUpdate.getClientId(), realm); @@ -97,8 +117,13 @@ private void updateClientIfNeeded(String realm, ClientRepresentation clientToUpd } } + private void createClient(String realm, ClientRepresentation client) { + ClientRepresentation clientToImport = CloneUtil.deepClone(client, ClientRepresentation.class, "authorizationSettings"); + clientRepository.create(realm, clientToImport); + } + private boolean isClientEqual(String realm, ClientRepresentation existingClient, ClientRepresentation patchedClient) { - if (!CloneUtil.deepEquals(existingClient, patchedClient, "id", "secret", "access", "protocolMappers")) { + if (!CloneUtil.deepEquals(existingClient, patchedClient, "id", "secret", "access", "authorizationSettings", "protocolMappers")) { return false; } @@ -124,9 +149,9 @@ private void updateClient(String realm, ClientRepresentation patchedClient) { } List protocolMappers = patchedClient.getProtocolMappers(); + if (protocolMappers != null) { - String clientId = patchedClient.getId(); - updateProtocolMappers(realm, clientId, protocolMappers); + updateProtocolMappers(realm, patchedClient.getId(), protocolMappers); } } @@ -143,4 +168,189 @@ private void updateProtocolMappers(String realm, String clientId, List clients) { + String realm = realmImport.getRealm(); + + for (ClientRepresentation client : clients) { + ClientRepresentation existingClient = clientRepository.getClientByClientId(realm, client.getClientId()); + updateAuthorization(realm, existingClient, client.getAuthorizationSettings()); + } + } + + private void updateAuthorization(String realm, ClientRepresentation client, ResourceServerRepresentation authorizationSettingsToImport) { + ResourceServerRepresentation existingAuthorization = clientRepository.getAuthorizationConfigById(realm, client.getId()); + + handleAuthorizationSettings(realm, client, existingAuthorization, authorizationSettingsToImport); + + createOrUpdateAuthorizationResources(realm, client, + existingAuthorization.getResources(), authorizationSettingsToImport.getResources()); + removeAuthorizationResources(realm, client, + existingAuthorization.getResources(), authorizationSettingsToImport.getResources()); + + createOrUpdateAuthorizationScopes(realm, client, + existingAuthorization.getScopes(), authorizationSettingsToImport.getScopes()); + removeAuthorizationScopes(realm, client, + existingAuthorization.getScopes(), authorizationSettingsToImport.getScopes()); + + createOrUpdateAuthorizationPolicies(realm, client, + existingAuthorization.getPolicies(), authorizationSettingsToImport.getPolicies()); + removeAuthorizationPolicies(realm, client, + existingAuthorization.getPolicies(), authorizationSettingsToImport.getPolicies()); + } + + private void handleAuthorizationSettings(String realm, ClientRepresentation client, ResourceServerRepresentation existingClientAuthorizationResources, ResourceServerRepresentation authorizationResourcesToImport) { + if (!CloneUtil.deepEquals(authorizationResourcesToImport, existingClientAuthorizationResources, "policies", "resources", "permissions", "scopes")) { + ResourceServerRepresentation patchedAuthorizationSettings = CloneUtil + .deepPatch(existingClientAuthorizationResources, authorizationResourcesToImport); + logger.debug("Update authorization settings for client '{}' in realm '{}'", client.getClientId(), realm); + clientRepository.updateAuthorizationSettings(realm, client.getId(), patchedAuthorizationSettings); + } + } + + private void createOrUpdateAuthorizationResources(String realm, ClientRepresentation client, List existingClientAuthorizationResources, List authorizationResourcesToImport) { + Map existingClientAuthorizationResourcesMap = existingClientAuthorizationResources + .stream() + .collect(Collectors.toMap(ResourceRepresentation::getName, resource -> resource)); + + for (ResourceRepresentation authorizationResourceToImport : authorizationResourcesToImport) { + createOrUpdateAuthorizationResource(realm, client, existingClientAuthorizationResourcesMap, authorizationResourceToImport); + } + } + + private void createOrUpdateAuthorizationResource(String realm, ClientRepresentation client, Map existingClientAuthorizationResourcesMap, ResourceRepresentation authorizationResourceToImport) { + if (!existingClientAuthorizationResourcesMap.containsKey(authorizationResourceToImport.getName())) { + logger.debug("Create authorization resource '{}' for client '{}' in realm '{}'", authorizationResourceToImport.getName(), client.getClientId(), realm); + clientRepository.createAuthorizationResource(realm, client.getId(), authorizationResourceToImport); + } else { + updateAuthorizationResource(realm, client, existingClientAuthorizationResourcesMap, authorizationResourceToImport); + } + } + + private void updateAuthorizationResource(String realm, ClientRepresentation client, Map existingClientAuthorizationResourcesMap, ResourceRepresentation authorizationResourceToImport) { + ResourceRepresentation existingClientAuthorizationResource = existingClientAuthorizationResourcesMap + .get(authorizationResourceToImport.getName()); + + if (!CloneUtil.deepEquals(authorizationResourceToImport, existingClientAuthorizationResource, "id", "_id")) { + authorizationResourceToImport.setId(existingClientAuthorizationResource.getId()); + logger.debug("Update authorization resource '{}' for client '{}' in realm '{}'", authorizationResourceToImport.getName(), client.getClientId(), realm); + clientRepository.updateAuthorizationResource(realm, client.getId(), authorizationResourceToImport); + } + } + + private void removeAuthorizationResources(String realm, ClientRepresentation client, List existingClientAuthorizationResources, List authorizationResourcesToImport) { + List authorizationResourceNamesToImport = authorizationResourcesToImport + .stream().map(ResourceRepresentation::getName) + .collect(Collectors.toList()); + + for (ResourceRepresentation existingClientAuthorizationResource : existingClientAuthorizationResources) { + if (!authorizationResourceNamesToImport.contains(existingClientAuthorizationResource.getName())) { + removeAuthorizationResource(realm, client, existingClientAuthorizationResource); + } + } + } + + private void removeAuthorizationResource(String realm, ClientRepresentation client, ResourceRepresentation existingClientAuthorizationResource) { + logger.debug("Remove authorization resource '{}' for client '{}' in realm '{}'", existingClientAuthorizationResource.getName(), client.getClientId(), realm); + clientRepository.removeAuthorizationResource(realm, client.getId(), existingClientAuthorizationResource.getId()); + } + + private void createOrUpdateAuthorizationScopes(String realm, ClientRepresentation client, List existingClientAuthorizationScopes, List authorizationScopesToImport) { + Map existingClientAuthorizationScopesMap = existingClientAuthorizationScopes + .stream() + .collect(Collectors.toMap(ScopeRepresentation::getName, scope -> scope)); + + for (ScopeRepresentation authorizationScopeToImport : authorizationScopesToImport) { + createOrUpdateAuthorizationScope(realm, client, existingClientAuthorizationScopesMap, authorizationScopeToImport); + } + } + + private void createOrUpdateAuthorizationScope(String realm, ClientRepresentation client, Map existingClientAuthorizationScopesMap, ScopeRepresentation authorizationScopeToImport) { + String authorizationScopeNameToImport = authorizationScopeToImport.getName(); + if (!existingClientAuthorizationScopesMap.containsKey(authorizationScopeToImport.getName())) { + logger.debug("Add authorization scope '{}' for client '{}' in realm '{}'", authorizationScopeNameToImport, client.getClientId(), realm); + clientRepository.addAuthorizationScope(realm, client.getId(), authorizationScopeNameToImport); + } else { + updateAuthorizationScope(realm, client, existingClientAuthorizationScopesMap, authorizationScopeToImport, authorizationScopeNameToImport); + } + } + + private void updateAuthorizationScope(String realm, ClientRepresentation client, Map existingClientAuthorizationScopesMap, ScopeRepresentation authorizationScopeToImport, String authorizationScopeNameToImport) { + ScopeRepresentation existingClientAuthorizationScope = existingClientAuthorizationScopesMap + .get(authorizationScopeNameToImport); + + if (!CloneUtil.deepEquals(authorizationScopeToImport, existingClientAuthorizationScope, "id")) { + authorizationScopeToImport.setId(existingClientAuthorizationScope.getId()); + logger.debug("Update authorization scope '{}' for client '{}' in realm '{}'", authorizationScopeNameToImport, client.getClientId(), realm); + clientRepository.updateAuthorizationScope(realm, client.getId(), authorizationScopeToImport); + } + } + + private void removeAuthorizationScopes(String realm, ClientRepresentation client, List existingClientAuthorizationScopes, List authorizationScopesToImport) { + List authorizationScopeNamesToImport = authorizationScopesToImport + .stream().map(ScopeRepresentation::getName) + .collect(Collectors.toList()); + + for (ScopeRepresentation existingClientAuthorizationScope : existingClientAuthorizationScopes) { + if (!authorizationScopeNamesToImport.contains(existingClientAuthorizationScope.getName())) { + removeAuthorizationScope(realm, client, existingClientAuthorizationScope); + } + } + } + + private void removeAuthorizationScope(String realm, ClientRepresentation client, ScopeRepresentation existingClientAuthorizationScope) { + logger.debug("Remove authorization scope '{}' for client '{}' in realm '{}'", existingClientAuthorizationScope.getName(), client.getClientId(), realm); + clientRepository.removeAuthorizationScope(realm, client.getId(), existingClientAuthorizationScope.getId()); + } + + private void createOrUpdateAuthorizationPolicies(String realm, ClientRepresentation client, List existingClientAuthorizationPolicies, List authorizationPoliciesToImport) { + Map existingClientAuthorizationPoliciesMap = existingClientAuthorizationPolicies + .stream() + .collect(Collectors.toMap(PolicyRepresentation::getName, resource -> resource)); + + for (PolicyRepresentation authorizationPolicyToImport : authorizationPoliciesToImport) { + createOrUpdateAuthorizationPolicy(realm, client, existingClientAuthorizationPoliciesMap, authorizationPolicyToImport); + } + } + + private void createOrUpdateAuthorizationPolicy(String realm, ClientRepresentation client, Map existingClientAuthorizationPoliciesMap, PolicyRepresentation authorizationPolicyToImport) { + if (!existingClientAuthorizationPoliciesMap.containsKey(authorizationPolicyToImport.getName())) { + logger.debug("Create authorization policy '{}' for client '{}' in realm '{}'", authorizationPolicyToImport.getName(), client.getClientId(), realm); + clientRepository.createAuthorizationPolicy(realm, client.getId(), authorizationPolicyToImport); + } else { + updateAuthorizationPolicy(realm, client, existingClientAuthorizationPoliciesMap, authorizationPolicyToImport); + } + } + + private void updateAuthorizationPolicy(String realm, ClientRepresentation client, Map existingClientAuthorizationPoliciesMap, PolicyRepresentation authorizationPolicyToImport) { + PolicyRepresentation existingClientAuthorizationPolicy = existingClientAuthorizationPoliciesMap + .get(authorizationPolicyToImport.getName()); + + if (!CloneUtil.deepEquals(authorizationPolicyToImport, existingClientAuthorizationPolicy, "id")) { + authorizationPolicyToImport.setId(existingClientAuthorizationPolicy.getId()); + logger.debug("Update authorization policy '{}' for client '{}' in realm '{}'", authorizationPolicyToImport.getName(), client.getClientId(), realm); + clientRepository.updateAuthorizationPolicy(realm, client.getId(), authorizationPolicyToImport); + } + } + + private void removeAuthorizationPolicies(String realm, ClientRepresentation client, List existingClientAuthorizationPolicies, List authorizationPoliciesToImport) { + List authorizationPolicyNamesToImport = authorizationPoliciesToImport + .stream().map(PolicyRepresentation::getName) + .collect(Collectors.toList()); + + for (PolicyRepresentation existingClientAuthorizationPolicy : existingClientAuthorizationPolicies) { + if (!authorizationPolicyNamesToImport.contains(existingClientAuthorizationPolicy.getName())) { + removeAuthorizationPolicy(realm, client, existingClientAuthorizationPolicy); + } + } + } + + private void removeAuthorizationPolicy(String realm, ClientRepresentation client, PolicyRepresentation existingClientAuthorizationPolicy) { + logger.debug("Remove authorization policy '{}' for client '{}' in realm '{}'", existingClientAuthorizationPolicy.getName(), client.getClientId(), realm); + try { + clientRepository.removeAuthorizationPolicy(realm, client.getId(), existingClientAuthorizationPolicy.getId()); + } catch (NotFoundException ignored) { + // policies got deleted if linked resources are deleted, too. + } + } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java index fa2c3dde1..c3214a885 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java @@ -165,6 +165,7 @@ private void createRealm(RealmImport realmImport) { stateService.loadState(realmImport); groupImportService.importGroups(realmImport); + clientImportService.importAuthorizationSettings(realmImport); userImportService.doImport(realmImport); authenticationFlowsImportService.doImport(realmImport); componentImportService.doImport(realmImport); @@ -187,6 +188,7 @@ private void updateRealm(RealmImport realmImport) { roleImportService.doImport(realmImport); groupImportService.importGroups(realmImport); clientScopeImportService.importClientScopes(realmImport); + clientImportService.importAuthorizationSettings(realmImport); userImportService.doImport(realmImport); requiredActionsImportService.doImport(realmImport); authenticationFlowsImportService.doImport(realmImport); diff --git a/src/main/java/de/adorsys/keycloak/config/util/ResponseUtil.java b/src/main/java/de/adorsys/keycloak/config/util/ResponseUtil.java index b1fc92c2c..e99caaa13 100644 --- a/src/main/java/de/adorsys/keycloak/config/util/ResponseUtil.java +++ b/src/main/java/de/adorsys/keycloak/config/util/ResponseUtil.java @@ -33,6 +33,7 @@ public static void validate(Response response) { if (!Family.familyOf(response.getStatus()).equals(Family.SUCCESSFUL)) { throw new WebApplicationException(response); } + response.close(); } public static String getErrorMessage(WebApplicationException error) { diff --git a/src/main/resources/META-INF/native-image/10.0.2/proxy-config.json b/src/main/resources/META-INF/native-image/10.0.2/proxy-config.json index d6e375ed5..3bd199a15 100644 --- a/src/main/resources/META-INF/native-image/10.0.2/proxy-config.json +++ b/src/main/resources/META-INF/native-image/10.0.2/proxy-config.json @@ -3,6 +3,10 @@ "org.keycloak.admin.client.resource.AuthenticationManagementResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" ], + [ + "org.keycloak.admin.client.resource.AuthorizationResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], [ "org.keycloak.admin.client.resource.ClientResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" @@ -43,6 +47,14 @@ "org.keycloak.admin.client.resource.IdentityProvidersResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" ], + [ + "org.keycloak.admin.client.resource.PoliciesResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.PolicyResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], [ "org.keycloak.admin.client.resource.ProtocolMappersResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" @@ -55,6 +67,22 @@ "org.keycloak.admin.client.resource.RealmsResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" ], + [ + "org.keycloak.admin.client.resource.ResourceResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.ResourceScopeResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.ResourceScopesResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.ResourcesResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], [ "org.keycloak.admin.client.resource.RoleMappingResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" diff --git a/src/main/resources/META-INF/native-image/10.0.2/reflect-config.json b/src/main/resources/META-INF/native-image/10.0.2/reflect-config.json index 19a31e1a6..e62106c63 100644 --- a/src/main/resources/META-INF/native-image/10.0.2/reflect-config.json +++ b/src/main/resources/META-INF/native-image/10.0.2/reflect-config.json @@ -62,6 +62,10 @@ "name": "org.keycloak.admin.client.resource.AuthenticationManagementResource", "allPublicMethods": true }, + { + "name": "org.keycloak.admin.client.resource.AuthorizationResource", + "allPublicMethods": true + }, { "name": "org.keycloak.admin.client.resource.BearerAuthFilter", "allDeclaredFields": true, @@ -111,6 +115,14 @@ "name": "org.keycloak.admin.client.resource.ProtocolMappersResource", "allPublicMethods": true }, + { + "name": "org.keycloak.admin.client.resource.PoliciesResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.PolicyResource", + "allPublicMethods": true + }, { "name": "org.keycloak.admin.client.resource.RealmResource", "allPublicMethods": true @@ -119,6 +131,22 @@ "name": "org.keycloak.admin.client.resource.RealmsResource", "allPublicMethods": true }, + { + "name": "org.keycloak.admin.client.resource.ResourceResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.ResourceScopeResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.ResourceScopesResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.ResourcesResource", + "allPublicMethods": true + }, { "name": "org.keycloak.admin.client.resource.RoleMappingResource", "allPublicMethods": true diff --git a/src/main/resources/META-INF/native-image/11.0.0/proxy-config.json b/src/main/resources/META-INF/native-image/11.0.0/proxy-config.json index d6e375ed5..3bd199a15 100644 --- a/src/main/resources/META-INF/native-image/11.0.0/proxy-config.json +++ b/src/main/resources/META-INF/native-image/11.0.0/proxy-config.json @@ -3,6 +3,10 @@ "org.keycloak.admin.client.resource.AuthenticationManagementResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" ], + [ + "org.keycloak.admin.client.resource.AuthorizationResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], [ "org.keycloak.admin.client.resource.ClientResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" @@ -43,6 +47,14 @@ "org.keycloak.admin.client.resource.IdentityProvidersResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" ], + [ + "org.keycloak.admin.client.resource.PoliciesResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.PolicyResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], [ "org.keycloak.admin.client.resource.ProtocolMappersResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" @@ -55,6 +67,22 @@ "org.keycloak.admin.client.resource.RealmsResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" ], + [ + "org.keycloak.admin.client.resource.ResourceResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.ResourceScopeResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.ResourceScopesResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], + [ + "org.keycloak.admin.client.resource.ResourcesResource", + "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" + ], [ "org.keycloak.admin.client.resource.RoleMappingResource", "org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy" diff --git a/src/main/resources/META-INF/native-image/11.0.0/reflect-config.json b/src/main/resources/META-INF/native-image/11.0.0/reflect-config.json index 19a31e1a6..e62106c63 100644 --- a/src/main/resources/META-INF/native-image/11.0.0/reflect-config.json +++ b/src/main/resources/META-INF/native-image/11.0.0/reflect-config.json @@ -62,6 +62,10 @@ "name": "org.keycloak.admin.client.resource.AuthenticationManagementResource", "allPublicMethods": true }, + { + "name": "org.keycloak.admin.client.resource.AuthorizationResource", + "allPublicMethods": true + }, { "name": "org.keycloak.admin.client.resource.BearerAuthFilter", "allDeclaredFields": true, @@ -111,6 +115,14 @@ "name": "org.keycloak.admin.client.resource.ProtocolMappersResource", "allPublicMethods": true }, + { + "name": "org.keycloak.admin.client.resource.PoliciesResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.PolicyResource", + "allPublicMethods": true + }, { "name": "org.keycloak.admin.client.resource.RealmResource", "allPublicMethods": true @@ -119,6 +131,22 @@ "name": "org.keycloak.admin.client.resource.RealmsResource", "allPublicMethods": true }, + { + "name": "org.keycloak.admin.client.resource.ResourceResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.ResourceScopeResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.ResourceScopesResource", + "allPublicMethods": true + }, + { + "name": "org.keycloak.admin.client.resource.ResourcesResource", + "allPublicMethods": true + }, { "name": "org.keycloak.admin.client.resource.RoleMappingResource", "allPublicMethods": true diff --git a/src/test/java/de/adorsys/keycloak/config/service/ImportClientsIT.java b/src/test/java/de/adorsys/keycloak/config/service/ImportClientsIT.java index 80a5b5e1c..cc537329f 100644 --- a/src/test/java/de/adorsys/keycloak/config/service/ImportClientsIT.java +++ b/src/test/java/de/adorsys/keycloak/config/service/ImportClientsIT.java @@ -20,6 +20,8 @@ package de.adorsys.keycloak.config.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import de.adorsys.keycloak.config.AbstractImportTest; import de.adorsys.keycloak.config.exception.ImportProcessingException; import de.adorsys.keycloak.config.model.RealmImport; @@ -29,7 +31,9 @@ import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.authorization.*; +import java.util.List; import java.util.Objects; import static org.hamcrest.MatcherAssert.assertThat; @@ -389,6 +393,439 @@ void shouldNotUpdateRealmCreateClientWithError() { assertThat(thrown.getMessage(), matchesPattern(".*Cannot create client 'new-client' in realm 'realmWithClients': .*")); } + @Test + @Order(10) + void shouldUpdateRealmAddAuthorization() { + doImport("10_update_realm__add_authorization.json"); + + RealmRepresentation realm = keycloakProvider.get().realm(REALM_NAME).toRepresentation(); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + ClientRepresentation client = getClientByName("auth-moped-client"); + assertThat(client.getName(), is("auth-moped-client")); + assertThat(client.getClientId(), is("auth-moped-client")); + assertThat(client.getDescription(), is("Auth-Moped-Client")); + assertThat(client.isEnabled(), is(true)); + assertThat(client.getClientAuthenticatorType(), is("client-secret")); + assertThat(client.getRedirectUris(), is(containsInAnyOrder("https://moped-client.org/redirect"))); + assertThat(client.getWebOrigins(), is(containsInAnyOrder("https://moped-client.org/webOrigin"))); + assertThat(client.isServiceAccountsEnabled(), is(true)); + + // ... and has to be retrieved separately + String clientSecret = getClientSecret(client.getId()); + assertThat(clientSecret, is("changed-special-client-secret")); + + ProtocolMapperRepresentation protocolMapper = client.getProtocolMappers().stream().filter(m -> Objects.equals(m.getName(), "BranchCodeMapper")).findFirst().orElse(null); + assertThat(protocolMapper, notNullValue()); + assertThat(protocolMapper.getProtocol(), is("openid-connect")); + assertThat(protocolMapper.getProtocolMapper(), is("oidc-usermodel-attribute-mapper")); + assertThat(protocolMapper.getConfig().get("aggregate.attrs"), is("false")); + assertThat(protocolMapper.getConfig().get("userinfo.token.claim"), is("true")); + assertThat(protocolMapper.getConfig().get("user.attribute"), is("branch")); + assertThat(protocolMapper.getConfig().get("multivalued"), is("false")); + assertThat(protocolMapper.getConfig().get("id.token.claim"), is("false")); + assertThat(protocolMapper.getConfig().get("access.token.claim"), is("true")); + assertThat(protocolMapper.getConfig().get("claim.name"), is("branch")); + assertThat(protocolMapper.getConfig().get("jsonType.label"), is("String")); + + ResourceServerRepresentation authorizationSettings = client.getAuthorizationSettings(); + assertThat(authorizationSettings.getPolicyEnforcementMode(), is(PolicyEnforcementMode.ENFORCING)); + assertThat(authorizationSettings.isAllowRemoteResourceManagement(), is(false)); + assertThat(authorizationSettings.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + + List authorizationSettingsResources = authorizationSettings.getResources(); + assertThat(authorizationSettingsResources, hasSize(3)); + + ResourceRepresentation authorizationSettingsResource; + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Admin Resource"); + assertThat(authorizationSettingsResource.getUris(), containsInAnyOrder("/protected/admin/*")); + assertThat(authorizationSettingsResource.getType(), is("http://servlet-authz/protected/admin")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder(new ScopeRepresentation("urn:servlet-authz:protected:admin:access"))); + + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Protected Resource"); + assertThat(authorizationSettingsResource.getUris(), containsInAnyOrder("/*")); + assertThat(authorizationSettingsResource.getType(), is("http://servlet-authz/protected/resource")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder(new ScopeRepresentation("urn:servlet-authz:protected:resource:access"))); + + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Main Page"); + assertThat(authorizationSettingsResource.getUris(), empty()); + assertThat(authorizationSettingsResource.getType(), is("urn:servlet-authz:protected:resource")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder( + new ScopeRepresentation("urn:servlet-authz:page:main:actionForAdmin"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForUser") + )); + + List authorizationSettingsPolicies = authorizationSettings.getPolicies(); + PolicyRepresentation authorizationSettingsPolicy; + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Any Admin Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that adminsitrators can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("role")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("roles"), equalTo("[{\"id\":\"admin\",\"required\":false}]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Any User Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that any user can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("role")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("roles"), equalTo("[{\"id\":\"user\",\"required\":false}]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "All Users Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that all users can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("aggregate")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.AFFIRMATIVE)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(readJson(authorizationSettingsPolicy.getConfig().get("applyPolicies")), containsInAnyOrder("Any Admin Policy", "Any User Policy")); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Administrative Resource Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to administrative resources")); + assertThat(authorizationSettingsPolicy.getType(), is("resource")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("resources"), equalTo("[\"Admin Resource\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any Admin Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "User Action Scope Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to a user scope")); + assertThat(authorizationSettingsPolicy.getType(), is("scope")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("scopes"), equalTo("[\"urn:servlet-authz:page:main:actionForUser\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any User Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Administrator Action Scope Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to an administrator scope")); + assertThat(authorizationSettingsPolicy.getType(), is("scope")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("scopes"), equalTo("[\"urn:servlet-authz:page:main:actionForAdmin\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any Admin Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Protected Resource Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to any protected resource")); + assertThat(authorizationSettingsPolicy.getType(), is("resource")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("resources"), equalTo("[\"Protected Resource\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"All Users Policy\"]"))); + + assertThat(authorizationSettings.getScopes(), hasSize(4)); + assertThat(authorizationSettings.getScopes(), containsInAnyOrder( + new ScopeRepresentation("urn:servlet-authz:protected:admin:access"), + new ScopeRepresentation("urn:servlet-authz:protected:resource:access"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForAdmin"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForUser") + )); + } + + @Test + @Order(11) + void shouldUpdateRealmUpdateAuthorization() { + doImport("11_update_realm__update_authorization.json"); + + RealmRepresentation realm = keycloakProvider.get().realm(REALM_NAME).toRepresentation(); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + ClientRepresentation client = getClientByName("auth-moped-client"); + assertThat(client.getName(), is("auth-moped-client")); + assertThat(client.getClientId(), is("auth-moped-client")); + assertThat(client.getDescription(), is("Auth-Moped-Client")); + assertThat(client.isEnabled(), is(true)); + assertThat(client.getClientAuthenticatorType(), is("client-secret")); + assertThat(client.getRedirectUris(), is(containsInAnyOrder("https://moped-client.org/redirect"))); + assertThat(client.getWebOrigins(), is(containsInAnyOrder("https://moped-client.org/webOrigin"))); + assertThat(client.isServiceAccountsEnabled(), is(true)); + + // ... and has to be retrieved separately + String clientSecret = getClientSecret(client.getId()); + assertThat(clientSecret, is("changed-special-client-secret")); + + ProtocolMapperRepresentation protocolMapper = client.getProtocolMappers().stream().filter(m -> Objects.equals(m.getName(), "BranchCodeMapper")).findFirst().orElse(null); + assertThat(protocolMapper, notNullValue()); + assertThat(protocolMapper.getProtocol(), is("openid-connect")); + assertThat(protocolMapper.getProtocolMapper(), is("oidc-usermodel-attribute-mapper")); + assertThat(protocolMapper.getConfig().get("aggregate.attrs"), is("false")); + assertThat(protocolMapper.getConfig().get("userinfo.token.claim"), is("true")); + assertThat(protocolMapper.getConfig().get("user.attribute"), is("branch")); + assertThat(protocolMapper.getConfig().get("multivalued"), is("false")); + assertThat(protocolMapper.getConfig().get("id.token.claim"), is("false")); + assertThat(protocolMapper.getConfig().get("access.token.claim"), is("true")); + assertThat(protocolMapper.getConfig().get("claim.name"), is("branch")); + assertThat(protocolMapper.getConfig().get("jsonType.label"), is("String")); + + ResourceServerRepresentation authorizationSettings = client.getAuthorizationSettings(); + assertThat(authorizationSettings.getPolicyEnforcementMode(), is(PolicyEnforcementMode.PERMISSIVE)); + assertThat(authorizationSettings.isAllowRemoteResourceManagement(), is(true)); + assertThat(authorizationSettings.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + + List authorizationSettingsResources = authorizationSettings.getResources(); + assertThat(authorizationSettingsResources, hasSize(4)); + + ResourceRepresentation authorizationSettingsResource; + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Admin Resource"); + assertThat(authorizationSettingsResource.getUris(), containsInAnyOrder("/protected/admin/*")); + assertThat(authorizationSettingsResource.getType(), is("http://servlet-authz/protected/admin")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder(new ScopeRepresentation("urn:servlet-authz:protected:admin:access", "https://www.keycloak.org/resources/favicon.ico"))); + + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Protected Resource"); + assertThat(authorizationSettingsResource.getUris(), containsInAnyOrder("/*")); + assertThat(authorizationSettingsResource.getType(), is("http://servlet-authz/protected/resource")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder(new ScopeRepresentation("urn:servlet-authz:protected:resource:access"))); + + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Premium Resource"); + assertThat(authorizationSettingsResource.getUris(), containsInAnyOrder("/protected/premium/*")); + assertThat(authorizationSettingsResource.getType(), is("urn:servlet-authz:protected:resource")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder(new ScopeRepresentation("urn:servlet-authz:protected:premium:access"))); + + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Main Page"); + assertThat(authorizationSettingsResource.getUris(), empty()); + assertThat(authorizationSettingsResource.getType(), is("urn:servlet-authz:protected:resource")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder( + new ScopeRepresentation("urn:servlet-authz:page:main:actionForPremiumUser"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForAdmin"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForUser") + )); + + List authorizationSettingsPolicies = authorizationSettings.getPolicies(); + PolicyRepresentation authorizationSettingsPolicy; + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Any Admin Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that adminsitrators can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("role")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("roles"), equalTo("[{\"id\":\"admin\",\"required\":false}]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Any User Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that any user can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("role")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("roles"), equalTo("[{\"id\":\"user\",\"required\":false}]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Only Premium User Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that only premium users can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("role")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("roles"), equalTo("[{\"id\":\"user_premium\",\"required\":false}]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "All Users Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that all users can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("aggregate")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.AFFIRMATIVE)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(readJson(authorizationSettingsPolicy.getConfig().get("applyPolicies")), containsInAnyOrder("Only Premium User Policy", "Any Admin Policy", "Any User Policy")); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Administrative Resource Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to administrative resources")); + assertThat(authorizationSettingsPolicy.getType(), is("resource")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("resources"), equalTo("[\"Admin Resource\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any Admin Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Premium User Scope Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to a premium scope")); + assertThat(authorizationSettingsPolicy.getType(), is("scope")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("scopes"), equalTo("[\"urn:servlet-authz:page:main:actionForPremiumUser\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Only Premium User Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "User Action Scope Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to a user scope")); + assertThat(authorizationSettingsPolicy.getType(), is("scope")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("scopes"), equalTo("[\"urn:servlet-authz:page:main:actionForUser\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any User Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Administrator Action Scope Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to an administrator scope")); + assertThat(authorizationSettingsPolicy.getType(), is("scope")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("scopes"), equalTo("[\"urn:servlet-authz:page:main:actionForAdmin\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any Admin Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Protected Resource Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to any protected resource")); + assertThat(authorizationSettingsPolicy.getType(), is("resource")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("resources"), equalTo("[\"Protected Resource\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"All Users Policy\"]"))); + + assertThat(authorizationSettings.getScopes(), hasSize(6)); + assertThat(authorizationSettings.getScopes(), containsInAnyOrder( + new ScopeRepresentation("urn:servlet-authz:protected:admin:access"), + new ScopeRepresentation("urn:servlet-authz:protected:resource:access"), + new ScopeRepresentation("urn:servlet-authz:protected:premium:access"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForPremiumUser"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForAdmin"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForUser", "https://www.keycloak.org/resources/favicon.ico") + )); + + ClientRepresentation mopedClient = getClientByName("moped-client"); + assertThat(mopedClient.isServiceAccountsEnabled(), is(true)); + + ResourceServerRepresentation mopedAuthorizationSettings = mopedClient.getAuthorizationSettings(); + assertThat(mopedAuthorizationSettings.getPolicyEnforcementMode(), is(PolicyEnforcementMode.PERMISSIVE)); + assertThat(mopedAuthorizationSettings.isAllowRemoteResourceManagement(), is(true)); + assertThat(mopedAuthorizationSettings.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + } + + @Test + @Order(12) + void shouldUpdateRealmRemoveAuthorization() { + doImport("12_update_realm__remove_authorization.json"); + + RealmRepresentation realm = keycloakProvider.get().realm(REALM_NAME).toRepresentation(); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + ClientRepresentation client = getClientByName("auth-moped-client"); + assertThat(client.getName(), is("auth-moped-client")); + assertThat(client.getClientId(), is("auth-moped-client")); + assertThat(client.getDescription(), is("Auth-Moped-Client")); + assertThat(client.isEnabled(), is(true)); + assertThat(client.getClientAuthenticatorType(), is("client-secret")); + assertThat(client.getRedirectUris(), is(containsInAnyOrder("https://moped-client.org/redirect"))); + assertThat(client.getWebOrigins(), is(containsInAnyOrder("https://moped-client.org/webOrigin"))); + assertThat(client.isServiceAccountsEnabled(), is(true)); + + // ... and has to be retrieved separately + String clientSecret = getClientSecret(client.getId()); + assertThat(clientSecret, is("changed-special-client-secret")); + + ProtocolMapperRepresentation protocolMapper = client.getProtocolMappers().stream().filter(m -> Objects.equals(m.getName(), "BranchCodeMapper")).findFirst().orElse(null); + assertThat(protocolMapper, notNullValue()); + assertThat(protocolMapper.getProtocol(), is("openid-connect")); + assertThat(protocolMapper.getProtocolMapper(), is("oidc-usermodel-attribute-mapper")); + assertThat(protocolMapper.getConfig().get("aggregate.attrs"), is("false")); + assertThat(protocolMapper.getConfig().get("userinfo.token.claim"), is("true")); + assertThat(protocolMapper.getConfig().get("user.attribute"), is("branch")); + assertThat(protocolMapper.getConfig().get("multivalued"), is("false")); + assertThat(protocolMapper.getConfig().get("id.token.claim"), is("false")); + assertThat(protocolMapper.getConfig().get("access.token.claim"), is("true")); + assertThat(protocolMapper.getConfig().get("claim.name"), is("branch")); + assertThat(protocolMapper.getConfig().get("jsonType.label"), is("String")); + + ResourceServerRepresentation authorizationSettings = client.getAuthorizationSettings(); + assertThat(authorizationSettings.getPolicyEnforcementMode(), is(PolicyEnforcementMode.PERMISSIVE)); + assertThat(authorizationSettings.isAllowRemoteResourceManagement(), is(true)); + assertThat(authorizationSettings.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + + List authorizationSettingsResources = authorizationSettings.getResources(); + assertThat(authorizationSettingsResources, hasSize(3)); + + ResourceRepresentation authorizationSettingsResource; + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Admin Resource"); + assertThat(authorizationSettingsResource.getUris(), containsInAnyOrder("/protected/admin/*")); + assertThat(authorizationSettingsResource.getType(), is("http://servlet-authz/protected/admin")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder(new ScopeRepresentation("urn:servlet-authz:protected:admin:access"))); + + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Premium Resource"); + assertThat(authorizationSettingsResource.getUris(), containsInAnyOrder("/protected/premium/*")); + assertThat(authorizationSettingsResource.getType(), is("urn:servlet-authz:protected:resource")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder(new ScopeRepresentation("urn:servlet-authz:protected:premium:access"))); + + authorizationSettingsResource = getAuthorizationSettingsResource(authorizationSettingsResources, "Main Page"); + assertThat(authorizationSettingsResource.getUris(), empty()); + assertThat(authorizationSettingsResource.getType(), is("urn:servlet-authz:protected:resource")); + assertThat(authorizationSettingsResource.getScopes(), containsInAnyOrder( + new ScopeRepresentation("urn:servlet-authz:page:main:actionForPremiumUser"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForAdmin") + )); + + List authorizationSettingsPolicies = authorizationSettings.getPolicies(); + PolicyRepresentation authorizationSettingsPolicy; + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Any Admin Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that adminsitrators can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("role")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("roles"), equalTo("[{\"id\":\"admin\",\"required\":false}]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Only Premium User Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that only premium users can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("role")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("roles"), equalTo("[{\"id\":\"user_premium\",\"required\":false}]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "All Users Policy"); + assertThat(authorizationSettingsPolicy.getDescription(), is("Defines that all users can do something")); + assertThat(authorizationSettingsPolicy.getType(), is("aggregate")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.AFFIRMATIVE)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(1)); + assertThat(readJson(authorizationSettingsPolicy.getConfig().get("applyPolicies")), containsInAnyOrder("Only Premium User Policy", "Any Admin Policy")); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Administrative Resource Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to administrative resources")); + assertThat(authorizationSettingsPolicy.getType(), is("resource")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("resources"), equalTo("[\"Admin Resource\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any Admin Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Premium User Scope Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to a premium scope")); + assertThat(authorizationSettingsPolicy.getType(), is("scope")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("scopes"), equalTo("[\"urn:servlet-authz:page:main:actionForPremiumUser\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Only Premium User Policy\"]"))); + + authorizationSettingsPolicy = getAuthorizationPolicy(authorizationSettingsPolicies, "Administrator Action Scope Permission"); + assertThat(authorizationSettingsPolicy.getDescription(), is("A policy that defines access to an administrator scope")); + assertThat(authorizationSettingsPolicy.getType(), is("scope")); + assertThat(authorizationSettingsPolicy.getLogic(), is(Logic.POSITIVE)); + assertThat(authorizationSettingsPolicy.getDecisionStrategy(), is(DecisionStrategy.UNANIMOUS)); + assertThat(authorizationSettingsPolicy.getConfig(), aMapWithSize(2)); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("scopes"), equalTo("[\"urn:servlet-authz:page:main:actionForAdmin\"]"))); + assertThat(authorizationSettingsPolicy.getConfig(), hasEntry(equalTo("applyPolicies"), equalTo("[\"Any Admin Policy\"]"))); + + assertThat(authorizationSettings.getScopes(), hasSize(4)); + assertThat(authorizationSettings.getScopes(), containsInAnyOrder( + new ScopeRepresentation("urn:servlet-authz:protected:admin:access"), + new ScopeRepresentation("urn:servlet-authz:protected:premium:access"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForPremiumUser"), + new ScopeRepresentation("urn:servlet-authz:page:main:actionForAdmin") + )); + + ClientRepresentation mopedClient = getClientByName("moped-client"); + assertThat(mopedClient.isServiceAccountsEnabled(), is(false)); + assertThat(mopedClient.getAuthorizationSettings(), nullValue()); + } + @Test @Order(96) void shouldUpdateRealmDeleteProtocolMapper() { @@ -548,4 +985,30 @@ private ClientRepresentation getClientByName(String clientName) { .findFirst() .orElse(null); } + + private ResourceRepresentation getAuthorizationSettingsResource(List authorizationSettings, String name) { + return authorizationSettings + .stream() + .filter(s -> Objects.equals(s.getName(), name)) + .findFirst() + .orElse(null); + } + + private PolicyRepresentation getAuthorizationPolicy(List authorizationSettings, String name) { + return authorizationSettings + .stream() + .filter(s -> Objects.equals(s.getName(), name)) + .findFirst() + .orElse(null); + } + + private List readJson(String jsonString) { + ObjectMapper objectMapper = new ObjectMapper(); + + try { + return objectMapper.readValue(jsonString, objectMapper.getTypeFactory().constructCollectionType(List.class, String.class)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/test/resources/import-files/clients/10_update_realm__add_authorization.json b/src/test/resources/import-files/clients/10_update_realm__add_authorization.json new file mode 100644 index 000000000..1c6616914 --- /dev/null +++ b/src/test/resources/import-files/clients/10_update_realm__add_authorization.json @@ -0,0 +1,246 @@ +{ + "enabled": true, + "realm": "realmWithClients", + "clients": [ + { + "clientId": "auth-moped-client", + "name": "auth-moped-client", + "description": "Auth-Moped-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "changed-special-client-secret", + "redirectUris": [ + "https://moped-client.org/redirect" + ], + "webOrigins": [ + "https://moped-client.org/webOrigin" + ], + "protocolMappers": [ + { + "name": "BranchCodeMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "false", + "user.attribute": "branch", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "branch", + "jsonType.label": "String" + } + } + ], + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "authorizationSettings": { + "allowRemoteResourceManagement": false, + "policyEnforcementMode": "ENFORCING", + "decisionStrategy": "UNANIMOUS", + "resources": [ + { + "name": "Admin Resource", + "uri": "/protected/admin/*", + "type": "http://servlet-authz/protected/admin", + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access" + } + ] + }, + { + "name": "Protected Resource", + "uris": [ + "/*" + ], + "type": "http://servlet-authz/protected/resource", + "scopes": [ + { + "name": "urn:servlet-authz:protected:resource:access" + } + ], + "attributes": {}, + "ownerManagedAccess": false + }, + { + "name": "Main Page", + "type": "urn:servlet-authz:protected:resource", + "scopes": [ + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + }, + { + "name": "urn:servlet-authz:page:main:actionForUser" + } + ] + } + ], + "policies": [ + { + "name": "Any Admin Policy", + "description": "Defines that adminsitrators can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"admin\"}]" + } + }, + { + "name": "Any User Policy", + "description": "Defines that any user can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"user\"}]" + } + }, + { + "name": "All Users Policy", + "description": "Defines that all users can do something", + "type": "aggregate", + "logic": "POSITIVE", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\"]" + } + }, + { + "name": "Administrative Resource Permission", + "description": "A policy that defines access to administrative resources", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Admin Resource\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "User Action Scope Permission", + "description": "A policy that defines access to a user scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForUser\"]", + "applyPolicies": "[\"Any User Policy\"]" + } + }, + { + "name": "Administrator Action Scope Permission", + "description": "A policy that defines access to an administrator scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Protected Resource Permission", + "description": "A policy that defines access to any protected resource", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Protected Resource\"]", + "applyPolicies": "[\"All Users Policy\"]" + } + } + ], + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access" + }, + { + "name": "urn:servlet-authz:protected:resource:access" + }, + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + }, + { + "name": "urn:servlet-authz:page:main:actionForUser" + } + ] + } + }, + { + "clientId": "moped-client", + "name": "moped-client", + "description": "Moped-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "changed-special-client-secret", + "redirectUris": [ + "https://moped-client.org/redirect" + ], + "webOrigins": [ + "https://moped-client.org/webOrigin" + ], + "protocolMappers": [ + { + "name": "BranchCodeMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "true", + "user.attribute": "branch", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "branch", + "jsonType.label": "String" + } + }, + { + "protocolMapper": "oidc-full-name-mapper", + "protocol": "openid-connect", + "name": "full name", + "config": { + "id.token.claim": "true", + "access.token.claim": "false" + } + } + ] + }, + { + "clientId": "another-client", + "description": "Another-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "my-other-client-secret", + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ] + } + ], + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges" + }, + { + "name": "admin", + "description": "Administrator privileges" + } + ] + }, + "users": [ + { + "username": "service-account-auth-moped-client", + "enabled": true, + "serviceAccountClientId": "auth-moped-client" + } + ] +} diff --git a/src/test/resources/import-files/clients/11_update_realm__update_authorization.json b/src/test/resources/import-files/clients/11_update_realm__update_authorization.json new file mode 100644 index 000000000..7aa808d11 --- /dev/null +++ b/src/test/resources/import-files/clients/11_update_realm__update_authorization.json @@ -0,0 +1,299 @@ +{ + "enabled": true, + "realm": "realmWithClients", + "clients": [ + { + "clientId": "auth-moped-client", + "name": "auth-moped-client", + "description": "Auth-Moped-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "changed-special-client-secret", + "redirectUris": [ + "https://moped-client.org/redirect" + ], + "webOrigins": [ + "https://moped-client.org/webOrigin" + ], + "protocolMappers": [ + { + "name": "BranchCodeMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "false", + "user.attribute": "branch", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "branch", + "jsonType.label": "String" + } + } + ], + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "PERMISSIVE", + "decisionStrategy": "UNANIMOUS", + "resources": [ + { + "name": "Admin Resource", + "uri": "/protected/admin/*", + "type": "http://servlet-authz/protected/admin", + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access", + "iconUri": "https://www.keycloak.org/resources/favicon.ico" + } + ] + }, + { + "name": "Protected Resource", + "uris": [ + "/*" + ], + "type": "http://servlet-authz/protected/resource", + "scopes": [ + { + "name": "urn:servlet-authz:protected:resource:access" + } + ], + "attributes": {}, + "ownerManagedAccess": false + }, + { + "name": "Premium Resource", + "uri": "/protected/premium/*", + "type": "urn:servlet-authz:protected:resource", + "scopes": [ + { + "name": "urn:servlet-authz:protected:premium:access" + } + ] + }, + { + "name": "Main Page", + "type": "urn:servlet-authz:protected:resource", + "scopes": [ + { + "name": "urn:servlet-authz:page:main:actionForPremiumUser" + }, + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + }, + { + "name": "urn:servlet-authz:page:main:actionForUser" + } + ] + } + ], + "policies": [ + { + "name": "Any Admin Policy", + "description": "Defines that adminsitrators can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"admin\"}]" + } + }, + { + "name": "Any User Policy", + "description": "Defines that any user can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"user\"}]" + } + }, + { + "name": "Only Premium User Policy", + "description": "Defines that only premium users can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"user_premium\"}]" + } + }, + { + "name": "All Users Policy", + "description": "Defines that all users can do something", + "type": "aggregate", + "logic": "POSITIVE", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]" + } + }, + { + "name": "Administrative Resource Permission", + "description": "A policy that defines access to administrative resources", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Admin Resource\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Premium User Scope Permission", + "description": "A policy that defines access to a premium scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]", + "applyPolicies": "[\"Only Premium User Policy\"]" + } + }, + { + "name": "User Action Scope Permission", + "description": "A policy that defines access to a user scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForUser\"]", + "applyPolicies": "[\"Any User Policy\"]" + } + }, + { + "name": "Administrator Action Scope Permission", + "description": "A policy that defines access to an administrator scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Protected Resource Permission", + "description": "A policy that defines access to any protected resource", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Protected Resource\"]", + "applyPolicies": "[\"All Users Policy\"]" + } + } + ], + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access" + }, + { + "name": "urn:servlet-authz:protected:resource:access" + }, + { + "name": "urn:servlet-authz:protected:premium:access" + }, + { + "name": "urn:servlet-authz:page:main:actionForPremiumUser" + }, + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + }, + { + "name": "urn:servlet-authz:page:main:actionForUser", + "iconUri": "https://www.keycloak.org/resources/favicon.ico" + } + ] + } + }, + { + "clientId": "moped-client", + "name": "moped-client", + "description": "Moped-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "changed-special-client-secret", + "redirectUris": [ + "https://moped-client.org/redirect" + ], + "webOrigins": [ + "https://moped-client.org/webOrigin" + ], + "protocolMappers": [ + { + "name": "BranchCodeMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "true", + "user.attribute": "branch", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "branch", + "jsonType.label": "String" + } + }, + { + "protocolMapper": "oidc-full-name-mapper", + "protocol": "openid-connect", + "name": "full name", + "config": { + "id.token.claim": "true", + "access.token.claim": "false" + } + } + ], + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "PERMISSIVE", + "decisionStrategy": "UNANIMOUS" + } + }, + { + "clientId": "another-client", + "description": "Another-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "my-other-client-secret", + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ] + } + ], + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges" + }, + { + "name": "admin", + "description": "Administrator privileges" + }, + { + "name": "user_premium", + "description": "User Premium privileges" + } + ] + }, + "users": [ + { + "username": "service-account-auth-moped-client", + "enabled": true, + "serviceAccountClientId": "auth-moped-client" + } + ] +} diff --git a/src/test/resources/import-files/clients/12_update_realm__remove_authorization.json b/src/test/resources/import-files/clients/12_update_realm__remove_authorization.json new file mode 100644 index 000000000..c861771f0 --- /dev/null +++ b/src/test/resources/import-files/clients/12_update_realm__remove_authorization.json @@ -0,0 +1,233 @@ +{ + "enabled": true, + "realm": "realmWithClients", + "clients": [ + { + "clientId": "auth-moped-client", + "name": "auth-moped-client", + "description": "Auth-Moped-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "changed-special-client-secret", + "redirectUris": [ + "https://moped-client.org/redirect" + ], + "webOrigins": [ + "https://moped-client.org/webOrigin" + ], + "protocolMappers": [ + { + "name": "BranchCodeMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "false", + "user.attribute": "branch", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "branch", + "jsonType.label": "String" + } + } + ], + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "PERMISSIVE", + "decisionStrategy": "UNANIMOUS", + "resources": [ + { + "name": "Admin Resource", + "uri": "/protected/admin/*", + "type": "http://servlet-authz/protected/admin", + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access" + } + ] + }, + { + "name": "Premium Resource", + "uri": "/protected/premium/*", + "type": "urn:servlet-authz:protected:resource", + "scopes": [ + { + "name": "urn:servlet-authz:protected:premium:access" + } + ] + }, + { + "name": "Main Page", + "type": "urn:servlet-authz:protected:resource", + "scopes": [ + { + "name": "urn:servlet-authz:page:main:actionForPremiumUser" + }, + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + } + ] + } + ], + "policies": [ + { + "name": "Any Admin Policy", + "description": "Defines that adminsitrators can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"admin\"}]" + } + }, + { + "name": "Only Premium User Policy", + "description": "Defines that only premium users can do something", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "roles": "[{\"id\":\"user_premium\"}]" + } + }, + { + "name": "All Users Policy", + "description": "Defines that all users can do something", + "type": "aggregate", + "logic": "POSITIVE", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "applyPolicies": "[\"Any Admin Policy\",\"Only Premium User Policy\"]" + } + }, + { + "name": "Administrative Resource Permission", + "description": "A policy that defines access to administrative resources", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Admin Resource\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Premium User Scope Permission", + "description": "A policy that defines access to a premium scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForPremiumUser\"]", + "applyPolicies": "[\"Only Premium User Policy\"]" + } + }, + { + "name": "Administrator Action Scope Permission", + "description": "A policy that defines access to an administrator scope", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "scopes": "[\"urn:servlet-authz:page:main:actionForAdmin\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + } + ], + "scopes": [ + { + "name": "urn:servlet-authz:protected:admin:access" + }, + { + "name": "urn:servlet-authz:protected:premium:access" + }, + { + "name": "urn:servlet-authz:page:main:actionForPremiumUser" + }, + { + "name": "urn:servlet-authz:page:main:actionForAdmin" + } + ] + } + }, + { + "clientId": "moped-client", + "name": "moped-client", + "description": "Moped-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "changed-special-client-secret", + "redirectUris": [ + "https://moped-client.org/redirect" + ], + "webOrigins": [ + "https://moped-client.org/webOrigin" + ], + "protocolMappers": [ + { + "name": "BranchCodeMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "aggregate.attrs": "false", + "userinfo.token.claim": "true", + "multivalued": "true", + "user.attribute": "branch", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "branch", + "jsonType.label": "String" + } + }, + { + "protocolMapper": "oidc-full-name-mapper", + "protocol": "openid-connect", + "name": "full name", + "config": { + "id.token.claim": "true", + "access.token.claim": "false" + } + } + ], + "serviceAccountsEnabled": false, + "authorizationServicesEnabled": false + }, + { + "clientId": "another-client", + "description": "Another-Client", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "my-other-client-secret", + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ] + } + ], + "roles": { + "realm": [ + { + "name": "admin", + "description": "Administrator privileges" + }, + { + "name": "user_premium", + "description": "User Premium privileges" + } + ] + }, + "users": [ + { + "username": "service-account-auth-moped-client", + "enabled": true, + "serviceAccountClientId": "auth-moped-client" + } + ] +}