diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java new file mode 100644 index 0000000000..2189c32fbf --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java @@ -0,0 +1,51 @@ +/* + * 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.policy.evaluation; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class DecisionResult extends DecisionResultCollector { + protected List results; + protected Throwable error; + + @Override + protected void onComplete(List results) { + this.results = results; + + } + + @Override + public void onError(Throwable cause) { + this.error = cause; + } + + public boolean completed() { + return results != null && error == null; + } + + public List getResults() { + return results; + } + + public Throwable getError() { + return error; + } +} 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 2e82794927..1267b186c3 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java @@ -38,6 +38,7 @@ import javax.ws.rs.core.Response; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.policy.evaluation.DecisionResult; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest; import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder; @@ -86,21 +87,6 @@ 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(); @@ -116,12 +102,12 @@ public class PolicyEvaluationService { CloseableKeycloakIdentity identity = createIdentity(evaluationRequest); try { EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest, identity); - Decision decisionCollector = new Decision(); + DecisionResult decisionCollector = new DecisionResult(); authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(decisionCollector); - if (decisionCollector.error != null) { - throw decisionCollector.error; + if (!decisionCollector.completed()) { + throw decisionCollector.getError(); } - return Response.ok(PolicyEvaluationResponseBuilder.build(decisionCollector.results, resourceServer, authorization, identity)).build(); + return Response.ok(PolicyEvaluationResponseBuilder.build(decisionCollector.getResults(), resourceServer, authorization, identity)).build(); } finally { identity.close(); } diff --git a/services/src/main/java/org/keycloak/authorization/common/AbstractEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java similarity index 82% rename from services/src/main/java/org/keycloak/authorization/common/AbstractEvaluationContext.java rename to services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java index 75882101c8..2551639fad 100644 --- a/services/src/main/java/org/keycloak/authorization/common/AbstractEvaluationContext.java +++ b/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java @@ -35,12 +35,19 @@ import java.util.Map; /** * @author Pedro Igor */ -public abstract class AbstractEvaluationContext implements EvaluationContext { +public class DefaultEvaluationContext implements EvaluationContext { - private final KeycloakSession keycloakSession; + protected final KeycloakSession keycloakSession; + protected final Identity identity; - public AbstractEvaluationContext(KeycloakSession keycloakSession) { + public DefaultEvaluationContext(Identity identity, KeycloakSession keycloakSession) { this.keycloakSession = keycloakSession; + this.identity = identity; + } + + @Override + public Identity getIdentity() { + return identity; } public Map> getBaseAttributes() { @@ -60,4 +67,9 @@ public abstract class AbstractEvaluationContext implements EvaluationContext { return attributes; } + + @Override + public Attributes getAttributes() { + return Attributes.from(getBaseAttributes()); + } } diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java index fc929ec5d9..da3cb0fa9c 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakEvaluationContext.java @@ -18,34 +18,28 @@ 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 class KeycloakEvaluationContext implements EvaluationContext { +public class KeycloakEvaluationContext extends DefaultEvaluationContext { private final KeycloakIdentity identity; - private final KeycloakSession keycloakSession; public KeycloakEvaluationContext(KeycloakSession keycloakSession) { this(new KeycloakIdentity(keycloakSession), keycloakSession); } public KeycloakEvaluationContext(KeycloakIdentity identity, KeycloakSession keycloakSession) { + super(identity, keycloakSession); this.identity = identity; - this.keycloakSession = keycloakSession; } @Override @@ -54,27 +48,13 @@ public class KeycloakEvaluationContext implements EvaluationContext { } @Override - public Attributes getAttributes() { - 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())); - + public Map> getBaseAttributes() { + Map> attributes = super.getBaseAttributes(); AccessToken accessToken = this.identity.getAccessToken(); if (accessToken != null) { attributes.put("kc.client.id", Arrays.asList(accessToken.getIssuedFor())); } - - 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.from(attributes); + 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 index 67e6bb494b..c54e4c0532 100644 --- a/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/UserModelIdentity.java @@ -34,7 +34,8 @@ public class UserModelIdentity implements Identity { protected RealmModel realm; protected UserModel user; - public UserModelIdentity(UserModel user) { + public UserModelIdentity(RealmModel realm, UserModel user) { + this.realm = realm; this.user = user; } 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 2e2c4a8b6a..80fcb81cbb 100644 --- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java +++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java @@ -19,6 +19,8 @@ package org.keycloak.authorization.util; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -45,6 +47,10 @@ import org.keycloak.representations.idm.authorization.Permission; */ public final class Permissions { + public static List permission(ResourceServer server, Resource resource, Scope scope) { + return Arrays.asList(new ResourcePermission(resource, Arrays.asList(scope), server)); + } + /** * Returns a list of permissions for all resources and scopes that belong to the given resourceServer and * identity. 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 index a533c41037..00aaf919bf 100644 --- 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 @@ -21,10 +21,18 @@ import org.junit.Ignore; import org.junit.Test; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.Decision; +import org.keycloak.authorization.common.DefaultEvaluationContext; +import org.keycloak.authorization.common.UserModelIdentity; 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.authorization.permission.ResourcePermission; +import org.keycloak.authorization.permission.evaluator.PermissionEvaluator; +import org.keycloak.authorization.policy.evaluation.DecisionResult; +import org.keycloak.authorization.policy.evaluation.EvaluationContext; +import org.keycloak.authorization.util.Permissions; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; @@ -43,6 +51,7 @@ import org.keycloak.testsuite.AbstractKeycloakTest; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -53,7 +62,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; * @author Bill Burke * @version $Revision: 1 $ */ -@Ignore +//@Ignore public class FineGrainAdminLocalTest extends AbstractKeycloakTest { @Override @@ -170,15 +179,16 @@ public class FineGrainAdminLocalTest extends AbstractKeycloakTest { } - @Test + //@Test public void testUI() throws Exception { testingClient.server().run(FineGrainAdminLocalTest::setupDefaults); testingClient.server().run(FineGrainAdminLocalTest::setupUsers); - //Thread.sleep(1000000000); + Thread.sleep(1000000000); } public static void evaluateAdminHasManageRealmPermissions(KeycloakSession session) { RealmModel realm = session.realms().getRealmByName(TEST); + session.getContext().setRealm(realm); UserModel admin = session.users().getUserByUsername("admin", realm); AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); @@ -187,9 +197,29 @@ public class FineGrainAdminLocalTest extends AbstractKeycloakTest { RoleModel manageRealmRole = client.getRole(AdminRoles.MANAGE_REALM); Resource roleResource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(manageRealmRole), resourceServer.getId()); + Scope mapRoleScope = authz.getStoreFactory().getScopeStore().findByName("map-role", resourceServer.getId()); + + UserModelIdentity identity = new UserModelIdentity(realm, admin); + EvaluationContext context = new DefaultEvaluationContext(identity, session); + DecisionResult decisionCollector = new DecisionResult(); + + List permissions = Permissions.permission(resourceServer, roleResource, mapRoleScope); + PermissionEvaluator from = authz.evaluators().from(permissions, context); + from.evaluate(decisionCollector); + if (!decisionCollector.completed()) { + decisionCollector.getError().printStackTrace(); + } + Assert.assertTrue(decisionCollector.completed()); + Assert.assertEquals(decisionCollector.getResults().get(0).getEffect(), Decision.Effect.PERMIT); + } + @Test + public void testEvaluation2() throws Exception { + testingClient.server().run(FineGrainAdminLocalTest::setupDefaults); + testingClient.server().run(FineGrainAdminLocalTest::setupUsers); + testingClient.server().run(FineGrainAdminLocalTest::evaluateAdminHasManageRealmPermissions); } @Test