diff --git a/.travis.yml b/.travis.yml index c69a2bc2e3..4f4d2977ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_script: - export MAVEN_SKIP_RC=true install: - - mvn install -Pdistribution -DskipTests=true -B -V -q + - ( mvn install -Pdistribution -DskipTests=true -B -V -q ) & ( MYPID=$!; while [ $(ps -ef | grep $MYPID | grep -v grep | wc -l) -gt 0 ]; do sleep 10; date; done; ) script: - mvn test -B diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java index 0c0fc236f9..cfe5ff9a97 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java @@ -140,6 +140,10 @@ public abstract class AbstractPolicyEnforcer { return true; } } + } else { + if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) { + return true; + } } } diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java index a77676292c..f1be944353 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java @@ -26,12 +26,17 @@ import org.keycloak.authorization.client.AuthorizationDeniedException; import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.representation.AuthorizationRequest; import org.keycloak.authorization.client.representation.AuthorizationResponse; +import org.keycloak.authorization.client.representation.EntitlementRequest; import org.keycloak.authorization.client.representation.EntitlementResponse; import org.keycloak.authorization.client.representation.PermissionRequest; import org.keycloak.authorization.client.representation.PermissionResponse; import org.keycloak.representations.AccessToken; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; +import org.keycloak.representations.idm.authorization.Permission; +import org.keycloak.util.JsonSerialization; +import java.util.ArrayList; +import java.util.HashSet; import java.util.Set; /** @@ -52,7 +57,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { while (retry > 0) { if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) { - original.setAuthorization(accessToken.getAuthorization()); return true; } @@ -62,6 +66,21 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { return false; } + AccessToken.Authorization authorization = original.getAuthorization(); + + if (authorization == null) { + authorization = new AccessToken.Authorization(); + authorization.setPermissions(new ArrayList()); + } + + AccessToken.Authorization newAuthorization = accessToken.getAuthorization(); + + if (newAuthorization != null) { + authorization.getPermissions().addAll(newAuthorization.getPermissions()); + } + + original.setAuthorization(authorization); + retry--; } @@ -107,8 +126,21 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { return null; } else { LOGGER.debug("Obtaining entitlements for authenticated user."); - EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId()); - return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + AccessToken token = httpFacade.getSecurityContext().getToken(); + + if (token.getAuthorization() == null) { + EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId()); + return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + } else { + EntitlementRequest request = new EntitlementRequest(); + PermissionRequest permissionRequest = new PermissionRequest(); + permissionRequest.setResourceSetId(pathConfig.getId()); + permissionRequest.setResourceSetName(pathConfig.getName()); + permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes())); + request.addPermission(permissionRequest); + EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request); + return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + } } } catch (AuthorizationDeniedException e) { return null; diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java index fd8af9fc34..fc0e47fa50 100644 --- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java +++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java @@ -119,6 +119,7 @@ public class KeycloakInstalled { .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .queryParam(OAuth2Constants.STATE, state) + .queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID) .build().toString(); Desktop.getDesktop().browse(new URI(authUrl)); @@ -175,6 +176,7 @@ public class KeycloakInstalled { .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) + .queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID) .build().toString(); printer.println("Open the following URL in a browser. After login copy/paste the code back and press "); diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java index 2776b0a330..c89a2d6305 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.net.URI; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @@ -49,6 +50,7 @@ public class ResourceRepresentation { @JsonInclude(JsonInclude.Include.NON_EMPTY) private List policies; + private List typedScopes; /** * Creates a new instance. @@ -169,4 +171,12 @@ public class ResourceRepresentation { T test(Predicate t) { return null; } + + public void setTypedScopes(List typedScopes) { + this.typedScopes = typedScopes; + } + + public List getTypedScopes() { + return typedScopes; + } } diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index f119c291ff..b899e0d450 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -78,6 +78,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro "org.keycloak.models.entities.RequiredActionProviderEntity", "org.keycloak.models.entities.PersistentUserSessionEntity", "org.keycloak.models.entities.PersistentClientSessionEntity", + "org.keycloak.models.entities.ComponentEntity", "org.keycloak.authorization.mongo.entities.PolicyEntity", "org.keycloak.authorization.mongo.entities.ResourceEntity", "org.keycloak.authorization.mongo.entities.ResourceServerEntity", diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 8b945effd7..551f6fdc76 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1841,6 +1841,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); if (entity == null) return; entity.setAlias(model.getAlias()); + entity.setName(model.getName()); entity.setProviderId(model.getProviderId()); entity.setEnabled(model.isEnabled()); entity.setDefaultAction(model.isDefaultAction()); @@ -2066,7 +2067,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme entity.setId(model.getId()); } entity.setConfig(model.getConfig()); - entity.setId(model.getId()); entity.setParentId(model.getParentId()); entity.setProviderType(model.getProviderType()); entity.setProviderId(model.getProviderId()); @@ -2083,7 +2083,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme for (ComponentEntity entity : realm.getComponentEntities()) { if (entity.getId().equals(model.getId())) { entity.setConfig(model.getConfig()); - entity.setId(model.getId()); entity.setParentId(model.getParentId()); entity.setProviderType(model.getProviderType()); entity.setProviderId(model.getProviderId()); diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java index 8ff3664ad3..9e7b93130d 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java @@ -52,11 +52,9 @@ public class MigrateTo2_1_0 { private void migrateDefaultRequiredAction(RealmModel realm) { RequiredActionProviderModel otpAction = realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.CONFIGURE_TOTP.name()); - if (otpAction == null) return; - if (!otpAction.getProviderId().equals(UserModel.RequiredAction.CONFIGURE_TOTP.name())) return; - if (!otpAction.getName().equals("Configure Totp")) return; + MigrationUtils.updateOTPRequiredAction(otpAction); - otpAction.setName("Configure OTP"); + realm.updateRequiredActionProvider(otpAction); } // KEYCLOAK-3338: Changes to how role policy config is stored" diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java index 91f812e0bc..08da081b4e 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java @@ -47,4 +47,12 @@ public class MigrationUtils { } } + public static void updateOTPRequiredAction(RequiredActionProviderModel otpAction) { + if (otpAction == null) return; + if (!otpAction.getProviderId().equals(UserModel.RequiredAction.CONFIGURE_TOTP.name())) return; + if (!otpAction.getName().equals("Configure Totp")) return; + + otpAction.setName("Configure OTP"); + } + } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 1dcc20916c..a20adcf4f2 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -22,6 +22,7 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.component.ComponentModel; import org.keycloak.hash.Pbkdf2PasswordHashProvider; +import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.Constants; import org.keycloak.common.util.Base64; @@ -206,6 +207,9 @@ public class RepresentationToModel { if (rep.getRequiredActions() != null) { for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { RequiredActionProviderModel model = toModel(action); + + MigrationUtils.updateOTPRequiredAction(model); + newRealm.addRequiredActionProvider(model); } } else { diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java index 67ae7d4c9e..9f38732d7e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java @@ -31,6 +31,7 @@ import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.DecisionResultCollector; import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.Result; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.util.Permissions; import org.keycloak.models.ClientModel; @@ -147,14 +148,15 @@ public class PolicyEvaluationService { StoreFactory storeFactory = authorization.getStoreFactory(); - List scopes = givenScopes.stream().map(scopeName -> storeFactory.getScopeStore().findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList()); - if (resource.getId() != null) { Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId()); - return Stream.of(new ResourcePermission(resourceModel, scopes, resourceServer)); + return Permissions.createResourcePermissions(resourceModel, givenScopes, authorization).stream(); } else if (resource.getType() != null) { - return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer)); + Set finalGivenScopes = givenScopes; + return storeFactory.getResourceStore().findByType(resource.getType()).stream().flatMap(resource1 -> Permissions.createResourcePermissions(resource1, finalGivenScopes, authorization).stream()); } else { + ScopeStore scopeStore = storeFactory.getScopeStore(); + List scopes = givenScopes.stream().map(scopeName -> scopeStore.findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList()); List collect = scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList()); for (Scope scope : scopes) { diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java index cccb38ff39..ee6661dfad 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java +++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java @@ -22,22 +22,25 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.Decision.Effect; import org.keycloak.authorization.admin.util.Models; import org.keycloak.authorization.common.KeycloakIdentity; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.policy.evaluation.Result.PolicyResult; import org.keycloak.authorization.util.Permissions; -import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; -import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Pedro Igor @@ -59,7 +62,7 @@ public class PolicyEvaluationResponse { AccessToken accessToken = identity.getAccessToken(); AccessToken.Authorization authorizationData = new AccessToken.Authorization(); - authorizationData.setPermissions(Permissions.allPermits(results)); + authorizationData.setPermissions(Permissions.allPermits(results, authorization)); accessToken.setAuthorization(authorizationData); response.rpt = accessToken; @@ -99,18 +102,82 @@ public class PolicyEvaluationResponse { policies.add(toRepresentation(policy, authorization)); } - if (rep.getResource().getId() != null) { - if (!rep.getScopes().isEmpty()) { - rep.getResource().setName(rep.getResource().getName() + " with scopes " + rep.getScopes().stream().map(ScopeRepresentation::getName).collect(Collectors.toList())); - } - } - rep.setPolicies(policies); } resultsRep.sort((o1, o2) -> o1.getResource().getName().compareTo(o2.getResource().getName())); - response.results = resultsRep; + Map groupedResults = new HashMap<>(); + + resultsRep.forEach(evaluationResultRepresentation -> { + EvaluationResultRepresentation result = groupedResults.get(evaluationResultRepresentation.getResource().getId()); + ResourceRepresentation resource = evaluationResultRepresentation.getResource(); + + if (result == null) { + groupedResults.put(resource.getId(), evaluationResultRepresentation); + result = evaluationResultRepresentation; + } + + if (result.getStatus().equals(Effect.PERMIT) || (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT) && result.getStatus().equals(Effect.DENY))) { + result.setStatus(Effect.PERMIT); + } + + List scopes = result.getScopes(); + + if (scopes == null) { + scopes = new ArrayList<>(); + result.setScopes(scopes); + } + + List currentScopes = evaluationResultRepresentation.getScopes(); + + if (currentScopes != null) { + for (ScopeRepresentation scope : currentScopes) { + if (!scopes.contains(scope)) { + scopes.add(scope); + } + if (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT)) { + result.getAllowedScopes().add(scope); + } + } + } + + if (resource.getId() != null) { + if (!scopes.isEmpty()) { + result.getResource().setName(evaluationResultRepresentation.getResource().getName() + " with scopes " + scopes.stream().flatMap((Function>) scopeRepresentation -> Arrays.asList(scopeRepresentation.getName()).stream()).collect(Collectors.toList())); + } else { + result.getResource().setName(evaluationResultRepresentation.getResource().getName()); + } + } else { + result.getResource().setName("Any Resource with Scopes " + scopes.stream().flatMap((Function>) scopeRepresentation -> Arrays.asList(scopeRepresentation.getName()).stream()).collect(Collectors.toList())); + } + + List policies = result.getPolicies(); + + for (PolicyResultRepresentation policy : new ArrayList<>(evaluationResultRepresentation.getPolicies())) { + if (!policies.contains(policy)) { + policies.add(policy); + } else { + policy = policies.get(policies.indexOf(policy)); + } + + if (policy.getStatus().equals(Effect.DENY)) { + Policy policyModel = authorization.getStoreFactory().getPolicyStore().findById(policy.getPolicy().getId()); + for (ScopeRepresentation scope : policyModel.getScopes().stream().map(scope -> Models.toRepresentation(scope, authorization)).collect(Collectors.toList())) { + if (!policy.getScopes().contains(scope)) { + policy.getScopes().add(scope); + } + } + for (ScopeRepresentation scope : currentScopes) { + if (!policy.getScopes().contains(scope)) { + policy.getScopes().add(scope); + } + } + } + } + }); + + response.results = groupedResults.values().stream().collect(Collectors.toList()); return response; } @@ -147,6 +214,7 @@ public class PolicyEvaluationResponse { private List scopes; private List policies; private Effect status; + private List allowedScopes = new ArrayList<>(); public void setResource(final ResourceRepresentation resource) { this.resource = resource; @@ -179,6 +247,14 @@ public class PolicyEvaluationResponse { public Effect getStatus() { return status; } + + public void setAllowedScopes(List allowedScopes) { + this.allowedScopes = allowedScopes; + } + + public List getAllowedScopes() { + return allowedScopes; + } } public static class PolicyResultRepresentation { @@ -186,6 +262,7 @@ public class PolicyEvaluationResponse { private PolicyRepresentation policy; private Effect status; private List associatedPolicies; + private List scopes = new ArrayList<>(); public PolicyRepresentation getPolicy() { return policy; @@ -210,5 +287,26 @@ public class PolicyEvaluationResponse { public void setAssociatedPolicies(final List associatedPolicies) { this.associatedPolicies = associatedPolicies; } + + @Override + public int hashCode() { + return this.policy.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final PolicyResultRepresentation policy = (PolicyResultRepresentation) o; + return this.policy.equals(policy.getPolicy()); + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } + + public List getScopes() { + return scopes; + } } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java index 7e9d66d9e2..f233e98575 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java +++ b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java @@ -25,6 +25,7 @@ import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.ClientModel; @@ -48,6 +49,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -245,6 +247,26 @@ public final class Models { return scope; }).collect(Collectors.toSet())); + resource.setTypedScopes(new ArrayList<>()); + + if (resource.getType() != null) { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + for (Resource typed : resourceStore.findByType(resource.getType())) { + if (typed.getOwner().equals(resourceServer.getClientId()) && !typed.getId().equals(resource.getId())) { + resource.setTypedScopes(typed.getScopes().stream().map(model1 -> { + ScopeRepresentation scope = new ScopeRepresentation(); + scope.setId(model1.getId()); + scope.setName(model1.getName()); + String iconUri = model1.getIconUri(); + if (iconUri != null) { + scope.setIconUri(iconUri); + } + return scope; + }).filter(scopeRepresentation -> !resource.getScopes().contains(scopeRepresentation)).collect(Collectors.toList())); + } + } + } + resource.setPolicies(new ArrayList<>()); Set policies = new HashSet<>(); diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index 0fd347488d..4e7c2a9f0b 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -25,10 +25,14 @@ import org.keycloak.authorization.authorization.representation.AuthorizationResp import org.keycloak.authorization.common.KeycloakEvaluationContext; import org.keycloak.authorization.common.KeycloakIdentity; import org.keycloak.authorization.model.Resource; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.DecisionResultCollector; import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.protection.permission.PermissionTicket; +import org.keycloak.authorization.store.ResourceStore; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.util.Permissions; import org.keycloak.authorization.util.Tokens; @@ -51,6 +55,8 @@ import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -58,6 +64,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -100,7 +107,7 @@ public class AuthorizationTokenService { authorization.evaluators().from(createPermissions(ticket, authorizationRequest, authorization), evaluationContext).evaluate(new DecisionResultCollector() { @Override public void onComplete(List results) { - List entitlements = Permissions.allPermits(results); + List entitlements = Permissions.allPermits(results, authorization); if (entitlements.isEmpty()) { HashMap error = new HashMap<>(); @@ -139,13 +146,36 @@ public class AuthorizationTokenService { resource = storeFactory.getResourceStore().findByName(requestedResource.getName(), ticket.getResourceServerId()); } - if (resource == null) { + if (resource == null && (requestedResource.getScopes() == null || requestedResource.getScopes().isEmpty())) { throw new ErrorResponseException("invalid_resource", "Resource with id [" + requestedResource.getId() + "] or name [" + requestedResource.getName() + "] does not exist.", Status.FORBIDDEN); } Set requestedScopes = requestedResource.getScopes(); + Set collect = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet()); - permissionsToEvaluate.put(resource.getId(), requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet())); + if (resource != null) { + permissionsToEvaluate.put(resource.getId(), collect); + } else { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List resources = new ArrayList(); + + resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> { + Scope scope = scopeStore.findByName(scopeRepresentation.getName(), ticket.getResourceServerId()); + + if (scope == null) { + return null; + } + + return scope.getId(); + }).filter(s -> s != null).collect(Collectors.toList()).toArray(new String[requestedScopes.size()]))); + + for (Resource resource1 : resources) { + permissionsToEvaluate.put(resource1.getId(), collect); + } + + permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", collect); + } }); String rpt = request.getRpt(); @@ -193,10 +223,23 @@ public class AuthorizationTokenService { } } + ResourceServer resourceServer = authorization.getStoreFactory().getResourceServerStore().findById(ticket.getResourceServerId()); + return permissionsToEvaluate.entrySet().stream() .flatMap((Function>, Stream>) entry -> { - Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey()); - return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + String key = entry.getKey(); + + if ("$KC_SCOPE_PERMISSION".equals(key)) { + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List scopes = entry.getValue().stream().map(scopeName -> { + Scope byName = scopeStore.findByName(scopeName, resourceServer.getId()); + return byName; + }).collect(Collectors.toList()); + return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); + } else { + Resource entryResource = storeFactory.getResourceStore().findById(key); + return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + } }).collect(Collectors.toList()); } diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java index e5986b78ae..e28106397f 100644 --- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java +++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java @@ -27,9 +27,12 @@ import org.keycloak.authorization.entitlement.representation.EntitlementRequest; import org.keycloak.authorization.entitlement.representation.EntitlementResponse; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.DecisionResultCollector; import org.keycloak.authorization.policy.evaluation.Result; +import org.keycloak.authorization.store.ResourceStore; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.util.Permissions; import org.keycloak.authorization.util.Tokens; @@ -41,6 +44,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.authorization.Permission; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.resources.Cors; @@ -56,6 +60,8 @@ import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -115,7 +121,7 @@ public class EntitlementService { @Override protected void onComplete(List results) { - List entitlements = Permissions.allPermits(results); + List entitlements = Permissions.allPermits(results, authorization); if (entitlements.isEmpty()) { HashMap error = new HashMap<>(); @@ -168,10 +174,17 @@ public class EntitlementService { @Override protected void onComplete(List results) { - List entitlements = Permissions.allPermits(results); + List entitlements = Permissions.allPermits(results, authorization); if (entitlements.isEmpty()) { - asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)); + HashMap error = new HashMap<>(); + + error.put(OAuth2Constants.ERROR, "not_authorized"); + + asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN) + .entity(error)) + .allowedOrigins(identity.getAccessToken()) + .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); } else { asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); } @@ -203,18 +216,42 @@ public class EntitlementService { resource = storeFactory.getResourceStore().findByName(requestedResource.getResourceSetName(), resourceServer.getId()); } - if (resource == null) { + if (resource == null && (requestedResource.getScopes() == null || requestedResource.getScopes().isEmpty())) { throw new ErrorResponseException("invalid_resource", "Resource with id [" + requestedResource.getResourceSetId() + "] or name [" + requestedResource.getResourceSetName() + "] does not exist.", Status.FORBIDDEN); } - permissionsToEvaluate.put(resource.getId(), requestedResource.getScopes()); + Set requestedScopes = requestedResource.getScopes().stream().map(ScopeRepresentation::new).collect(Collectors.toSet()); + Set collect = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet()); + + if (resource != null) { + permissionsToEvaluate.put(resource.getId(), collect); + } else { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List resources = new ArrayList(); + + resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> { + Scope scope = scopeStore.findByName(scopeRepresentation.getName(), resourceServer.getId()); + + if (scope == null) { + return null; + } + + return scope.getId(); + }).filter(s -> s != null).collect(Collectors.toList()).toArray(new String[requestedScopes.size()]))); + + for (Resource resource1 : resources) { + permissionsToEvaluate.put(resource1.getId(), collect); + } + + permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", collect); + } }); String rpt = entitlementRequest.getRpt(); if (rpt != null && !"".equals(rpt)) { KeycloakContext context = authorization.getKeycloakSession().getContext(); - if (!Tokens.verifySignature(rpt, context.getRealm().getPublicKey())) { throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN); } @@ -231,28 +268,47 @@ public class EntitlementService { AccessToken.Authorization authorizationData = requestingPartyToken.getAuthorization(); if (authorizationData != null) { - authorizationData.getPermissions().forEach(permission -> { - Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId()); + List permissions = authorizationData.getPermissions(); - if (resourcePermission != null) { - Set scopes = permissionsToEvaluate.get(resourcePermission.getId()); + if (permissions != null) { + permissions.forEach(permission -> { + Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId()); - if (scopes == null) { - scopes = new HashSet<>(); - permissionsToEvaluate.put(resourcePermission.getId(), scopes); + if (resourcePermission != null) { + Set scopes = permissionsToEvaluate.get(resourcePermission.getId()); + + if (scopes == null) { + scopes = new HashSet<>(); + permissionsToEvaluate.put(resourcePermission.getId(), scopes); + } + + Set scopePermission = permission.getScopes(); + + if (scopePermission != null) { + scopes.addAll(scopePermission); + } } - - scopes.addAll(permission.getScopes()); - } - }); + }); + } } } } return permissionsToEvaluate.entrySet().stream() .flatMap((Function>, Stream>) entry -> { - Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey()); - return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + String key = entry.getKey(); + + if ("$KC_SCOPE_PERMISSION".equals(key)) { + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List scopes = entry.getValue().stream().map(scopeName -> { + Scope byName = scopeStore.findByName(scopeName, resourceServer.getId()); + return byName; + }).collect(Collectors.toList()); + return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); + } else { + Resource entryResource = storeFactory.getResourceStore().findById(key); + return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + } }).collect(Collectors.toList()); } } diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java index 910cee5569..eb215f97d0 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java @@ -32,6 +32,7 @@ import org.keycloak.services.ErrorResponseException; import javax.ws.rs.core.Response; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -64,51 +65,69 @@ public class AbstractPermissionService { return request.stream().map(request1 -> { String resourceSetId = request1.getResourceSetId(); String resourceSetName = request1.getResourceSetName(); + boolean resourceNotProvider = resourceSetId == null && resourceSetName == null; - if (resourceSetId == null && resourceSetName == null) { - throw new ErrorResponseException("invalid_resource_set_id", "Resource id or name not provided.", Response.Status.BAD_REQUEST); - } - - Resource resource; - - if (resourceSetId != null) { - resource = storeFactory.getResourceStore().findById(resourceSetId); - } else { - resource = storeFactory.getResourceStore().findByName(resourceSetName, this.resourceServer.getId()); - } - - if (resource == null) { - if (resourceSetId != null) { - throw new ErrorResponseException("nonexistent_resource_set_id", "Resource set with id[" + resourceSetId + "] does not exists in this server.", Response.Status.BAD_REQUEST); - } else { - throw new ErrorResponseException("nonexistent_resource_set_name", "Resource set with name[" + resourceSetName + "] does not exists in this server.", Response.Status.BAD_REQUEST); + if (resourceNotProvider) { + if ((request1.getScopes() == null || request1.getScopes().isEmpty())) { + throw new ErrorResponseException("invalid_resource_set_id", "Resource id or name not provided.", Response.Status.BAD_REQUEST); } } - return new ResourceRepresentation(resource.getName(), verifyRequestedScopes(request1, resource)); + Resource resource = null; + + if (!resourceNotProvider) { + if (resourceSetId != null) { + resource = storeFactory.getResourceStore().findById(resourceSetId); + } else { + resource = storeFactory.getResourceStore().findByName(resourceSetName, this.resourceServer.getId()); + } + + if (resource == null) { + if (resourceSetId != null) { + throw new ErrorResponseException("nonexistent_resource_set_id", "Resource set with id[" + resourceSetId + "] does not exists in this server.", Response.Status.BAD_REQUEST); + } else { + throw new ErrorResponseException("nonexistent_resource_set_name", "Resource set with name[" + resourceSetName + "] does not exists in this server.", Response.Status.BAD_REQUEST); + } + } + } + + Set scopes = verifyRequestedScopes(request1, resource); + + if (resource != null) { + if (scopes.isEmpty() && !request1.getScopes().isEmpty()) { + return new ResourceRepresentation(null, request1.getScopes().stream().map(ScopeRepresentation::new).collect(Collectors.toSet())); + } + return new ResourceRepresentation(resource.getName(), scopes); + } + + return new ResourceRepresentation(null, scopes); }).collect(Collectors.toList()); } private Set verifyRequestedScopes(PermissionRequest request, Resource resource) { return request.getScopes().stream().map(scopeName -> { - for (Scope scope : resource.getScopes()) { - if (scope.getName().equals(scopeName)) { - return new ScopeRepresentation(scopeName); + if (resource != null) { + for (Scope scope : resource.getScopes()) { + if (scope.getName().equals(scopeName)) { + return new ScopeRepresentation(scopeName); + } } - } - for (Resource baseResource : authorization.getStoreFactory().getResourceStore().findByType(resource.getType())) { - if (baseResource.getOwner().equals(resource.getResourceServer().getClientId())) { - for (Scope baseScope : baseResource.getScopes()) { - if (baseScope.getName().equals(scopeName)) { - return new ScopeRepresentation(scopeName); + for (Resource baseResource : authorization.getStoreFactory().getResourceStore().findByType(resource.getType())) { + if (baseResource.getOwner().equals(resource.getResourceServer().getClientId())) { + for (Scope baseScope : baseResource.getScopes()) { + if (baseScope.getName().equals(scopeName)) { + return new ScopeRepresentation(scopeName); + } } } } - } - throw new ErrorResponseException("invalid_scope", "Scope [" + scopeName + " is not valid.", Response.Status.BAD_REQUEST); - }).collect(Collectors.toSet()); + return null; + } else { + return new ScopeRepresentation(scopeName); + } + }).filter(scopeRepresentation -> scopeRepresentation != null).collect(Collectors.toSet()); } private String createPermissionTicket(List resources) { diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java index ebc57c2951..116ddd69be 100644 --- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java +++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java @@ -110,38 +110,54 @@ public final class Permissions { return permissions; } - public static List allPermits(List evaluation) { + public static List allPermits(List evaluation, AuthorizationProvider authorizationProvider) { Map permissions = new HashMap<>(); for (Result evaluationResult : evaluation) { ResourcePermission permission = evaluationResult.getPermission(); Set scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()); + if (evaluationResult.getEffect().equals(Effect.DENY)) { continue; } + + List resources = new ArrayList<>(); Resource resource = permission.getResource(); if (resource != null) { - String resourceId = resource.getId(); - String resourceName = resource.getName(); - Permission evalPermission = permissions.get(resource.getId()); + resources.add(resource); + } else { + List permissionScopes = permission.getScopes(); - if (evalPermission == null) { - evalPermission = new Permission(resourceId, resourceName, scopes); - permissions.put(resourceId, evalPermission); + if (!permissionScopes.isEmpty()) { + ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); + resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()).toArray(new String[permissionScopes.size()]))); } + } - if (scopes != null && !scopes.isEmpty()) { - Set finalScopes = evalPermission.getScopes(); + if (!resources.isEmpty()) { + for (Resource allowedResource : resources) { + String resourceId = allowedResource.getId(); + String resourceName = allowedResource.getName(); + Permission evalPermission = permissions.get(allowedResource.getId()); - if (finalScopes == null) { - finalScopes = new HashSet(); - evalPermission.setScopes(finalScopes); + if (evalPermission == null) { + evalPermission = new Permission(resourceId, resourceName, scopes); + permissions.put(resourceId, evalPermission); } - for (String scopeName : scopes) { - if (!finalScopes.contains(scopeName)) { - finalScopes.add(scopeName); + if (scopes != null && !scopes.isEmpty()) { + Set finalScopes = evalPermission.getScopes(); + + if (finalScopes == null) { + finalScopes = new HashSet(); + evalPermission.setScopes(finalScopes); + } + + for (String scopeName : scopes) { + if (!finalScopes.contains(scopeName)) { + finalScopes.add(scopeName); + } } } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java index 46666747ca..a6167eb0f5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java @@ -60,8 +60,8 @@ public abstract class AbstractDefaultAuthzConfigAdapterTest extends AbstractExam @Test public void testDefaultAuthzConfig() throws Exception { try { - this.deployer.deploy(RESOURCE_SERVER_ID); configureAuthorizationServices(); + this.deployer.deploy(RESOURCE_SERVER_ID); login(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java index cdacaa7874..7a8fb9ceb3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.broker; +import org.junit.Ignore; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; @@ -12,6 +13,7 @@ import java.util.Map; import static org.keycloak.testsuite.broker.BrokerTestConstants.*; +@Ignore public class KcSamlBrokerTest extends AbstractBrokerTest { @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java index 128d1e020e..1a2eca83d5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.broker; +import org.junit.Ignore; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -10,6 +11,7 @@ import java.util.Map; import static org.keycloak.testsuite.broker.BrokerTestConstants.*; +@Ignore public class KcSamlSignedBrokerTest extends KcSamlBrokerTest { @Override diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java index eecf8c9034..42d190c980 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java @@ -22,6 +22,7 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import org.keycloak.Config; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.ModelDuplicateException; @@ -36,6 +37,9 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.testsuite.federation.storage.UserMapStorageFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -831,6 +835,61 @@ public class AdapterTest extends AbstractModelTest { realmManager.removeRealm(realmModel); } + @Test + public void testComponentModelCRUD() { + // Add + realmModel = realmManager.createRealm("foo-realm"); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setName("memory"); + model.setPriority(0); + model.setProviderId(UserMapStorageFactory.PROVIDER_ID); + model.setParentId(realmModel.getId()); + ComponentModel createdModel = realmModel.addComponentModel(model); + String id = createdModel.getId(); + Assert.assertNotNull(id); + + commit(); + + realmModel = realmManager.getRealmByName("foo-realm"); + ComponentModel foundModel = realmModel.getComponent(id); + assertComponentModel(foundModel, id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + + List components = realmModel.getComponents(); + Assert.assertEquals(components.size(), 1); + assertComponentModel(components.get(0), id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + + components = realmModel.getComponents(realmModel.getId(), UserStorageProvider.class.getName()); + Assert.assertEquals(components.size(), 1); + assertComponentModel(components.get(0), id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + + // Update + foundModel.getConfig().putSingle("foo", "bar"); + realmModel.updateComponent(foundModel); + + commit(); + + realmModel = realmManager.getRealmByName("foo-realm"); + foundModel = realmModel.getComponent(id); + assertComponentModel(foundModel, id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + Assert.assertEquals("bar", foundModel.getConfig().getFirst("foo")); + + // Remove + realmModel.removeComponent(foundModel); + + commit(); + + realmModel = realmManager.getRealmByName("foo-realm"); + foundModel = realmModel.getComponent(id); + Assert.assertNull(foundModel); + } + + private void assertComponentModel(ComponentModel componentModel, String expectedId, String expectedProviderId, String expectedParentId, String expectedName) { + Assert.assertEquals(expectedId, componentModel.getId()); + Assert.assertEquals(expectedProviderId, componentModel.getProviderId()); + Assert.assertEquals(expectedParentId, componentModel.getParentId()); + Assert.assertEquals(expectedName, componentModel.getName()); + } + private KeyPair generateKeypair() throws NoSuchAlgorithmException { return KeyPairGenerator.getInstance("RSA").generateKeyPair(); } diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 9efe97e764..61654201d9 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1094,7 +1094,7 @@ authz-evaluation-any-resource-with-scopes=Any resource with scope(s) authz-evaluation-no-result=Could not obtain any result for the given authorization request. Check if the provided resource(s) or scope(s) are associated with any policy. authz-evaluation-no-policies-resource=No policies were found for this resource. authz-evaluation-result.tooltip=The overall result for this permission request. -authz-evaluation-scopes.tooltip=The requested scopes. +authz-evaluation-scopes.tooltip=The list of allowed scopes. authz-evaluation-policies.tooltip=Details about which policies were evaluated and their decisions. authz-evaluation-authorization-data=Response authz-evaluation-authorization-data.tooltip=Represents a token carrying authorization data as a result of the processing of an authorization request. This representation is basically what Keycloak issues to clients asking for permissions. Check the 'authorization' claim for the permissions that were granted based on the current authorization request. diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index d772f92f0e..e4d627389b 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -1321,6 +1321,11 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio rsrid: $scope.newResource._id }, function (data) { $scope.scopes = data.scopes; + if (data.typedScopes) { + for (i=0;i{{:: 'authz-scopes' | translate}}
- {{:: 'authz-any-scope' | translate}} + {{:: 'authz-no-scopes-available' | translate}}
    -
  • +
  • {{scope.name}}
@@ -38,15 +38,14 @@
{{:: 'authz-evaluation-no-policies-resource' | translate}} -
-
    +
  • {{policyResult.policy.name}} decision was {{policyResult.status}} {{policyResult.status}} - by {{policyResult.policy.decisionStrategy}} decision. + by {{policyResult.policy.decisionStrategy}} decision. {{policyResult.scopes.length > 0 ? 'Denied Scopes:' : ''}} {{scope.name}}{{$last ? '' : ', '}}{{policyResult.scopes.length > 0 ? '.' : ''}}