diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/DecisionEffect.java b/core/src/main/java/org/keycloak/representations/idm/authorization/DecisionEffect.java new file mode 100644 index 0000000000..cc13a08ca1 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/DecisionEffect.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm.authorization; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public enum DecisionEffect { + PERMIT, + DENY +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationRequest.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationRequest.java similarity index 74% rename from services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationRequest.java rename to core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationRequest.java index da7b4204c2..268cf37519 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationRequest.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationRequest.java @@ -16,8 +16,10 @@ * limitations under the License. */ -package org.keycloak.authorization.admin.representation; +package org.keycloak.representations.idm.authorization; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -29,11 +31,11 @@ import org.keycloak.representations.idm.authorization.ResourceRepresentation; */ public class PolicyEvaluationRequest { - private Map> context; - private List resources; + private Map> context = new HashMap<>(); + private List resources = new LinkedList<>(); private String clientId; private String userId; - private List roleIds; + private List roleIds = new LinkedList<>(); private boolean entitlements; public Map> getContext() { @@ -44,11 +46,11 @@ public class PolicyEvaluationRequest { this.context = context; } - public List getResources() { + public List getResources() { return this.resources; } - public void setResources(List resources) { + public void setResources(List resources) { this.resources = resources; } @@ -84,7 +86,13 @@ public class PolicyEvaluationRequest { this.entitlements = entitlements; } - public static class Resource extends ResourceRepresentation { - + public PolicyEvaluationRequest addResource(String name, String... scopes) { + if (resources == null) { + resources = new LinkedList<>(); + } + resources.add(new ResourceRepresentation(name, scopes)); + return this; } + + } diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java new file mode 100644 index 0000000000..45657713a4 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java @@ -0,0 +1,173 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.representations.idm.authorization; + +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.authorization.DecisionEffect; +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.List; + +/** + * @author Pedro Igor + */ +public class PolicyEvaluationResponse { + + private List results; + private boolean entitlements; + private DecisionEffect status; + private AccessToken rpt; + + public List getResults() { + return results; + } + + public DecisionEffect getStatus() { + return status; + } + + public boolean isEntitlements() { + return entitlements; + } + + public AccessToken getRpt() { + return rpt; + } + + public void setResults(List results) { + this.results = results; + } + + public void setEntitlements(boolean entitlements) { + this.entitlements = entitlements; + } + + public void setStatus(DecisionEffect status) { + this.status = status; + } + + public void setRpt(AccessToken rpt) { + this.rpt = rpt; + } + + public static class EvaluationResultRepresentation { + + private ResourceRepresentation resource; + private List scopes; + private List policies; + private DecisionEffect status; + private List allowedScopes = new ArrayList<>(); + + public void setResource(final ResourceRepresentation resource) { + this.resource = resource; + } + + public ResourceRepresentation getResource() { + return resource; + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } + + public List getScopes() { + return scopes; + } + + public void setPolicies(final List policies) { + this.policies = policies; + } + + public List getPolicies() { + return policies; + } + + public void setStatus(final DecisionEffect status) { + this.status = status; + } + + public DecisionEffect getStatus() { + return status; + } + + public void setAllowedScopes(List allowedScopes) { + this.allowedScopes = allowedScopes; + } + + public List getAllowedScopes() { + return allowedScopes; + } + } + + public static class PolicyResultRepresentation { + + private PolicyRepresentation policy; + private DecisionEffect status; + private List associatedPolicies; + private List scopes = new ArrayList<>(); + + public PolicyRepresentation getPolicy() { + return policy; + } + + public void setPolicy(final PolicyRepresentation policy) { + this.policy = policy; + } + + public DecisionEffect getStatus() { + return status; + } + + public void setStatus(final DecisionEffect status) { + this.status = status; + } + + public List getAssociatedPolicies() { + return associatedPolicies; + } + + 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/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java index 86f6f98bed..d3c2e4620c 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 @@ -18,6 +18,8 @@ package org.keycloak.representations.idm.authorization; import java.net.URI; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Set; @@ -91,6 +93,15 @@ public class ResourceRepresentation { this(name, scopes, null, null, null); } + public ResourceRepresentation(String name, String... scopes) { + this.name = name; + this.scopes = new HashSet<>(); + for (String s : scopes) { + ScopeRepresentation rep = new ScopeRepresentation(s); + this.scopes.add(rep); + } + } + /** * Creates a new instance. * diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java index fd5d43a9ca..e5120c621c 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java @@ -17,6 +17,8 @@ package org.keycloak.admin.client.resource; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest; +import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse; import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; @@ -53,4 +55,11 @@ public interface PoliciesResource { @Produces(MediaType.APPLICATION_JSON) @NoCache List policyProviders(); + + @POST + @Consumes("application/json") + @Produces("application/json") + @Path("evaluate") + PolicyEvaluationResponse evaluate(PolicyEvaluationRequest evaluationRequest); + } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java index 3d57c07a02..b5fa96d568 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java @@ -36,11 +36,15 @@ public interface UsersResource { @GET @Produces(MediaType.APPLICATION_JSON) List search(@QueryParam("username") String username, - @QueryParam("firstName") String firstName, - @QueryParam("lastName") String lastName, - @QueryParam("email") String email, - @QueryParam("first") Integer firstResult, - @QueryParam("max") Integer maxResults); + @QueryParam("firstName") String firstName, + @QueryParam("lastName") String lastName, + @QueryParam("email") String email, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResults); + + @GET + @Produces(MediaType.APPLICATION_JSON) + List search(@QueryParam("username") String username); @GET @Produces(MediaType.APPLICATION_JSON) 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 b096d24d4d..6a2f522318 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java @@ -17,12 +17,12 @@ */ package org.keycloak.authorization.admin; -import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,15 +33,13 @@ import java.util.stream.Stream; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Produces; -import javax.ws.rs.container.AsyncResponse; -import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.admin.representation.PolicyEvaluationRequest; -import org.keycloak.authorization.admin.representation.PolicyEvaluationResponse; +import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest; +import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder; import org.keycloak.authorization.attribute.Attributes; import org.keycloak.authorization.common.KeycloakEvaluationContext; import org.keycloak.authorization.common.KeycloakIdentity; @@ -58,15 +56,13 @@ import org.keycloak.authorization.util.Permissions; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; -import org.keycloak.protocol.ProtocolMapper; -import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; +import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.Urls; import org.keycloak.services.resources.admin.RealmAuth; @@ -89,32 +85,41 @@ public class PolicyEvaluationService { this.auth = auth; } + static class Decision extends DecisionResultCollector { + Throwable error; + List results; + + @Override + protected void onComplete(List results) { + this.results = results; + } + + @Override + public void onError(Throwable cause) { + this.error = cause; + + } + } + + public static List asList(T... a) { + List list = new LinkedList(); + for (T t : a) list.add(t); + return list; + } + @POST @Consumes("application/json") @Produces("application/json") - public void evaluate(PolicyEvaluationRequest evaluationRequest, @Suspended AsyncResponse asyncResponse) { + public Response evaluate(PolicyEvaluationRequest evaluationRequest) throws Throwable { this.auth.requireView(); KeycloakIdentity identity = createIdentity(evaluationRequest); EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest, identity); - authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(createDecisionCollector(authorization, identity, asyncResponse)); - } - - private DecisionResultCollector createDecisionCollector(AuthorizationProvider authorization, KeycloakIdentity identity, AsyncResponse asyncResponse) { - return new DecisionResultCollector() { - @Override - protected void onComplete(List results) { - try { - asyncResponse.resume(Response.ok(PolicyEvaluationResponse.build(results, resourceServer, authorization, identity)).build()); - } catch (Throwable cause) { - asyncResponse.resume(cause); - } - } - - @Override - public void onError(Throwable cause) { - asyncResponse.resume(cause); - } - }; + Decision decisionCollector = new Decision(); + authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(decisionCollector); + if (decisionCollector.error != null) { + throw decisionCollector.error; + } + return Response.ok(PolicyEvaluationResponseBuilder.build(decisionCollector.results, resourceServer, authorization, identity)).build(); } private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) { @@ -144,11 +149,11 @@ public class PolicyEvaluationService { } private List createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) { - List resources = representation.getResources(); - return resources.stream().flatMap((Function>) resource -> { + List resources = representation.getResources(); + return resources.stream().flatMap((Function>) resource -> { StoreFactory storeFactory = authorization.getStoreFactory(); if (resource == null) { - resource = new PolicyEvaluationRequest.Resource(); + resource = new ResourceRepresentation(); } Set givenScopes = resource.getScopes(); @@ -183,32 +188,15 @@ public class PolicyEvaluationService { private KeycloakIdentity createIdentity(PolicyEvaluationRequest representation) { KeycloakSession keycloakSession = this.authorization.getKeycloakSession(); RealmModel realm = keycloakSession.getContext().getRealm(); - AccessToken accessToken = new AccessToken(); + AccessToken accessToken = null; - accessToken.subject(representation.getUserId()); - accessToken.issuedFor(representation.getClientId()); - accessToken.audience(representation.getClientId()); - accessToken.issuer(Urls.realmIssuer(keycloakSession.getContext().getUri().getBaseUri(), realm.getName())); - accessToken.setRealmAccess(new AccessToken.Access()); - AccessToken.Access realmAccess = accessToken.getRealmAccess(); - Map claims = accessToken.getOtherClaims(); - Map givenAttributes = representation.getContext().get("attributes"); - - if (givenAttributes != null) { - givenAttributes.forEach((key, value) -> claims.put(key, asList(value))); - } - - String subject = accessToken.getSubject(); + String subject = representation.getUserId(); if (subject != null) { UserModel userModel = keycloakSession.users().getUserById(subject, realm); if (userModel != null) { - userModel.getAttributes().forEach(claims::put); - - userModel.getRoleMappings().stream().map(RoleModel::getName).forEach(roleName -> realmAccess.addRole(roleName)); - String clientId = representation.getClientId(); if (clientId == null) { @@ -223,17 +211,18 @@ public class PolicyEvaluationService { clientSession = keycloakSession.sessions().createClientSession(realm, clientModel); userSession = keycloakSession.sessions().createUserSession(realm, userModel, userModel.getUsername(), "127.0.0.1", "passwd", false, null, null); - UserSessionModel finalUserSession = userSession; - ClientSessionModel finalClientSession = clientSession; + new TokenManager().attachClientSession(userSession, clientSession); - for (ProtocolMapperModel mapping : clientModel.getProtocolMappers()) { - KeycloakSessionFactory sessionFactory = keycloakSession.getKeycloakSessionFactory(); - ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); - - if (mapper != null && (mapper instanceof OIDCAccessTokenMapper)) { - accessToken = ((OIDCAccessTokenMapper)mapper).transformAccessToken(accessToken, mapping, keycloakSession, finalUserSession, finalClientSession); + Set requestedRoles = new HashSet<>(); + for (String roleId : clientSession.getRoles()) { + RoleModel role = realm.getRoleById(roleId); + if (role != null) { + requestedRoles.add(role); } } + + + accessToken = new TokenManager().createClientAccessToken(keycloakSession, requestedRoles, realm, clientModel, userModel, userSession, clientSession); } finally { if (clientSession != null) { keycloakSession.sessions().removeClientSession(realm, clientSession); @@ -243,21 +232,30 @@ public class PolicyEvaluationService { keycloakSession.sessions().removeUserSession(realm, userSession); } } - - AccessToken.Access clientAccess = accessToken.addAccess(clientModel.getClientId()); - clientAccess.roles(new HashSet<>()); - - userModel.getClientRoleMappings(clientModel).stream().map(RoleModel::getName).forEach(roleName -> clientAccess.addRole(roleName)); - - ClientModel resourceServerClient = realm.getClientById(resourceServer.getClientId()); - AccessToken.Access resourceServerAccess = accessToken.addAccess(resourceServerClient.getClientId()); - resourceServerAccess.roles(new HashSet<>()); - - userModel.getClientRoleMappings(resourceServerClient).stream().map(RoleModel::getName).forEach(roleName -> resourceServerAccess.addRole(roleName)); } } } + if (accessToken == null) { + accessToken = new AccessToken(); + + accessToken.subject(representation.getUserId()); + accessToken.issuedFor(representation.getClientId()); + accessToken.audience(representation.getClientId()); + accessToken.issuer(Urls.realmIssuer(keycloakSession.getContext().getUri().getBaseUri(), realm.getName())); + accessToken.setRealmAccess(new AccessToken.Access()); + + } + + AccessToken.Access realmAccess = accessToken.getRealmAccess(); + Map claims = accessToken.getOtherClaims(); + Map givenAttributes = representation.getContext().get("attributes"); + + if (givenAttributes != null) { + givenAttributes.forEach((key, value) -> claims.put(key, asList(value))); + } + + if (representation.getRoleIds() != null) { representation.getRoleIds().forEach(roleName -> realmAccess.addRole(roleName)); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java similarity index 57% rename from services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java rename to services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java index 427978a2c4..c42472549c 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java +++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java @@ -1,13 +1,12 @@ /* - * JBoss, Home of Professional Open Source. - * Copyright 2016 Red Hat, Inc., and individual contributors - * as indicated by the @author tags. + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,9 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.keycloak.authorization.admin.representation; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.Decision; +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.util.Permissions; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.authorization.DecisionEffect; +import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse; +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.Comparator; @@ -29,56 +43,37 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.Decision.Effect; -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.models.utils.ModelToRepresentation; -import org.keycloak.representations.AccessToken; -import org.keycloak.representations.idm.authorization.PolicyRepresentation; -import org.keycloak.representations.idm.authorization.ResourceRepresentation; -import org.keycloak.representations.idm.authorization.ScopeRepresentation; - /** - * @author Pedro Igor + * @author Bill Burke + * @version $Revision: 1 $ */ -public class PolicyEvaluationResponse { - - private List results; - private boolean entitlements; - private Effect status; - private AccessToken rpt; - - private PolicyEvaluationResponse() { - - } - +public class PolicyEvaluationResponseBuilder { public static PolicyEvaluationResponse build(List results, ResourceServer resourceServer, AuthorizationProvider authorization, KeycloakIdentity identity) { PolicyEvaluationResponse response = new PolicyEvaluationResponse(); - List resultsRep = new ArrayList<>(); + List resultsRep = new ArrayList<>(); AccessToken accessToken = identity.getAccessToken(); AccessToken.Authorization authorizationData = new AccessToken.Authorization(); authorizationData.setPermissions(Permissions.permits(results, authorization, resourceServer.getId())); accessToken.setAuthorization(authorizationData); - response.rpt = accessToken; + response.setRpt(accessToken); - if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Effect.DENY))) { - response.status = Effect.DENY; + if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Decision.Effect.DENY))) { + response.setStatus(DecisionEffect.DENY); } else { - response.status = Effect.PERMIT; + response.setStatus(DecisionEffect.PERMIT); } for (Result result : results) { - EvaluationResultRepresentation rep = new EvaluationResultRepresentation(); + PolicyEvaluationResponse.EvaluationResultRepresentation rep = new PolicyEvaluationResponse.EvaluationResultRepresentation(); - rep.setStatus(result.getEffect()); + if (result.getEffect() == Decision.Effect.DENY) { + rep.setStatus(DecisionEffect.DENY); + } else { + rep.setStatus(DecisionEffect.PERMIT); + + } resultsRep.add(rep); if (result.getPermission().getResource() != null) { @@ -105,9 +100,9 @@ public class PolicyEvaluationResponse { return representation; }).collect(Collectors.toList())); - List policies = new ArrayList<>(); + List policies = new ArrayList<>(); - for (PolicyResult policy : result.getResults()) { + for (Result.PolicyResult policy : result.getResults()) { policies.add(toRepresentation(policy, authorization)); } @@ -116,10 +111,10 @@ public class PolicyEvaluationResponse { resultsRep.sort(Comparator.comparing(o -> o.getResource().getName())); - Map groupedResults = new HashMap<>(); + Map groupedResults = new HashMap<>(); resultsRep.forEach(evaluationResultRepresentation -> { - EvaluationResultRepresentation result = groupedResults.get(evaluationResultRepresentation.getResource().getId()); + PolicyEvaluationResponse.EvaluationResultRepresentation result = groupedResults.get(evaluationResultRepresentation.getResource().getId()); ResourceRepresentation resource = evaluationResultRepresentation.getResource(); if (result == null) { @@ -127,8 +122,8 @@ public class PolicyEvaluationResponse { result = evaluationResultRepresentation; } - if (result.getStatus().equals(Effect.PERMIT) || (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT) && result.getStatus().equals(Effect.DENY))) { - result.setStatus(Effect.PERMIT); + if (result.getStatus().equals(DecisionEffect.PERMIT) || (evaluationResultRepresentation.getStatus().equals(DecisionEffect.PERMIT) && result.getStatus().equals(DecisionEffect.DENY))) { + result.setStatus(DecisionEffect.PERMIT); } List scopes = result.getScopes(); @@ -146,15 +141,15 @@ public class PolicyEvaluationResponse { if (!scopes.contains(scope)) { scopes.add(scope); } - if (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT)) { + if (evaluationResultRepresentation.getStatus().equals(Decision.Effect.PERMIT)) { if (!allowedScopes.contains(scope)) { allowedScopes.add(scope); } } else { - evaluationResultRepresentation.getPolicies().forEach(new Consumer() { + evaluationResultRepresentation.getPolicies().forEach(new Consumer() { @Override - public void accept(PolicyResultRepresentation policyResultRepresentation) { - if (policyResultRepresentation.getStatus().equals(Effect.PERMIT)) { + public void accept(PolicyEvaluationResponse.PolicyResultRepresentation policyResultRepresentation) { + if (policyResultRepresentation.getStatus().equals(Decision.Effect.PERMIT)) { if (!allowedScopes.contains(scope)) { allowedScopes.add(scope); } @@ -176,16 +171,16 @@ public class PolicyEvaluationResponse { result.getResource().setName("Any Resource with Scopes " + scopes.stream().flatMap((Function>) scopeRepresentation -> Arrays.asList(scopeRepresentation.getName()).stream()).collect(Collectors.toList())); } - List policies = result.getPolicies(); + List policies = result.getPolicies(); - for (PolicyResultRepresentation policy : new ArrayList<>(evaluationResultRepresentation.getPolicies())) { + for (PolicyEvaluationResponse.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)) { + if (policy.getStatus().equals(Decision.Effect.DENY)) { Policy policyModel = authorization.getStoreFactory().getPolicyStore().findById(policy.getPolicy().getId(), resourceServer.getId()); for (ScopeRepresentation scope : policyModel.getScopes().stream().map(scopeModel -> ModelToRepresentation.toRepresentation(scopeModel, authorization)).collect(Collectors.toList())) { if (!policy.getScopes().contains(scope) && policyModel.getScopes().stream().filter(policyScope -> policyScope.getId().equals(scope.getId())).findFirst().isPresent()) { @@ -197,13 +192,13 @@ public class PolicyEvaluationResponse { } }); - response.results = groupedResults.values().stream().collect(Collectors.toList()); + response.setResults(groupedResults.values().stream().collect(Collectors.toList())); return response; } - private static PolicyResultRepresentation toRepresentation(PolicyResult policy, AuthorizationProvider authorization) { - PolicyResultRepresentation policyResultRep = new PolicyResultRepresentation(); + private static PolicyEvaluationResponse.PolicyResultRepresentation toRepresentation(Result.PolicyResult policy, AuthorizationProvider authorization) { + PolicyEvaluationResponse.PolicyResultRepresentation policyResultRep = new PolicyEvaluationResponse.PolicyResultRepresentation(); PolicyRepresentation representation = new PolicyRepresentation(); @@ -213,127 +208,15 @@ public class PolicyEvaluationResponse { representation.setDecisionStrategy(policy.getPolicy().getDecisionStrategy()); policyResultRep.setPolicy(representation); - policyResultRep.setStatus(policy.getStatus()); + if (policy.getStatus() == Decision.Effect.DENY) { + policyResultRep.setStatus(DecisionEffect.DENY); + } else { + policyResultRep.setStatus(DecisionEffect.PERMIT); + + } + policyResultRep.setAssociatedPolicies(policy.getAssociatedPolicies().stream().map(result -> toRepresentation(result, authorization)).collect(Collectors.toList())); return policyResultRep; } - - public List getResults() { - return results; - } - - public Effect getStatus() { - return status; - } - - public boolean isEntitlements() { - return entitlements; - } - - public AccessToken getRpt() { - return rpt; - } - - public static class EvaluationResultRepresentation { - - private ResourceRepresentation resource; - private List scopes; - private List policies; - private Effect status; - private List allowedScopes = new ArrayList<>(); - - public void setResource(final ResourceRepresentation resource) { - this.resource = resource; - } - - public ResourceRepresentation getResource() { - return resource; - } - - public void setScopes(List scopes) { - this.scopes = scopes; - } - - public List getScopes() { - return scopes; - } - - public void setPolicies(final List policies) { - this.policies = policies; - } - - public List getPolicies() { - return policies; - } - - public void setStatus(final Effect status) { - this.status = status; - } - - public Effect getStatus() { - return status; - } - - public void setAllowedScopes(List allowedScopes) { - this.allowedScopes = allowedScopes; - } - - public List getAllowedScopes() { - return allowedScopes; - } - } - - public static class PolicyResultRepresentation { - - private PolicyRepresentation policy; - private Effect status; - private List associatedPolicies; - private List scopes = new ArrayList<>(); - - public PolicyRepresentation getPolicy() { - return policy; - } - - public void setPolicy(final PolicyRepresentation policy) { - this.policy = policy; - } - - public Effect getStatus() { - return status; - } - - public void setStatus(final Effect status) { - this.status = status; - } - - public List getAssociatedPolicies() { - return associatedPolicies; - } - - 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/common/AbstractEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/AbstractEvaluationContext.java new file mode 100644 index 0000000000..75882101c8 --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/common/AbstractEvaluationContext.java @@ -0,0 +1,63 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.authorization.common; + +import org.keycloak.authorization.attribute.Attributes; +import org.keycloak.authorization.identity.Identity; +import org.keycloak.authorization.policy.evaluation.EvaluationContext; +import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.AccessToken; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Pedro Igor + */ +public abstract class AbstractEvaluationContext implements EvaluationContext { + + private final KeycloakSession keycloakSession; + + public AbstractEvaluationContext(KeycloakSession keycloakSession) { + this.keycloakSession = keycloakSession; + } + + public Map> getBaseAttributes() { + HashMap> attributes = new HashMap<>(); + + attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("MM/dd/yyyy hh:mm:ss").format(new Date()))); + attributes.put("kc.client.network.ip_address", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteAddr())); + attributes.put("kc.client.network.host", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteHost())); + + List userAgents = this.keycloakSession.getContext().getRequestHeaders().getRequestHeader("User-Agent"); + + if (userAgents != null) { + attributes.put("kc.client.user_agent", userAgents); + } + + attributes.put("kc.realm.name", Arrays.asList(this.keycloakSession.getContext().getRealm().getName())); + + return attributes; + } +} diff --git a/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java b/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java new file mode 100644 index 0000000000..67e6bb494b --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.authorization.common; + +import org.keycloak.authorization.attribute.Attributes; +import org.keycloak.authorization.identity.Identity; +import org.keycloak.models.ClientModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; + +import java.util.Collection; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserModelIdentity implements Identity { + protected RealmModel realm; + protected UserModel user; + + public UserModelIdentity(UserModel user) { + this.user = user; + } + + @Override + public String getId() { + return user.getId(); + } + + @Override + public Attributes getAttributes() { + Map attr = user.getAttributes(); + return Attributes.from(attr); + } + + @Override + public boolean hasRealmRole(String roleName) { + RoleModel role = realm.getRole(roleName); + if (role == null) return false; + return user.hasRole(role); + } + + @Override + public boolean hasClientRole(String clientId, String roleName) { + ClientModel client = realm.getClientByClientId(clientId); + RoleModel role = client.getRole(roleName); + if (role == null) return false; + return user.hasRole(role); + } + + @Override + public boolean hasRole(String roleName) { + throw new RuntimeException("Should not execute"); + } + + @Override + public boolean hasClientRole(String roleName) { + throw new RuntimeException("Should not execute"); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminLocalTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminLocalTest.java new file mode 100644 index 0000000000..f4a11be693 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminLocalTest.java @@ -0,0 +1,215 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.admin; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.DecisionEffect; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest; +import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse; +import org.keycloak.testsuite.AbstractKeycloakTest; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +//@Ignore +public class FineGrainAdminLocalTest extends AbstractKeycloakTest { + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation testRealmRep = new RealmRepresentation(); + testRealmRep.setId(TEST); + testRealmRep.setRealm(TEST); + testRealmRep.setEnabled(true); + testRealms.add(testRealmRep); + } + + public static void setupDefaults(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + + ClientModel client = realm.getClientByClientId("realm-management"); + + AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); + ResourceServer resourceServer = authz.getStoreFactory().getResourceServerStore().create(client.getId()); + Scope mapRoleScope = authz.getStoreFactory().getScopeStore().create("map-role", resourceServer); + Scope manageScope = authz.getStoreFactory().getScopeStore().create("manage", resourceServer); + + Policy manageUsersPolicy = null; + Policy manageClientsPolicy = null; + for (RoleModel role : client.getRoles()) { + Policy policy = createRolePolicy(authz, resourceServer, role); + if (role.getName().equals(AdminRoles.MANAGE_USERS)) { + manageUsersPolicy = policy; + } else if (role.getName().equals(AdminRoles.MANAGE_CLIENTS)) { + manageClientsPolicy = policy; + } + Resource resource = createRoleResource(authz, resourceServer, role); + Set scopeset = new HashSet<>(); + scopeset.add(mapRoleScope); + resource.updateScopes(scopeset); + + + String name = "map.role.permission." + client.getClientId() + "." + role.getName(); + Policy permission = addScopePermission(authz, resourceServer, name, resource, mapRoleScope, policy); + + } + Resource usersResource = authz.getStoreFactory().getResourceStore().create("Users", resourceServer, resourceServer.getClientId()); + Set scopeset = new HashSet<>(); + scopeset.add(manageScope); + usersResource.updateScopes(scopeset); + addScopePermission(authz, resourceServer, "Users.manage.permission", usersResource, manageScope, manageUsersPolicy); + } + + private static Policy addScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope, Policy policy) { + Policy permission = authz.getStoreFactory().getPolicyStore().create(name, "scope", resourceServer); + String resources = "[\"" + resource.getId() + "\"]"; + String scopes = "[\"" + scope.getId() + "\"]"; + String applyPolicies = "[\"" + policy.getId() + "\"]"; + Map config = new HashMap<>(); + config.put("resources", resources); + config.put("scopes", scopes); + config.put("applyPolicies", applyPolicies); + permission.setConfig(config); + permission.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + permission.setLogic(Logic.POSITIVE); + permission.addResource(resource); + permission.addScope(scope); + permission.addAssociatedPolicy(policy); + return permission; + } + + private static Resource createRoleResource(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role) { + String roleName = getRoleResourceName(role); + Resource resource = authz.getStoreFactory().getResourceStore().create(roleName, resourceServer, resourceServer.getClientId()); + resource.setType("Role"); + return resource; + } + + private static String getRoleResourceName(RoleModel role) { + String roleName = "realm"; + if (role.getContainer() instanceof ClientModel) { + ClientModel client = (ClientModel)role.getContainer(); + roleName = client.getClientId(); + } + roleName = "role.resource." + roleName + "." + role.getName(); + return roleName; + } + + + private static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role) { + String roleName = "realm"; + if (role.getContainer() instanceof ClientModel) { + ClientModel client = (ClientModel) role.getContainer(); + roleName = client.getClientId() ; + } + roleName = "role.policy." + roleName + "." + role.getName(); + Policy policy = authz.getStoreFactory().getPolicyStore().create(roleName, "role", resourceServer); + + String roleValues = "[{\"id\":\"" + role.getId() + "\",\"required\": true}]"; + policy.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + policy.setLogic(Logic.POSITIVE); + Map config = new HashMap<>(); + config.put("roles", roleValues); + policy.setConfig(config); + return policy; + } + + public static void setupUsers(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + UserModel admin = session.users().addUser(realm, "admin"); + admin.grantRole(client.getRole(AdminRoles.REALM_ADMIN)); + UserModel manageUserOnlyUser = session.users().addUser(realm, "manage-user"); + RoleModel manageUsersRole = client.getRole(AdminRoles.MANAGE_USERS); + manageUserOnlyUser.grantRole(manageUsersRole); + UserModel manageRealmUser = session.users().addUser(realm, "manage-realm"); + manageRealmUser.grantRole(manageUsersRole); + RoleModel manageRealmRole = client.getRole(AdminRoles.MANAGE_REALM); + manageRealmUser.grantRole(manageRealmRole); + + } + + @Test + public void testUI() throws Exception { + testingClient.server().run(FineGrainAdminLocalTest::setupDefaults); + testingClient.server().run(FineGrainAdminLocalTest::setupUsers); + //Thread.sleep(1000000000); + } + + public static void evaluateAdminHasManageRealmPermissions(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + UserModel admin = session.users().getUserByUsername("admin", realm); + + AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ResourceServer resourceServer = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + + RoleModel manageRealmRole = client.getRole(AdminRoles.MANAGE_REALM); + Resource roleResource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(manageRealmRole), resourceServer.getId()); + + + + } + + @Test + public void testEvaluation() throws Exception { + testingClient.server().run(FineGrainAdminLocalTest::setupDefaults); + testingClient.server().run(FineGrainAdminLocalTest::setupUsers); + + RealmResource realm = adminClient.realm(TEST); + String resourceServerId = realm.clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0).getId(); + UserRepresentation admin = realm.users().search("admin").get(0); + UserRepresentation manageUser = realm.users().search("manage-user").get(0); + UserRepresentation manageRealm = realm.users().search("manage-realm").get(0); + + PolicyEvaluationRequest request = new PolicyEvaluationRequest(); + request.setUserId(admin.getId()); + request.setClientId(resourceServerId); + request.addResource("role.resource." + Constants.REALM_MANAGEMENT_CLIENT_ID + "." + AdminRoles.MANAGE_REALM, + "map-role"); + PolicyEvaluationResponse result = realm.clients().get(resourceServerId).authorization().policies().evaluate(request); + Assert.assertEquals(result.getStatus(), DecisionEffect.PERMIT); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationCompositeRoleTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationCompositeRoleTest.java new file mode 100644 index 0000000000..98d33663a7 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationCompositeRoleTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.authz; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.DecisionEffect; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest; +import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse; +import org.keycloak.testsuite.AbstractKeycloakTest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class PolicyEvaluationCompositeRoleTest extends AbstractKeycloakTest { + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation testRealmRep = new RealmRepresentation(); + testRealmRep.setId(TEST); + testRealmRep.setRealm(TEST); + testRealmRep.setEnabled(true); + testRealms.add(testRealmRep); + } + + public static void setup(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + ClientModel client = session.realms().addClient(realm, "myclient"); + RoleModel role1 = client.addRole("client-role1"); + + + AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); + ResourceServer resourceServer = authz.getStoreFactory().getResourceServerStore().create(client.getId()); + Policy policy = createRolePolicy(authz, resourceServer, role1); + + Scope scope = authz.getStoreFactory().getScopeStore().create("myscope", resourceServer); + Resource resource = authz.getStoreFactory().getResourceStore().create("myresource", resourceServer, resourceServer.getClientId()); + addScopePermission(authz, resourceServer, "mypermission", resource, scope, policy); + + RoleModel composite = realm.addRole("composite"); + composite.addCompositeRole(role1); + + UserModel user = session.users().addUser(realm, "user"); + user.grantRole(composite); + } + + private static Policy addScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope, Policy policy) { + Policy permission = authz.getStoreFactory().getPolicyStore().create(name, "scope", resourceServer); + String resources = "[\"" + resource.getId() + "\"]"; + String scopes = "[\"" + scope.getId() + "\"]"; + String applyPolicies = "[\"" + policy.getId() + "\"]"; + Map config = new HashMap<>(); + config.put("resources", resources); + config.put("scopes", scopes); + config.put("applyPolicies", applyPolicies); + permission.setConfig(config); + permission.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + permission.setLogic(Logic.POSITIVE); + permission.addResource(resource); + permission.addScope(scope); + permission.addAssociatedPolicy(policy); + return permission; + } + + + private static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role) { + Policy policy = authz.getStoreFactory().getPolicyStore().create(role.getName(), "role", resourceServer); + + String roleValues = "[{\"id\":\"" + role.getId() + "\",\"required\": true}]"; + policy.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + policy.setLogic(Logic.POSITIVE); + Map config = new HashMap<>(); + config.put("roles", roleValues); + policy.setConfig(config); + return policy; + } + + + @Test + public void testCreate() throws Exception { + testingClient.server().run(PolicyEvaluationCompositeRoleTest::setup); + + RealmResource realm = adminClient.realm(TEST); + String resourceServerId = realm.clients().findByClientId("myclient").get(0).getId(); + UserRepresentation user = realm.users().search("user").get(0); + + PolicyEvaluationRequest request = new PolicyEvaluationRequest(); + request.setUserId(user.getId()); + request.setClientId(resourceServerId); + request.addResource("myresource", "myscope"); + PolicyEvaluationResponse result = realm.clients().get(resourceServerId).authorization().policies().evaluate(request); + Assert.assertEquals(result.getStatus(), DecisionEffect.PERMIT); + } + + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java index 4a2ce9693d..4eb2bb4b80 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java @@ -99,7 +99,7 @@ public class AdapterTest { @Rule public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule); - //@Test + @Test public void testUi() throws Exception { Thread.sleep(1000000000); }