From 58868ca99fc2b3da74e955099569fc7f55a1331a Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 24 Apr 2017 10:05:39 -0400 Subject: [PATCH 01/18] prototype --- .../policy/evaluation/DecisionResult.java | 51 +++++++++++++++++++ .../admin/PolicyEvaluationService.java | 24 ++------- ...ext.java => DefaultEvaluationContext.java} | 18 +++++-- .../common/KeycloakEvaluationContext.java | 32 +++--------- .../common/UserModelIdentity.java | 3 +- .../authorization/util/Permissions.java | 6 +++ .../admin/FineGrainAdminLocalTest.java | 36 +++++++++++-- 7 files changed, 118 insertions(+), 52 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java rename services/src/main/java/org/keycloak/authorization/common/{AbstractEvaluationContext.java => DefaultEvaluationContext.java} (82%) 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 From f67013bcb63dcac3193fb7bf3fcc919478046510 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 26 Apr 2017 14:39:41 -0400 Subject: [PATCH 02/18] fix --- .../representations/JsonWebToken.java | 1 + .../admin/permissions/Helper.java | 95 +++++++++ .../admin/permissions/MgmtPermissions.java | 96 +++++++++ .../permissions/RoleMgmtPermissions.java | 177 +++++++++++++++++ .../admin/permissions/UsersPermissions.java | 188 ++++++++++++++++++ .../common/KeycloakIdentity.java | 68 +++++++ .../admin/FineGrainAdminLocalTest.java | 21 +- .../PolicyEvaluationCompositeRoleTest.java | 4 +- 8 files changed, 646 insertions(+), 4 deletions(-) create mode 100644 services/src/main/java/org/keycloak/authorization/admin/permissions/Helper.java create mode 100644 services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java create mode 100644 services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java create mode 100644 services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java index e499271998..04071b6ab9 100755 --- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java +++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java @@ -141,6 +141,7 @@ public class JsonWebToken implements Serializable { } public boolean hasAudience(String audience) { + if (this.audience == null) return false; for (String a : this.audience) { if (a.equals(audience)) { return true; diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/Helper.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/Helper.java new file mode 100644 index 0000000000..a45ea7f79a --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/Helper.java @@ -0,0 +1,95 @@ +/* + * 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.admin.permissions; + +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.ClientModel; +import org.keycloak.models.RoleModel; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class Helper { + public static Policy addScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope, Policy policy) { + ScopePermissionRepresentation representation = new ScopePermissionRepresentation(); + + representation.setName(name); + representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + representation.setLogic(Logic.POSITIVE); + representation.addResource(resource.getName()); + representation.addScope(scope.getName()); + representation.addPolicy(policy.getName()); + + return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); + } + + public static Policy addEmptyScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope) { + ScopePermissionRepresentation representation = new ScopePermissionRepresentation(); + + representation.setName(name); + representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + representation.setLogic(Logic.POSITIVE); + representation.addResource(resource.getName()); + representation.addScope(scope.getName()); + + return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); + } + + public static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role) { + String roleName = getRolePolicyName(role); + return createRolePolicy(authz, resourceServer, role, roleName); + } + + public static Policy createRolePolicy(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role, String policyName) { + PolicyRepresentation representation = new PolicyRepresentation(); + + representation.setName(policyName); + representation.setType("role"); + representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + representation.setLogic(Logic.POSITIVE); + String roleValues = "[{\"id\":\"" + role.getId() + "\",\"required\": true}]"; + Map config = new HashMap<>(); + config.put("roles", roleValues); + representation.setConfig(config); + + return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); + } + + public static String getRolePolicyName(RoleModel role) { + String roleName = ""; + if (role.getContainer() instanceof ClientModel) { + ClientModel client = (ClientModel) role.getContainer(); + roleName = client.getClientId() + "." + role.getName(); + } else { + roleName = role.getName(); + } + roleName = "role.policy." + roleName; + return roleName; + } +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java new file mode 100644 index 0000000000..098c263df6 --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java @@ -0,0 +1,96 @@ +/* + * 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.admin.permissions; + +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.AuthorizationProviderFactory; +import org.keycloak.authorization.common.KeycloakIdentity; +import org.keycloak.authorization.common.UserModelIdentity; +import org.keycloak.authorization.identity.Identity; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.AccessToken; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.resources.admin.AdminAuth; +import org.keycloak.services.resources.admin.RealmAuth; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class MgmtPermissions { + public static final String MANAGE_SCOPE = "manage"; + + protected RealmModel realm; + protected KeycloakSession session; + protected AuthorizationProvider authz; + protected AdminAuth auth; + protected Identity identity; + + public MgmtPermissions(KeycloakSession session, RealmModel realm) { + this.session = session; + this.realm = realm; + AuthorizationProviderFactory factory = (AuthorizationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); + this.authz = factory.create(session, realm); + } + public MgmtPermissions(KeycloakSession session, RealmModel realm, AdminAuth auth) { + this(session, realm); + this.auth = auth; + + } + + public boolean isAdminSameRealm() { + return realm.getId().equals(auth.getRealm().getId()); + } + + public RealmAuth getRealmAuth() { + RealmManager realmManager = new RealmManager(session); + if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) { + return new RealmAuth(auth, realm.getMasterAdminClient()); + } else { + return new RealmAuth(auth, realm.getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm()))); + } + } + + public Identity identity() { + if (identity != null) return identity; + if (auth.getClient().getClientId().equals(Constants.REALM_MANAGEMENT_CLIENT_ID)) { + this.identity = new UserModelIdentity(realm, auth.getUser()); + + } else { + this.identity = new KeycloakIdentity(auth.getToken(), session); + } + return this.identity; + } + + + public RoleMgmtPermissions roles() { + return new RoleMgmtPermissions(session, realm, authz, this); + } + + public ResourceServer findOrCreateResourceServer(ClientModel client) { + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) { + server = authz.getStoreFactory().getResourceServerStore().create(client.getId()); + } + return server; + } + +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java new file mode 100644 index 0000000000..a745d56708 --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java @@ -0,0 +1,177 @@ +/* + * 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.admin.permissions; + +import org.jboss.logging.Logger; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.Decision; +import org.keycloak.authorization.common.DefaultEvaluationContext; +import org.keycloak.authorization.identity.Identity; +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; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RoleMgmtPermissions { + private static final Logger logger = Logger.getLogger(RoleMgmtPermissions.class); + public static final String MAP_ROLE_SCOPE = "map-role"; + protected final KeycloakSession session; + protected final RealmModel realm; + protected final AuthorizationProvider authz; + protected final MgmtPermissions root; + + public RoleMgmtPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { + this.session = session; + this.realm = realm; + this.authz = authz; + this.root = root; + } + + public boolean isPermissionsEnabled(RoleModel role) { + ClientModel client = getRoleClient(role); + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), server.getId()); + + return policy != null; + } + + public void setPermissionsEnabled(RoleModel role, boolean enable) { + if (enable) { + ResourceServer server = getResourceServer(role); + if (authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()) != null) { + return; + } + createResource(role); + } else { + ClientModel client = getRoleClient(role); + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return; + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); + if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), server.getId()); + if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId()); + } + } + + public boolean canMapRole(RoleModel role) { + if (!root.isAdminSameRealm()) { + return true; + } + if (!isPermissionsEnabled(role)) return true; // no authz permissions set up so just allow it. + + ResourceServer resourceServer = getResourceServer(role); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), resourceServer.getId()); + if (policy.getAssociatedPolicies().isEmpty()) { + return true; // if no policies applied, just ignore + } + + + Identity identity = root.identity(); + + EvaluationContext context = new DefaultEvaluationContext(identity, session); + DecisionResult decisionCollector = new DecisionResult(); + Resource roleResource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), resourceServer.getId()); + Scope mapRoleScope = getMapRoleScope(resourceServer); + + List permissions = Permissions.permission(resourceServer, roleResource, mapRoleScope); + PermissionEvaluator from = authz.evaluators().from(permissions, context); + from.evaluate(decisionCollector); + if (!decisionCollector.completed()) { + logger.error("Failed to run map role policy check", decisionCollector.getError()); + return false; + } + return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; + } + + + private ClientModel getRoleClient(RoleModel role) { + ClientModel client = null; + if (role.getContainer() instanceof ClientModel) { + client = (ClientModel)role.getContainer(); + } else { + client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + } + return client; + } + + public Policy getManageUsersPolicy(ResourceServer server) { + RoleModel role = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).getRole(AdminRoles.MANAGE_USERS); + return getRolePolicy(server, role); + } + + public Policy getRolePolicy(ResourceServer server, RoleModel role) { + String policyName = Helper.getRolePolicyName(role); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(policyName, server.getId()); + if (policy != null) return policy; + return Helper.createRolePolicy(authz, server, role, policyName); + } + + private Scope getMapRoleScope(ResourceServer server) { + Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_SCOPE, server.getId()); + if (scope == null) { + scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_SCOPE, server); + } + return scope; + } + + + private Resource createResource(RoleModel role) { + ResourceServer server = getResourceServer(role); + Resource resource = authz.getStoreFactory().getResourceStore().create(getRoleResourceName(role), server, server.getClientId()); + resource.setType("Role"); + Scope mapRoleScope = getMapRoleScope(server); + Helper.addEmptyScopePermission(authz, server, getMapRoleScopePermissionName(role), resource, mapRoleScope); + return resource; + } + + private String getMapRoleScopePermissionName(RoleModel role) { + return MAP_ROLE_SCOPE + ".permission." + role.getName(); + } + + private ResourceServer getResourceServer(RoleModel role) { + ClientModel client = getRoleClient(role); + return root.findOrCreateResourceServer(client); + } + + private static String getRoleResourceName(RoleModel role) { + return "role.resource." + role.getName(); + } + + +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java new file mode 100644 index 0000000000..c198885d74 --- /dev/null +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java @@ -0,0 +1,188 @@ +/* + * 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.admin.permissions; + +import org.jboss.logging.Logger; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.Decision; +import org.keycloak.authorization.common.DefaultEvaluationContext; +import org.keycloak.authorization.identity.Identity; +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.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.services.managers.RealmManager; +import org.keycloak.services.resources.admin.AdminAuth; +import org.keycloak.services.resources.admin.RealmAuth; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UsersPermissions { + private static final Logger logger = Logger.getLogger(UsersPermissions.class); + protected final KeycloakSession session; + protected final RealmModel realm; + protected final AuthorizationProvider authz; + protected final MgmtPermissions root; + + public UsersPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { + this.session = session; + this.realm = realm; + this.authz = authz; + this.root = root; + } + + private void initialize() { + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ResourceServer server = root.findOrCreateResourceServer(client); + Scope manageScope = authz.getStoreFactory().getScopeStore().findByName(MgmtPermissions.MANAGE_SCOPE, server.getId()); + if (manageScope == null) { + manageScope = authz.getStoreFactory().getScopeStore().create(MgmtPermissions.MANAGE_SCOPE, server); + + } + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + if (usersResource == null) { + usersResource = authz.getStoreFactory().getResourceStore().create("Users", server, server.getClientId()); + } + Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + if (policy == null) { + Set scopeset = new HashSet<>(); + scopeset.add(manageScope); + usersResource.updateScopes(scopeset); + Policy manageUsersPolicy = root.roles().getManageUsersPolicy(server); + Helper.addScopePermission(authz, server, "manage.permission.users", usersResource, manageScope, manageUsersPolicy); + } + } + + public boolean isPermissionsEnabled() { + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + + return policy != null; + } + + public void setPermissionsEnabled(RoleModel role, boolean enable) { + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + if (enable) { + initialize(); + } else { + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return; + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + if (usersResource == null) { + authz.getStoreFactory().getResourceStore().delete(usersResource.getId()); + } + Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + if (policy == null) { + authz.getStoreFactory().getPolicyStore().delete(policy.getId()); + + } + } + } + + private Resource getUsersResource(ResourceServer server) { + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + if (usersResource == null) { + usersResource = authz.getStoreFactory().getResourceStore().create("Users", server, server.getClientId()); + } + return usersResource; + } + + private Scope getManageScope(ResourceServer server) { + Scope manageScope = authz.getStoreFactory().getScopeStore().findByName(MgmtPermissions.MANAGE_SCOPE, server.getId()); + if (manageScope == null) { + manageScope = authz.getStoreFactory().getScopeStore().create(MgmtPermissions.MANAGE_SCOPE, server); + + } + return manageScope; + } + + private ResourceServer getRealmManagementResourceServer() { + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + return root.findOrCreateResourceServer(client); + } + + private boolean canManageDefault() { + RealmAuth auth = root.getRealmAuth(); + auth.init(RealmAuth.Resource.USER); + return auth.hasManage(); + + } + + public boolean canManage(UserModel user) { + if (!root.isAdminSameRealm()) { + return canManageDefault(); + } + + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return canManageDefault(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + if (resource == null) return canManageDefault(); + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + if (policy == null) { + return canManageDefault(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return canManageDefault(); + } + + Identity identity = root.identity(); + EvaluationContext context = new DefaultEvaluationContext(identity, session); + DecisionResult decisionCollector = new DecisionResult(); + ResourceServer resourceServer = getRealmManagementResourceServer(); + Resource roleResource = authz.getStoreFactory().getResourceStore().findByName("Users", resourceServer.getId()); + Scope manageScope = getManageScope(resourceServer); + + List permissions = Permissions.permission(resourceServer, roleResource, manageScope); + PermissionEvaluator from = authz.evaluators().from(permissions, context); + from.evaluate(decisionCollector); + if (!decisionCollector.completed()) { + logger.error("Failed to run Users manage check", decisionCollector.getError()); + return false; + } + return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; + + } +} diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java index 6b5b0275ab..7898f7b715 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java @@ -54,6 +54,74 @@ public class KeycloakIdentity implements Identity { this(Tokens.getAccessToken(keycloakSession), keycloakSession); } + public KeycloakIdentity(KeycloakSession keycloakSession, AccessToken accessToken) { + this(accessToken, keycloakSession, keycloakSession.getContext().getRealm()); + } + + public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession, RealmModel realm) { + if (accessToken == null) { + throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN); + } + if (keycloakSession == null) { + throw new ErrorResponseException("no_keycloak_session", "No keycloak session", Status.FORBIDDEN); + } + if (realm == null) { + throw new ErrorResponseException("no_keycloak_session", "No realm set", Status.FORBIDDEN); + } + this.accessToken = accessToken; + this.keycloakSession = keycloakSession; + this.realm = realm; + + Map> attributes = new HashMap<>(); + + try { + ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken); + Iterator iterator = objectNode.fieldNames(); + + while (iterator.hasNext()) { + String fieldName = iterator.next(); + JsonNode fieldValue = objectNode.get(fieldName); + List values = new ArrayList<>(); + + if (fieldValue.isArray()) { + Iterator valueIterator = fieldValue.iterator(); + + while (valueIterator.hasNext()) { + values.add(valueIterator.next().asText()); + } + } else { + String value = fieldValue.asText(); + + if (StringUtil.isNullOrEmpty(value)) { + continue; + } + + values.add(value); + } + + if (!values.isEmpty()) { + attributes.put(fieldName, values); + } + } + + AccessToken.Access realmAccess = accessToken.getRealmAccess(); + + if (realmAccess != null) { + attributes.put("kc.realm.roles", realmAccess.getRoles()); + } + + Map resourceAccess = accessToken.getResourceAccess(); + + if (resourceAccess != null) { + resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles())); + } + } catch (Exception e) { + throw new RuntimeException("Error while reading attributes from security token.", e); + } + + this.attributes = Attributes.from(attributes); + } + public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) { if (accessToken == null) { throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN); 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 bcf35fb6dd..f9afdee37c 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,6 +21,7 @@ import org.junit.Ignore; import org.junit.Test; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.Decision; import org.keycloak.authorization.common.DefaultEvaluationContext; import org.keycloak.authorization.common.UserModelIdentity; @@ -79,9 +80,10 @@ public class FineGrainAdminLocalTest extends AbstractKeycloakTest { public static void setupDefaults(KeycloakSession session) { RealmModel realm = session.realms().getRealmByName(TEST); - ClientModel client = realm.getClientByClientId("realm-management"); + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); - AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); + AuthorizationProviderFactory factory = (AuthorizationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); + AuthorizationProvider authz = factory.create(session, realm); 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); @@ -103,6 +105,7 @@ public class FineGrainAdminLocalTest extends AbstractKeycloakTest { String name = "map.role.permission." + client.getClientId() + "." + role.getName(); Policy permission = addScopePermission(authz, resourceServer, name, resource, mapRoleScope, policy); + addEmptyScopePermission(authz, resourceServer, "empty. " + name, resource, mapRoleScope); } Resource usersResource = authz.getStoreFactory().getResourceStore().create("Users", resourceServer, resourceServer.getClientId()); @@ -125,6 +128,18 @@ public class FineGrainAdminLocalTest extends AbstractKeycloakTest { return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); } + private static Policy addEmptyScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope) { + ScopePermissionRepresentation representation = new ScopePermissionRepresentation(); + + representation.setName(name); + representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + representation.setLogic(Logic.POSITIVE); + representation.addResource(resource.getName()); + representation.addScope(scope.getName()); + + return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); + } + private static Resource createRoleResource(AuthorizationProvider authz, ResourceServer resourceServer, RoleModel role) { String roleName = getRoleResourceName(role); Resource resource = authz.getStoreFactory().getResourceStore().create(roleName, resourceServer, resourceServer.getClientId()); @@ -179,7 +194,7 @@ public class FineGrainAdminLocalTest extends AbstractKeycloakTest { } - //@Test + @Test public void testUI() throws Exception { testingClient.server().run(FineGrainAdminLocalTest::setupDefaults); testingClient.server().run(FineGrainAdminLocalTest::setupUsers); 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 index 74fcb7bd0b..25382e8bc9 100644 --- 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 @@ -20,6 +20,7 @@ import org.junit.Assert; import org.junit.Test; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; @@ -66,7 +67,8 @@ public class PolicyEvaluationCompositeRoleTest extends AbstractKeycloakTest { RoleModel role1 = client.addRole("client-role1"); - AuthorizationProvider authz = session.getProvider(AuthorizationProvider.class); + AuthorizationProviderFactory factory = (AuthorizationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); + AuthorizationProvider authz = factory.create(session, realm); ResourceServer resourceServer = authz.getStoreFactory().getResourceServerStore().create(client.getId()); Policy policy = createRolePolicy(authz, resourceServer, role1); From f760427c5c71e0f8beca54e36608a873c07f6e60 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 8 May 2017 13:48:51 -0400 Subject: [PATCH 03/18] fine grain tests --- .../idm/ManagementPermissionReference.java | 53 +++ .../admin/permissions/MgmtPermissions.java | 16 +- .../permissions/RoleMgmtPermissions.java | 90 +++-- .../admin/permissions/UsersPermissions.java | 144 +++++--- .../resources/admin/ClientResource.java | 2 +- .../admin/ClientRoleMappingsResource.java | 44 ++- .../resources/admin/RealmAdminResource.java | 53 ++- .../resources/admin/RoleByIdResource.java | 64 ++++ .../admin/RoleContainerResource.java | 73 +++- .../resources/admin/RoleMapperResource.java | 48 ++- .../resources/admin/UsersResource.java | 6 +- .../testsuite/util/AdminClientUtil.java | 11 +- .../admin/FineGrainAdminLocalTest.java | 260 -------------- .../admin/FineGrainAdminUnitTest.java | 336 ++++++++++++++++++ .../messages/admin-messages_en.properties | 9 +- .../theme/base/admin/resources/js/app.js | 18 + .../admin/resources/js/authz/authz-app.js | 36 +- .../resources/js/authz/authz-controller.js | 59 ++- .../admin/resources/js/controllers/realm.js | 15 + .../theme/base/admin/resources/js/services.js | 22 ++ .../authz/mgmt/client-role-permissions.html | 41 +++ .../authz/mgmt/realm-role-permissions.html | 39 ++ .../authz/mgmt/users-permissions.html | 35 ++ .../partials/client-role-detail.html | 4 +- .../admin/resources/partials/role-detail.html | 4 +- .../admin/resources/partials/user-list.html | 3 +- .../templates/kc-tabs-client-role.html | 13 + .../resources/templates/kc-tabs-role.html | 13 + .../resources/templates/kc-tabs-users.html | 11 + 29 files changed, 1160 insertions(+), 362 deletions(-) create mode 100644 core/src/main/java/org/keycloak/representations/idm/ManagementPermissionReference.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminLocalTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java create mode 100644 themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html create mode 100644 themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html create mode 100644 themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html create mode 100755 themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html create mode 100755 themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html create mode 100755 themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html diff --git a/core/src/main/java/org/keycloak/representations/idm/ManagementPermissionReference.java b/core/src/main/java/org/keycloak/representations/idm/ManagementPermissionReference.java new file mode 100644 index 0000000000..22550d6fb1 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ManagementPermissionReference.java @@ -0,0 +1,53 @@ +/* + * 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; + +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ManagementPermissionReference { + private boolean enabled; + private String resource; + private Map scopePermissions; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public Map getScopePermissions() { + return scopePermissions; + } + + public void setScopePermissions(Map scopePermissions) { + this.scopePermissions = scopePermissions; + } +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java index 098c263df6..84aa631558 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java @@ -25,7 +25,9 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.representations.AccessToken; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.admin.AdminAuth; @@ -47,7 +49,8 @@ public class MgmtPermissions { public MgmtPermissions(KeycloakSession session, RealmModel realm) { this.session = session; this.realm = realm; - AuthorizationProviderFactory factory = (AuthorizationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); + KeycloakSessionFactory keycloakSessionFactory = session.getKeycloakSessionFactory(); + AuthorizationProviderFactory factory = (AuthorizationProviderFactory) keycloakSessionFactory.getProviderFactory(AuthorizationProvider.class); this.authz = factory.create(session, realm); } public MgmtPermissions(KeycloakSession session, RealmModel realm, AdminAuth auth) { @@ -57,10 +60,11 @@ public class MgmtPermissions { } public boolean isAdminSameRealm() { - return realm.getId().equals(auth.getRealm().getId()); + return auth == null || realm.getId().equals(auth.getRealm().getId()); } public RealmAuth getRealmAuth() { + if (auth == null) return null; RealmManager realmManager = new RealmManager(session); if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) { return new RealmAuth(auth, realm.getMasterAdminClient()); @@ -71,7 +75,7 @@ public class MgmtPermissions { public Identity identity() { if (identity != null) return identity; - if (auth.getClient().getClientId().equals(Constants.REALM_MANAGEMENT_CLIENT_ID)) { + if (auth.getClient().getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID)) { this.identity = new UserModelIdentity(realm, auth.getUser()); } else { @@ -80,11 +84,17 @@ public class MgmtPermissions { return this.identity; } + public void setIdentity(UserModel user) { + this.identity = new UserModelIdentity(realm, user); + } + public RoleMgmtPermissions roles() { return new RoleMgmtPermissions(session, realm, authz, this); } + public UsersPermissions users() { return new UsersPermissions(session, realm, authz, this); } + public ResourceServer findOrCreateResourceServer(ClientModel client) { ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); if (server == null) { diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java index a745d56708..49241310fd 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java @@ -29,6 +29,7 @@ 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.store.ResourceStore; import org.keycloak.authorization.util.Permissions; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; @@ -59,16 +60,7 @@ public class RoleMgmtPermissions { } public boolean isPermissionsEnabled(RoleModel role) { - ClientModel client = getRoleClient(role); - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); - if (server == null) return false; - - Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); - if (resource == null) return false; - - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), server.getId()); - - return policy != null; + return mapRolePermission(role) != null; } public void setPermissionsEnabled(RoleModel role, boolean enable) { @@ -79,8 +71,7 @@ public class RoleMgmtPermissions { } createResource(role); } else { - ClientModel client = getRoleClient(role); - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + ResourceServer server = resourceServer(role); if (server == null) return; Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); @@ -89,6 +80,44 @@ public class RoleMgmtPermissions { } } + public Policy mapRolePermission(RoleModel role) { + ResourceServer server = resourceServer(role); + if (server == null) return null; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); + if (resource == null) return null; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), server.getId()); + return authz.getStoreFactory().getPolicyStore().findById(policy.getId(), server.getId()); + } + + public Resource resource(RoleModel role) { + ResourceStore resourceStore = authz.getStoreFactory().getResourceStore(); + ResourceServer server = resourceServer(role); + if (server == null) return null; + Resource resource = resourceStore.findByName(getRoleResourceName(role), server.getId()); + if (resource == null) return null; + return resourceStore.findById(resource.getId(), server.getId()); + } + + public ResourceServer resourceServer(RoleModel role) { + ClientModel client = getRoleClient(role); + return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + } + + /** + * Is admin allowed to map this role? In Authz terms, does the user have the "map-role" scope for the role's Authz resource? + * + * This method is hardcoded to return TRUE if any of these conditions are met: + * - The admin is from the master realm managing a different realm + * - If the Authz objects are not set up correctly for this role (resource server, resource, permission) + * - If the role's mapRole permission does not have a policy associated with it. + * + * Otherwise, it will use the Authz policy engine to resolve this answer. + * + * @param role + * @return + */ public boolean canMapRole(RoleModel role) { if (!root.isAdminSameRealm()) { return true; @@ -101,22 +130,27 @@ public class RoleMgmtPermissions { return true; // if no policies applied, just ignore } + RealmModel oldRealm = session.getContext().getRealm(); + try { + session.getContext().setRealm(realm); + Identity identity = root.identity(); - Identity identity = root.identity(); + EvaluationContext context = new DefaultEvaluationContext(identity, session); + DecisionResult decisionCollector = new DecisionResult(); + Resource roleResource = resource(role); + Scope mapRoleScope = getMapRoleScope(resourceServer); - EvaluationContext context = new DefaultEvaluationContext(identity, session); - DecisionResult decisionCollector = new DecisionResult(); - Resource roleResource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), resourceServer.getId()); - Scope mapRoleScope = getMapRoleScope(resourceServer); - - List permissions = Permissions.permission(resourceServer, roleResource, mapRoleScope); - PermissionEvaluator from = authz.evaluators().from(permissions, context); - from.evaluate(decisionCollector); - if (!decisionCollector.completed()) { - logger.error("Failed to run map role policy check", decisionCollector.getError()); - return false; + List permissions = Permissions.permission(resourceServer, roleResource, mapRoleScope); + PermissionEvaluator from = authz.evaluators().from(permissions, context); + from.evaluate(decisionCollector); + if (!decisionCollector.completed()) { + logger.error("Failed to run map role policy check", decisionCollector.getError()); + return false; + } + return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; + } finally { + session.getContext().setRealm(oldRealm); } - return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; } @@ -130,12 +164,12 @@ public class RoleMgmtPermissions { return client; } - public Policy getManageUsersPolicy(ResourceServer server) { + public Policy manageUsersPolicy(ResourceServer server) { RoleModel role = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).getRole(AdminRoles.MANAGE_USERS); - return getRolePolicy(server, role); + return rolePolicy(server, role); } - public Policy getRolePolicy(ResourceServer server, RoleModel role) { + public Policy rolePolicy(ResourceServer server, RoleModel role) { String policyName = Helper.getRolePolicyName(role); Policy policy = authz.getStoreFactory().getPolicyStore().findByName(policyName, server.getId()); if (policy != null) return policy; diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java index c198885d74..3f6b72a3e2 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java @@ -30,14 +30,13 @@ 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; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.admin.AdminAuth; import org.keycloak.services.resources.admin.RealmAuth; import java.util.HashSet; @@ -45,11 +44,16 @@ import java.util.List; import java.util.Set; /** + * Manages default policies for all users. + * + * * @author Bill Burke * @version $Revision: 1 $ */ public class UsersPermissions { private static final Logger logger = Logger.getLogger(UsersPermissions.class); + public static final String MANAGE_PERMISSION_USERS = "manage.permission.users"; + public static final String USERS_RESOURCE = "Users"; protected final KeycloakSession session; protected final RealmModel realm; protected final AuthorizationProvider authz; @@ -70,45 +74,44 @@ public class UsersPermissions { manageScope = authz.getStoreFactory().getScopeStore().create(MgmtPermissions.MANAGE_SCOPE, server); } - Resource usersResource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); if (usersResource == null) { - usersResource = authz.getStoreFactory().getResourceStore().create("Users", server, server.getClientId()); + usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getClientId()); } - Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); if (policy == null) { Set scopeset = new HashSet<>(); scopeset.add(manageScope); usersResource.updateScopes(scopeset); - Policy manageUsersPolicy = root.roles().getManageUsersPolicy(server); - Helper.addScopePermission(authz, server, "manage.permission.users", usersResource, manageScope, manageUsersPolicy); + Policy manageUsersPolicy = root.roles().manageUsersPolicy(server); + Helper.addScopePermission(authz, server, MANAGE_PERMISSION_USERS, usersResource, manageScope, manageUsersPolicy); } } public boolean isPermissionsEnabled() { - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + ResourceServer server = resourceServer(); if (server == null) return false; - Resource resource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); if (resource == null) return false; - Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); return policy != null; } - public void setPermissionsEnabled(RoleModel role, boolean enable) { + public void setPermissionsEnabled(boolean enable) { ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); if (enable) { initialize(); } else { ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); if (server == null) return; - Resource usersResource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); if (usersResource == null) { authz.getStoreFactory().getResourceStore().delete(usersResource.getId()); } - Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); if (policy == null) { authz.getStoreFactory().getPolicyStore().delete(policy.getId()); @@ -117,9 +120,9 @@ public class UsersPermissions { } private Resource getUsersResource(ResourceServer server) { - Resource usersResource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); if (usersResource == null) { - usersResource = authz.getStoreFactory().getResourceStore().create("Users", server, server.getClientId()); + usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getClientId()); } return usersResource; } @@ -138,51 +141,108 @@ public class UsersPermissions { return root.findOrCreateResourceServer(client); } - private boolean canManageDefault() { + private boolean canManageDefault(UserModel admin) { RealmAuth auth = root.getRealmAuth(); - auth.init(RealmAuth.Resource.USER); - return auth.hasManage(); + if (auth != null) { + auth.init(RealmAuth.Resource.USER); + return auth.hasManage(); + } else { + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + RoleModel manageUsers = client.getRole(AdminRoles.MANAGE_USERS); + return admin.hasRole(manageUsers); + } } - public boolean canManage(UserModel user) { + public boolean canManage() { + if (root.getRealmAuth() == null) { + throw new NullPointerException("Realm auth null"); + } + return canManage(root.getRealmAuth().getAuth().getUser()); + } + + public Resource resource() { + ResourceServer server = resourceServer(); + if (server == null) return null; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (resource == null) return null; + return authz.getStoreFactory().getResourceStore().findById(resource.getId(), server.getId()); + + } + + /** + * Is admin allowed to manage users? In Authz terms, does the admin have the "map-role" scope for the role's Authz resource? + * + * This method will follow the old default behavior (does the admin have the manage-users role) if any of these conditions + * are met.: + * - The admin is from the master realm managing a different realm + * - If the Authz objects are not set up correctly for the Users resource in Authz + * - The "manage" permission for the Users resource has an empty associatedPolicy list. + * + * Otherwise, it will use the Authz policy engine to resolve this answer. + * + * @param admin + * @return + */ + public boolean canManage(UserModel admin) { if (!root.isAdminSameRealm()) { - return canManageDefault(); + return canManageDefault(admin); } - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); - if (server == null) return canManageDefault(); + ResourceServer server = resourceServer(); + if (server == null) return canManageDefault(admin); - Resource resource = authz.getStoreFactory().getResourceStore().findByName("Users", server.getId()); - if (resource == null) return canManageDefault(); + Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (resource == null) return canManageDefault(admin); - Policy policy = authz.getStoreFactory().getPolicyStore().findByName("manage.permissions.users", server.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); if (policy == null) { - return canManageDefault(); + return canManageDefault(admin); } Set associatedPolicies = policy.getAssociatedPolicies(); // if no policies attached to permission then just do default behavior if (associatedPolicies == null || associatedPolicies.isEmpty()) { - return canManageDefault(); + return canManageDefault(admin); } - Identity identity = root.identity(); - EvaluationContext context = new DefaultEvaluationContext(identity, session); - DecisionResult decisionCollector = new DecisionResult(); - ResourceServer resourceServer = getRealmManagementResourceServer(); - Resource roleResource = authz.getStoreFactory().getResourceStore().findByName("Users", resourceServer.getId()); - Scope manageScope = getManageScope(resourceServer); + RealmModel oldRealm = session.getContext().getRealm(); + try { + session.getContext().setRealm(realm); + Identity identity = root.identity(); + EvaluationContext context = new DefaultEvaluationContext(identity, session); + DecisionResult decisionCollector = new DecisionResult(); + ResourceServer resourceServer = getRealmManagementResourceServer(); + Resource roleResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, resourceServer.getId()); + Scope manageScope = getManageScope(resourceServer); - List permissions = Permissions.permission(resourceServer, roleResource, manageScope); - PermissionEvaluator from = authz.evaluators().from(permissions, context); - from.evaluate(decisionCollector); - if (!decisionCollector.completed()) { - logger.error("Failed to run Users manage check", decisionCollector.getError()); - return false; + List permissions = Permissions.permission(resourceServer, roleResource, manageScope); + PermissionEvaluator from = authz.evaluators().from(permissions, context); + from.evaluate(decisionCollector); + if (!decisionCollector.completed()) { + logger.error("Failed to run Users manage check", decisionCollector.getError()); + return false; + } + return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; + } finally { + session.getContext().setRealm(oldRealm); } - return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; } + + public ResourceServer resourceServer() { + ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + } + + public Policy managePermission() { + ResourceServer server = resourceServer(); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); + // have to do this because findByName returns a Jpa Entity todo change when fixed + return authz.getStoreFactory().getPolicyStore().findById(policy.getId(), server.getId()); + + } + + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 89b0a3317c..db24956d23 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -328,7 +328,7 @@ public class ClientResource { @Path("roles") public RoleContainerResource getRoleContainerResource() { - return new RoleContainerResource(uriInfo, realm, auth, client, adminEvent); + return new RoleContainerResource(session, uriInfo, realm, auth, client, adminEvent); } /** diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java index 3d7d4cd173..8cc456fd55 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java @@ -19,6 +19,7 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.authorization.admin.permissions.MgmtPermissions; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; @@ -30,6 +31,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.ForbiddenException; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -63,6 +65,8 @@ public class ClientRoleMappingsResource { protected ClientModel client; protected AdminEventBuilder adminEvent; private UriInfo uriInfo; + private RoleMapperResource.ManageResourcePermissionCheck manageResourcePermissionCheck; + public ClientRoleMappingsResource(UriInfo uriInfo, KeycloakSession session, RealmModel realm, RealmAuth auth, RoleMapperModel user, ClientModel client, AdminEventBuilder adminEvent) { this.uriInfo = uriInfo; @@ -74,6 +78,12 @@ public class ClientRoleMappingsResource { this.adminEvent = adminEvent.resource(ResourceType.CLIENT_ROLE_MAPPING); } + public void setManageCheck(RoleMapperResource.ManageResourcePermissionCheck mapperPermissions) { + this.manageResourcePermissionCheck = mapperPermissions; + } + + + /** * Get client-level role mappings for the user, and the app * @@ -157,6 +167,17 @@ public class ClientRoleMappingsResource { return mappings; } + private void checkManagePermission() { + if (manageResourcePermissionCheck == null) { + auth.requireManage(); + } else { + if (!manageResourcePermissionCheck.canManage()) { + throw new ForbiddenException(); + } + } + } + + /** * Add client-level roles to the user role mapping * @@ -165,7 +186,7 @@ public class ClientRoleMappingsResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addClientRoleMapping(List roles) { - auth.requireManage(); + checkManagePermission(); if (user == null || client == null) { throw new NotFoundException("Not found"); @@ -176,21 +197,28 @@ public class ClientRoleMappingsResource { if (roleModel == null || !roleModel.getId().equals(role.getId())) { throw new NotFoundException("Role not found"); } + checkMapRolePermission(roleModel); user.grantRole(roleModel); } adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success(); } - /** - * Delete client-level roles from user role mapping - * - * @param roles - */ + private void checkMapRolePermission(RoleModel roleModel) { + if (!new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel)) { + throw new ForbiddenException(); + } + } + + /** + * Delete client-level roles from user role mapping + * + * @param roles + */ @DELETE @Consumes(MediaType.APPLICATION_JSON) public void deleteClientRoleMapping(List roles) { - auth.requireManage(); + checkManagePermission(); if (user == null || client == null) { throw new NotFoundException("Not found"); @@ -205,6 +233,7 @@ public class ClientRoleMappingsResource { ClientModel client = (ClientModel) roleModel.getContainer(); if (!client.getId().equals(this.client.getId())) continue; } + checkMapRolePermission(roleModel); user.deleteRoleMapping(roleModel); roles.add(ModelToRepresentation.toRepresentation(roleModel)); } @@ -216,6 +245,7 @@ public class ClientRoleMappingsResource { throw new NotFoundException("Role not found"); } + checkMapRolePermission(roleModel); try { user.deleteRoleMapping(roleModel); } catch (ModelException me) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 146ef865f4..4585e16225 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -23,6 +23,9 @@ import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; import org.keycloak.KeyPairVerifier; +import org.keycloak.authorization.admin.permissions.MgmtPermissions; +import org.keycloak.authorization.admin.permissions.RoleMgmtPermissions; +import org.keycloak.authorization.admin.permissions.UsersPermissions; import org.keycloak.common.ClientConnection; import org.keycloak.common.VerificationException; import org.keycloak.common.util.PemUtils; @@ -44,6 +47,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.UserCache; @@ -61,6 +65,7 @@ import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.PartialImportRepresentation; import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -132,7 +137,6 @@ public class RealmAdminResource { this.adminEvent = adminEvent.realm(realm).resource(ResourceType.REALM); auth.init(RealmAuth.Resource.REALM); - auth.requireAny(); } /** @@ -234,7 +238,7 @@ public class RealmAdminResource { */ @Path("roles") public RoleContainerResource getRoleContainerResource() { - return new RoleContainerResource(uriInfo, realm, auth, realm, adminEvent); + return new RoleContainerResource(session, uriInfo, realm, auth, realm, adminEvent); } /** @@ -360,6 +364,51 @@ public class RealmAdminResource { return users; } + @NoCache + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("users-management-permissions") + public ManagementPermissionReference getUserMgmtPermissions() { + auth.requireView(); + + MgmtPermissions permissions = new MgmtPermissions(session, realm); + if (permissions.users().isPermissionsEnabled()) { + return toUsersMgmtRef(permissions); + } else { + return new ManagementPermissionReference(); + } + + } + + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @NoCache + @Path("users-management-permissions") + public ManagementPermissionReference setUsersManagementPermissionsEnabled(ManagementPermissionReference ref) { + auth.requireManage(); + + MgmtPermissions permissions = new MgmtPermissions(session, realm); + permissions.users().setPermissionsEnabled(ref.isEnabled()); + if (ref.isEnabled()) { + return toUsersMgmtRef(permissions); + } else { + return new ManagementPermissionReference(); + } + } + + + public static ManagementPermissionReference toUsersMgmtRef(MgmtPermissions permissions) { + ManagementPermissionReference ref = new ManagementPermissionReference(); + ref.setEnabled(true); + ref.setResource(permissions.users().resource().getId()); + Map scopes = new HashMap<>(); + scopes.put(MgmtPermissions.MANAGE_SCOPE, permissions.users().managePermission().getId()); + ref.setScopePermissions(scopes); + return ref; + } + + @Path("user-storage") public UserStorageProviderResource userStorage() { UserStorageProviderResource fed = new UserStorageProviderResource(realm, auth, adminEvent); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java index 1f19b64eb4..9d3e1006cc 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java @@ -19,6 +19,8 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.authorization.admin.permissions.MgmtPermissions; +import org.keycloak.authorization.admin.permissions.RoleMgmtPermissions; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; @@ -26,6 +28,7 @@ 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.ManagementPermissionReference; import org.keycloak.representations.idm.RoleRepresentation; import javax.ws.rs.Consumes; @@ -39,7 +42,9 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -244,4 +249,63 @@ public class RoleByIdResource extends RoleResource { deleteComposites(adminEvent, uriInfo, roles, role); } + /** + * Return object stating whether role Authoirzation permissions have been initialized or not and a reference + * + * + * @param id + * @return + */ + @Path("{role-id}/management/permissions") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference getManagementPermissions(final @PathParam("role-id") String id) { + auth.requireView(); + + RoleModel role = getRoleModel(id); + + MgmtPermissions permissions = new MgmtPermissions(session, realm); + if (!permissions.roles().isPermissionsEnabled(role)) { + return new ManagementPermissionReference(); + } + return toMgmtRef(role, permissions); + } + + public static ManagementPermissionReference toMgmtRef(RoleModel role, MgmtPermissions permissions) { + ManagementPermissionReference ref = new ManagementPermissionReference(); + ref.setEnabled(true); + ref.setResource(permissions.roles().resource(role).getId()); + Map scopes = new HashMap<>(); + scopes.put(RoleMgmtPermissions.MAP_ROLE_SCOPE, permissions.roles().mapRolePermission(role).getId()); + ref.setScopePermissions(scopes); + return ref; + } + + /** + * Return object stating whether role Authoirzation permissions have been initialized or not and a reference + * + * + * @param id + * @return initialized manage permissions reference + */ + @Path("{role-id}/management/permissions") + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-id") String id, ManagementPermissionReference ref) { + auth.requireManage(); + + RoleModel role = getRoleModel(id); + + MgmtPermissions permissions = new MgmtPermissions(session, realm); + permissions.roles().setPermissionsEnabled(role, ref.isEnabled()); + if (ref.isEnabled()) { + return toMgmtRef(role, permissions); + } else { + return new ManagementPermissionReference(); + } + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java index 47f9c6420a..94e4defc86 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java @@ -19,14 +19,18 @@ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.authorization.admin.permissions.MgmtPermissions; +import org.keycloak.authorization.admin.permissions.RoleMgmtPermissions; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.services.ErrorResponse; @@ -44,7 +48,9 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -58,14 +64,16 @@ public class RoleContainerResource extends RoleResource { protected RoleContainerModel roleContainer; private AdminEventBuilder adminEvent; private UriInfo uriInfo; + private KeycloakSession session; - public RoleContainerResource(UriInfo uriInfo, RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) { + public RoleContainerResource(KeycloakSession session, UriInfo uriInfo, RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) { super(realm); this.uriInfo = uriInfo; this.realm = realm; this.auth = auth; this.roleContainer = roleContainer; this.adminEvent = adminEvent; + this.session = session; } /** @@ -355,4 +363,67 @@ public class RoleContainerResource extends RoleResource { deleteComposites(adminEvent, uriInfo, roles, role); } + /** + * Return object stating whether role Authoirzation permissions have been initialized or not and a reference + * + * + * @param roleName + * @return + */ + @Path("{role-name}/management/permissions") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference getManagementPermissions(final @PathParam("role-name") String roleName) { + auth.requireView(); + + if (roleContainer == null) { + throw new NotFoundException("Could not find client"); + } + + RoleModel role = roleContainer.getRole(roleName); + if (role == null) { + throw new NotFoundException("Could not find role"); + } + + MgmtPermissions permissions = new MgmtPermissions(session, realm); + if (!permissions.roles().isPermissionsEnabled(role)) { + return new ManagementPermissionReference(); + } + return RoleByIdResource.toMgmtRef(role, permissions); + } + + /** + * Return object stating whether role Authoirzation permissions have been initialized or not and a reference + * + * + * @param roleName + * @return initialized manage permissions reference + */ + @Path("{role-name}/management/permissions") + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-name") String roleName, ManagementPermissionReference ref) { + auth.requireManage(); + + if (roleContainer == null) { + throw new NotFoundException("Could not find client"); + } + + RoleModel role = roleContainer.getRole(roleName); + if (role == null) { + throw new NotFoundException("Could not find role"); + } + + if (ref.isEnabled()) { + MgmtPermissions permissions = new MgmtPermissions(session, realm); + permissions.roles().setPermissionsEnabled(role, ref.isEnabled()); + return RoleByIdResource.toMgmtRef(role, permissions); + } else { + return new ManagementPermissionReference(); + } + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java index 5b7af1fd3c..51b1847e75 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java @@ -19,6 +19,7 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.authorization.admin.permissions.MgmtPermissions; import org.keycloak.common.ClientConnection; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; @@ -33,6 +34,7 @@ import org.keycloak.representations.idm.ClientMappingsRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.ForbiddenException; import org.keycloak.services.managers.RealmManager; import javax.ws.rs.Consumes; @@ -64,6 +66,17 @@ import java.util.Set; * @version $Revision: 1 $ */ public class RoleMapperResource { + + /** + * RoleMapperResource is reused bewteen GroupResource and UserResource to manage role mappings. + * We don't know what type of resource we're managing here (user or group), so we don't know how to query the policy engine to determine + * if an action is allowed. + * + */ + public interface ManageResourcePermissionCheck { + boolean canManage(); + } + protected static final Logger logger = Logger.getLogger(RoleMapperResource.class); protected RealmModel realm; @@ -74,6 +87,8 @@ public class RoleMapperResource { private AdminEventBuilder adminEvent; + private ManageResourcePermissionCheck manageResourcePermissionCheck; + @Context protected ClientConnection clientConnection; @@ -94,6 +109,9 @@ public class RoleMapperResource { } + public void setManageCheck(ManageResourcePermissionCheck mapperPermissions) { + this.manageResourcePermissionCheck = mapperPermissions; + } /** * Get role mappings @@ -224,7 +242,7 @@ public class RoleMapperResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addRealmRoleMappings(List roles) { - auth.requireManage(); + checkManagePermission(); if (roleMapper == null) { throw new NotFoundException("User not found"); @@ -237,12 +255,23 @@ public class RoleMapperResource { if (roleModel == null || !roleModel.getId().equals(role.getId())) { throw new NotFoundException("Role not found"); } + checkMapRolePermission(roleModel); roleMapper.grantRole(roleModel); } adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success(); } + private void checkManagePermission() { + if (manageResourcePermissionCheck == null) { + auth.requireManage(); + } else { + if (!manageResourcePermissionCheck.canManage()) { + throw new ForbiddenException(); + } + } + } + /** * Delete realm-level role mappings * @@ -252,7 +281,7 @@ public class RoleMapperResource { @DELETE @Consumes(MediaType.APPLICATION_JSON) public void deleteRealmRoleMappings(List roles) { - auth.requireManage(); + checkManagePermission(); if (roleMapper == null) { throw new NotFoundException("User not found"); @@ -264,6 +293,7 @@ public class RoleMapperResource { roles = new LinkedList<>(); for (RoleModel roleModel : roleModels) { + checkMapRolePermission(roleModel); roleMapper.deleteRoleMapping(roleModel); roles.add(ModelToRepresentation.toRepresentation(roleModel)); } @@ -274,7 +304,7 @@ public class RoleMapperResource { if (roleModel == null || !roleModel.getId().equals(role.getId())) { throw new NotFoundException("Role not found"); } - + checkMapRolePermission(roleModel); try { roleMapper.deleteRoleMapping(roleModel); } catch (ModelException me) { @@ -290,10 +320,20 @@ public class RoleMapperResource { } + private void checkMapRolePermission(RoleModel roleModel) { + if (!new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel)) { + throw new ForbiddenException(); + } + } + @Path("clients/{client}") public ClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("client") String client) { ClientModel clientModel = realm.getClientById(client); - return new ClientRoleMappingsResource(uriInfo, session, realm, auth, roleMapper, clientModel, adminEvent); + ClientRoleMappingsResource resource = new ClientRoleMappingsResource(uriInfo, session, realm, auth, roleMapper, clientModel, adminEvent); + resource.setManageCheck(() -> { + return new MgmtPermissions(session, realm, auth.getAuth()).users().canManage(); + }); + return resource; } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 3259982457..3e4cfefe2e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -22,6 +22,7 @@ import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.authorization.admin.permissions.MgmtPermissions; import org.keycloak.common.ClientConnection; import org.keycloak.common.Profile; import org.keycloak.common.util.Time; @@ -48,9 +49,7 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserLoginFailureModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; -import org.keycloak.models.credential.PasswordUserCredentialModel; import org.keycloak.models.utils.ModelToRepresentation; -import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.provider.ProviderFactory; @@ -723,6 +722,9 @@ public class UsersResource { UserModel user = session.users().getUserById(id, realm); RoleMapperResource resource = new RoleMapperResource(realm, auth, user, adminEvent); + resource.setManageCheck(() -> { + return new MgmtPermissions(session, realm, auth.getAuth()).users().canManage(); + }); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java index c1869d78aa..a8d8089ec3 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/AdminClientUtil.java @@ -42,6 +42,15 @@ import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY; public class AdminClientUtil { public static Keycloak createAdminClient(boolean ignoreUnknownProperties) throws Exception { + String realmName = MASTER; + String username = ADMIN; + String password = ADMIN; + String clientId = Constants.ADMIN_CLI_CLIENT_ID; + String clientSecret = null; + return createAdminClient(ignoreUnknownProperties, realmName, username, password, clientId, clientSecret); + } + + public static Keycloak createAdminClient(boolean ignoreUnknownProperties, String realmName, String username, String password, String clientId, String clientSecret) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException { SSLContext ssl = null; if ("true".equals(System.getProperty("auth.server.ssl.required"))) { File trustore = new File(PROJECT_BUILD_DIRECTORY, "dependency/keystore/keycloak.truststore"); @@ -62,7 +71,7 @@ public class AdminClientUtil { } return Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", - MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID, null, ssl, jacksonProvider); + realmName, username, password, clientId, clientSecret, ssl, jacksonProvider); } public static Keycloak createAdminClient() throws Exception { 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 deleted file mode 100644 index f9afdee37c..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminLocalTest.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * 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.AuthorizationProviderFactory; -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; -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.representations.idm.authorization.PolicyRepresentation; -import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; -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; - -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(Constants.REALM_MANAGEMENT_CLIENT_ID); - - AuthorizationProviderFactory factory = (AuthorizationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); - AuthorizationProvider authz = factory.create(session, realm); - 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); - addEmptyScopePermission(authz, resourceServer, "empty. " + name, resource, mapRoleScope); - - } - 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) { - ScopePermissionRepresentation representation = new ScopePermissionRepresentation(); - - representation.setName(name); - representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); - representation.setLogic(Logic.POSITIVE); - representation.addResource(resource.getName()); - representation.addScope(scope.getName()); - representation.addPolicy(policy.getName()); - - return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); - } - - private static Policy addEmptyScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope) { - ScopePermissionRepresentation representation = new ScopePermissionRepresentation(); - - representation.setName(name); - representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); - representation.setLogic(Logic.POSITIVE); - representation.addResource(resource.getName()); - representation.addScope(scope.getName()); - - return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); - } - - 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(); - PolicyRepresentation representation = new PolicyRepresentation(); - - representation.setName(roleName); - representation.setType("role"); - representation.setDecisionStrategy(DecisionStrategy.UNANIMOUS); - representation.setLogic(Logic.POSITIVE); - String roleValues = "[{\"id\":\"" + role.getId() + "\",\"required\": true}]"; - Map config = new HashMap<>(); - config.put("roles", roleValues); - representation.setConfig(config); - - return authz.getStoreFactory().getPolicyStore().create(representation, resourceServer); - } - - 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); - session.getContext().setRealm(realm); - 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()); - 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 - 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/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java new file mode 100644 index 0000000000..add91cc43c --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -0,0 +1,336 @@ +/* + * 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.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authorization.admin.permissions.MgmtPermissions; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +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.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +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.PolicyEvaluationRequest; +import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.util.AdminClientUtil; + +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.ForbiddenException; +import javax.ws.rs.core.Response; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN; +import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +//@Ignore +public class FineGrainAdminUnitTest 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 setupPolices(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + MgmtPermissions permissions = new MgmtPermissions(session, realm); + RoleModel realmRole = realm.addRole("realm-role"); + RoleModel realmRole2 = realm.addRole("realm-role2"); + ClientModel client1 = realm.addClient("role-namespace"); + RoleModel client1Role = client1.addRole("client-role"); + + RoleModel mapperRole = realm.addRole("mapper"); + RoleModel managerRole = realm.addRole("manager"); + RoleModel compositeRole = realm.addRole("composite-role"); + compositeRole.addCompositeRole(mapperRole); + compositeRole.addCompositeRole(managerRole); + + // realm-role and role-namespace.client-role will have a role policy associated with their map-role permission + { + permissions.roles().setPermissionsEnabled(realmRole, true); + Policy mapRolePermission = permissions.roles().mapRolePermission(realmRole); + ResourceServer server = permissions.roles().resourceServer(realmRole); + Policy mapperPolicy = permissions.roles().rolePolicy(server, mapperRole); + mapRolePermission.addAssociatedPolicy(mapperPolicy); + } + + { + permissions.roles().setPermissionsEnabled(client1Role, true); + Policy mapRolePermission = permissions.roles().mapRolePermission(client1Role); + ResourceServer server = permissions.roles().resourceServer(client1Role); + Policy mapperPolicy = permissions.roles().rolePolicy(server, mapperRole); + mapRolePermission.addAssociatedPolicy(mapperPolicy); + } + + // realmRole2 will have an empty map-role policy + { + permissions.roles().setPermissionsEnabled(realmRole2, true); + } + + // setup Users manage policies + { + permissions.users().setPermissionsEnabled(true); + ResourceServer server = permissions.users().resourceServer(); + Policy managerPolicy = permissions.roles().rolePolicy(server, managerRole); + Policy permission = permissions.users().managePermission(); + permission.addAssociatedPolicy(managerPolicy); + permission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE); + } + + } + + public static void setupUsers(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + ClientModel client = realm.getClientByClientId("role-namespace"); + RoleModel realmRole = realm.getRole("realm-role"); + RoleModel realmRole2 = realm.getRole("realm-role2"); + RoleModel clientRole = client.getRole("client-role"); + RoleModel mapperRole = realm.getRole("mapper"); + RoleModel managerRole = realm.getRole("manager"); + RoleModel compositeRole = realm.getRole("composite-role"); + + UserModel authorizedUser = session.users().addUser(realm, "authorized"); + authorizedUser.setEnabled(true); + session.userCredentialManager().updateCredential(realm, authorizedUser, UserCredentialModel.password("password")); + authorizedUser.grantRole(mapperRole); + authorizedUser.grantRole(managerRole); + + UserModel authorizedComposite = session.users().addUser(realm, "authorizedComposite"); + authorizedComposite.setEnabled(true); + session.userCredentialManager().updateCredential(realm, authorizedComposite, UserCredentialModel.password("password")); + authorizedComposite.grantRole(compositeRole); + + UserModel unauthorizedUser = session.users().addUser(realm, "unauthorized"); + unauthorizedUser.setEnabled(true); + session.userCredentialManager().updateCredential(realm, unauthorizedUser, UserCredentialModel.password("password")); + + UserModel unauthorizedMapper = session.users().addUser(realm, "unauthorizedMapper"); + unauthorizedMapper.setEnabled(true); + session.userCredentialManager().updateCredential(realm, unauthorizedMapper, UserCredentialModel.password("password")); + unauthorizedMapper.grantRole(managerRole); + + UserModel user1 = session.users().addUser(realm, "user1"); + user1.setEnabled(true); + UserModel user2 = session.users().addUser(realm, "user2"); + user2.setEnabled(true); + UserModel user3 = session.users().addUser(realm, "user3"); + user3.setEnabled(true); + UserModel user4 = session.users().addUser(realm, "user4"); + user4.setEnabled(true); + + } + + public static void evaluateLocally(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + RoleModel realmRole = realm.getRole("realm-role"); + RoleModel realmRole2 = realm.getRole("realm-role2"); + ClientModel client = realm.getClientByClientId("role-namespace"); + RoleModel clientRole = client.getRole("client-role"); + + // test authorized + { + UserModel user = session.users().getUserByUsername("authorized", realm); + MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); + permissionsForAdmin.setIdentity(user); + Assert.assertTrue(permissionsForAdmin.users().canManage(user)); + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole)); + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2)); + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole)); + } + // test composite role + { + UserModel user = session.users().getUserByUsername("authorizedComposite", realm); + MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); + permissionsForAdmin.setIdentity(user); + Assert.assertTrue(permissionsForAdmin.users().canManage(user)); + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole)); + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2)); + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole)); + } + + // test unauthorized + { + UserModel user = session.users().getUserByUsername("unauthorized", realm); + MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); + permissionsForAdmin.setIdentity(user); + Assert.assertFalse(permissionsForAdmin.users().canManage(user)); + Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole)); + Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole)); + + // will result to true because realmRole2 does not have any policies attached to this permission + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2)); + } + // test unauthorized mapper + { + UserModel user = session.users().getUserByUsername("unauthorizedMapper", realm); + MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); + permissionsForAdmin.setIdentity(user); + Assert.assertTrue(permissionsForAdmin.users().canManage(user)); + Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole)); + Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole)); + // will result to true because realmRole2 does not have any policies attached to this permission + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2)); + } + + } + + + protected boolean isImportAfterEachMethod() { + return true; + } + + + @Test + public void testUI() throws Exception { + testingClient.server().run(FineGrainAdminUnitTest::setupPolices); + testingClient.server().run(FineGrainAdminUnitTest::setupUsers); + Thread.sleep(1000000000); + } + + @Test + public void testEvaluationLocal() throws Exception { + testingClient.server().run(FineGrainAdminUnitTest::setupPolices); + testingClient.server().run(FineGrainAdminUnitTest::setupUsers); + testingClient.server().run(FineGrainAdminUnitTest::evaluateLocally); + } + + @Test + public void testRestEvaluation() throws Exception { + testingClient.server().run(FineGrainAdminUnitTest::setupPolices); + testingClient.server().run(FineGrainAdminUnitTest::setupUsers); + + UserRepresentation user1 = adminClient.realm(TEST).users().search("user1").get(0); + UserRepresentation user2 = adminClient.realm(TEST).users().search("user2").get(0); + UserRepresentation user3 = adminClient.realm(TEST).users().search("user3").get(0); + UserRepresentation user4 = adminClient.realm(TEST).users().search("user4").get(0); + RoleRepresentation realmRole = adminClient.realm(TEST).roles().get("realm-role").toRepresentation(); + List realmRoleSet = new LinkedList<>(); + realmRoleSet.add(realmRole); + RoleRepresentation realmRole2 = adminClient.realm(TEST).roles().get("realm-role2").toRepresentation(); + List realmRole2Set = new LinkedList<>(); + realmRole2Set.add(realmRole); + ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId("role-namespace").get(0); + RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation(); + List clientRoleSet = new LinkedList<>(); + clientRoleSet.add(clientRole); + + + { + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "authorized", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); + List roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll(); + Assert.assertTrue(roles.stream().anyMatch((r) -> { + return r.getName().equals("realm-role"); + })); + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().remove(realmRoleSet); + roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll(); + Assert.assertTrue(roles.stream().noneMatch((r) -> { + return r.getName().equals("realm-role"); + })); + + realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet); + roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + Assert.assertTrue(roles.stream().anyMatch((r) -> { + return r.getName().equals("client-role"); + })); + realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet); + roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + Assert.assertTrue(roles.stream().noneMatch((r) -> { + return r.getName().equals("client-role"); + })); + realmClient.close(); + } + + { + Keycloak realmClient= AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "authorizedComposite", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); + List roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll(); + Assert.assertTrue(roles.stream().anyMatch((r) -> { + return r.getName().equals("realm-role"); + })); + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().remove(realmRoleSet); + roles = adminClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAll(); + Assert.assertTrue(roles.stream().noneMatch((r) -> { + return r.getName().equals("realm-role"); + })); + + realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet); + roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + Assert.assertTrue(roles.stream().anyMatch((r) -> { + return r.getName().equals("client-role"); + })); + realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet); + roles = adminClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + Assert.assertTrue(roles.stream().noneMatch((r) -> { + return r.getName().equals("client-role"); + })); + } + { + Keycloak realmClient= AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "unauthorized", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + try { + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + } + { + Keycloak realmClient= AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "unauthorizedMapper", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + try { + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + } + + } + + +} 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 dc4ef935e7..175e39e98c 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 @@ -1290,8 +1290,13 @@ disable-credentials=Disable Credentials credential-reset-actions=Credential Reset ldap-mappers=LDAP Mappers create-ldap-mapper=Create LDAP mapper - - +map-role-mgmt-scope-description=Policies that decide if an admin can map this role to a user or group +manage-mgmt-scope-description=Policies that decide if an admin can manage this resource or resources +permissions-enabled-role=Permissions Enabled +permissions-enabled-role.tooltip=Whether or not to enable fine grain permissions for this role. Disabling will delete all current permissions that have been set up. +manage-permissions-role.tooltip=Fine grain permissions for managing roles. For example, you can define different policies for who is allowed to map a role. +lookup=Lookup +manage-permissions-users.tooltip=Fine grain permssions for managing all users in realm. You can define different policies for who is allowed to manage users in the realm. diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 5f58e88f08..58db5a09cb 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -2345,6 +2345,24 @@ module.directive('kcTabsAuthentication', function () { } }); +module.directive('kcTabsRole', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-role.html' + } +}); + +module.directive('kcTabsClientRole', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-client-role.html' + } +}); + module.directive('kcTabsUser', function () { return { scope: true, diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js index ecb008de47..7b61a3c9ea 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js @@ -373,7 +373,41 @@ module.config(['$routeProvider', function ($routeProvider) { } }, controller: 'ResourceServerPolicyAggregateDetailCtrl' - }); + }).when('/realms/:realm/roles/:role/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/realm-role-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + } + }, + controller : 'RealmRolePermissionsCtrl' + }).when('/realms/:realm/clients/:client/roles/:role/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/client-role-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + } + }, + controller : 'ClientRolePermissionsCtrl' + }).when('/realms/:realm/users-permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/users-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'UsersPermissionsCtrl' + }) + ; }]); module.directive('kcTabsResourceServer', function () { 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 5d8d4629cf..f9a9ca5116 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 @@ -2353,4 +2353,61 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio $scope.authzRequest.userId = user.id; } -}); \ No newline at end of file +}); + +module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $location, realm, role, RoleManagementPermissions, Client, Notifications) { + console.log('RealmRolePermissionsCtrl'); + $scope.role = role; + $scope.realm = realm; + RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { + $scope.permissions = data; + }); + Client.query({realm: realm.realm, clientId: 'realm-management'}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); + $scope.setEnabled = function() { + var param = { enabled: $scope.permissions.enabled}; + RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param, function(data) { + $scope.permissions = data; + }) + }; + + +}); +module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $location, realm, client, role, RoleManagementPermissions, Client, Notifications) { + console.log('RealmRolePermissionsCtrl'); + $scope.client = client; + $scope.role = role; + $scope.realm = realm; + RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { + $scope.permissions = data; + }); + $scope.setEnabled = function() { + var param = { enabled: $scope.permissions.enabled}; + RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param, function(data) { + $scope.permissions = data; + }) + }; + + +}); + +module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $location, realm, UsersManagementPermissions, Client, Notifications) { + console.log('UsersPermissionsCtrl'); + $scope.realm = realm; + UsersManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { + $scope.permissions = data; + }); + Client.query({realm: realm.realm, clientId: 'realm-management'}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); + $scope.setEnabled = function() { + var param = { enabled: $scope.permissions.enabled}; + UsersManagementPermissions.update({realm: realm.realm, role:role.id}, param, function(data) { + $scope.permissions = data; + }) + }; + + +}); + diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index bcb0d0c211..4f2a62f410 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -1326,6 +1326,21 @@ module.controller('RealmRevocationCtrl', function($scope, Realm, RealmPushRevoca }); +module.controller('RoleTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeRole = function() { + Dialog.confirmDelete($scope.role.name, 'role', function() { + RoleById.remove({ + realm: realm.realm, + role: $scope.role.id + }, function () { + $route.reload(); + Notifications.success("The role has been deleted."); + }); + }); + }; +}); + + module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications, realm, roles, RoleById, filterFilter) { $scope.realm = realm; $scope.roles = roles; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index fe09ebbe28..679a3c9b75 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -841,6 +841,28 @@ module.factory('Role', function($resource) { }); }); +module.factory('RoleManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/management/permissions', { + realm : '@realm', + role : '@role' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('UsersManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users-management-permissions', { + realm : '@realm' + }, { + update: { + method: 'PUT' + } + }); +}); + + module.factory('RoleById', function($resource) { return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role', { realm : '@realm', diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html new file mode 100644 index 0000000000..d2d6c140e1 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html @@ -0,0 +1,41 @@ +
+ + + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled-role.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html new file mode 100644 index 0000000000..860f20fbdb --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html @@ -0,0 +1,39 @@ +
+ + + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html new file mode 100644 index 0000000000..03420d069e --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html @@ -0,0 +1,35 @@ +
+ + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html index de1a76302d..9c3e215a1a 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html @@ -8,9 +8,7 @@
  • {{role.name}}
  • -

    {{:: 'add-role' | translate}}

    -

    {{role.name|capitalize}}

    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html index 97acab869c..3bcaa66060 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html @@ -6,9 +6,7 @@
  • {{:: 'add-role' | translate}}
  • -

    {{role.name|capitalize}}

    -

    {{:: 'add-role' | translate}}

    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html index 4864acf736..f1dd03b839 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html @@ -1,5 +1,6 @@
    -

    {{:: 'users' | translate}}

    + + diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html new file mode 100755 index 0000000000..963718613e --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html @@ -0,0 +1,13 @@ +
    +

    {{:: 'add-role' | translate}}

    +

    {{role.name|capitalize}}

    + + +
    \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html new file mode 100755 index 0000000000..5c2d71decd --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html @@ -0,0 +1,13 @@ +
    +

    {{role.name|capitalize}}

    +

    {{:: 'add-role' | translate}}

    + + +
    \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html new file mode 100755 index 0000000000..8cc17ea8c4 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html @@ -0,0 +1,11 @@ +
    +

    {{:: 'users' | translate}}

    + + +
    \ No newline at end of file From 46ec12c41cba3252a4d537fb50d880b461b9eb5c Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 10 May 2017 14:19:10 -0400 Subject: [PATCH 04/18] fixes --- .../admin/permissions/MgmtPermissions.java | 12 ++++++++++++ .../admin/permissions/RoleMgmtPermissions.java | 6 +++--- .../admin/permissions/UsersPermissions.java | 14 +++++++++----- .../testsuite/admin/FineGrainAdminUnitTest.java | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java index 84aa631558..78e41e373e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java @@ -16,6 +16,7 @@ */ package org.keycloak.authorization.admin.permissions; +import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.common.KeycloakIdentity; @@ -59,6 +60,17 @@ public class MgmtPermissions { } + public ClientModel getRealmManagementClient() { + ClientModel client = null; + if (realm.getName().equals(Config.getAdminRealm())) { + client = realm.getClientByClientId(Config.getAdminRealm() + "-realm"); + } else { + client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + + } + return client; + } + public boolean isAdminSameRealm() { return auth == null || realm.getId().equals(auth.getRealm().getId()); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java index 49241310fd..3c784cedf7 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java @@ -17,6 +17,7 @@ package org.keycloak.authorization.admin.permissions; import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.Decision; import org.keycloak.authorization.common.DefaultEvaluationContext; @@ -153,19 +154,18 @@ public class RoleMgmtPermissions { } } - private ClientModel getRoleClient(RoleModel role) { ClientModel client = null; if (role.getContainer() instanceof ClientModel) { client = (ClientModel)role.getContainer(); } else { - client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + client = root.getRealmManagementClient(); } return client; } public Policy manageUsersPolicy(ResourceServer server) { - RoleModel role = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).getRole(AdminRoles.MANAGE_USERS); + RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_USERS); return rolePolicy(server, role); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java index 3f6b72a3e2..d9a16aef73 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java +++ b/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java @@ -17,6 +17,7 @@ package org.keycloak.authorization.admin.permissions; import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.Decision; import org.keycloak.authorization.common.DefaultEvaluationContext; @@ -29,6 +30,7 @@ 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.store.ResourceServerStore; import org.keycloak.authorization.util.Permissions; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; @@ -66,8 +68,9 @@ public class UsersPermissions { this.root = root; } + private void initialize() { - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ClientModel client = root.getRealmManagementClient(); ResourceServer server = root.findOrCreateResourceServer(client); Scope manageScope = authz.getStoreFactory().getScopeStore().findByName(MgmtPermissions.MANAGE_SCOPE, server.getId()); if (manageScope == null) { @@ -101,7 +104,7 @@ public class UsersPermissions { } public void setPermissionsEnabled(boolean enable) { - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ClientModel client = root.getRealmManagementClient(); if (enable) { initialize(); } else { @@ -137,7 +140,7 @@ public class UsersPermissions { } private ResourceServer getRealmManagementResourceServer() { - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ClientModel client = root.getRealmManagementClient(); return root.findOrCreateResourceServer(client); } @@ -147,7 +150,7 @@ public class UsersPermissions { auth.init(RealmAuth.Resource.USER); return auth.hasManage(); } else { - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ClientModel client = root.getRealmManagementClient(); RoleModel manageUsers = client.getRole(AdminRoles.MANAGE_USERS); return admin.hasRole(manageUsers); } @@ -232,7 +235,8 @@ public class UsersPermissions { } public ResourceServer resourceServer() { - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ResourceServerStore resourceServerStore = authz.getStoreFactory().getResourceServerStore(); + ClientModel client = root.getRealmManagementClient(); return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index add91cc43c..910d139bb0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -220,7 +220,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } - @Test + //@Test public void testUI() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupPolices); testingClient.server().run(FineGrainAdminUnitTest::setupUsers); From bfb4395b28da770297cf5e85a3ce7fb52197f0d1 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 10 May 2017 15:25:04 -0400 Subject: [PATCH 05/18] role and Users finished UI work --- .../testsuite/admin/FineGrainAdminUnitTest.java | 2 +- .../admin/messages/admin-messages_en.properties | 5 +++-- .../theme/base/admin/resources/js/app.js | 9 +++++++++ .../resources/js/authz/authz-controller.js | 17 ++++++----------- .../admin/resources/js/controllers/clients.js | 1 + .../authz/mgmt/realm-role-permissions.html | 4 ++-- .../partials/authz/mgmt/users-permissions.html | 6 +++--- 7 files changed, 25 insertions(+), 19 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 910d139bb0..add91cc43c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -220,7 +220,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } - //@Test + @Test public void testUI() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupPolices); testingClient.server().run(FineGrainAdminUnitTest::setupUsers); 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 db61ff7a4c..5b0ee31384 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 @@ -1297,11 +1297,12 @@ create-ldap-mapper=Create LDAP mapper map-role-mgmt-scope-description=Policies that decide if an admin can map this role to a user or group manage-mgmt-scope-description=Policies that decide if an admin can manage this resource or resources permissions-enabled-role=Permissions Enabled -permissions-enabled-role.tooltip=Whether or not to enable fine grain permissions for this role. Disabling will delete all current permissions that have been set up. +permissions-enabled-role.tooltip=Whether or not to enable fine grain permissions for managing this role. Disabling will delete all current permissions that have been set up. manage-permissions-role.tooltip=Fine grain permissions for managing roles. For example, you can define different policies for who is allowed to map a role. lookup=Lookup manage-permissions-users.tooltip=Fine grain permssions for managing all users in realm. You can define different policies for who is allowed to manage users in the realm. - +permissions-enabled-users=Permissions Enabled +permissions-enabled-users.tooltip=Whether or not to enable fine grain permissions for managing users. Disabling will delete all current permissions that have been set up. diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 734040e0fc..718d5feb64 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -2376,6 +2376,15 @@ module.directive('kcTabsUser', function () { } }); +module.directive('kcTabsUsers', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-users.html' + } +}); + module.directive('kcTabsGroup', function () { return { scope: true, 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 f9a9ca5116..69c4ec2605 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 @@ -2367,9 +2367,7 @@ module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $l }); $scope.setEnabled = function() { var param = { enabled: $scope.permissions.enabled}; - RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param, function(data) { - $scope.permissions = data; - }) + $scope.permissions= RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param); }; @@ -2384,9 +2382,7 @@ module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $ }); $scope.setEnabled = function() { var param = { enabled: $scope.permissions.enabled}; - RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param, function(data) { - $scope.permissions = data; - }) + $scope.permissions = RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param); }; @@ -2395,17 +2391,16 @@ module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $ module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $location, realm, UsersManagementPermissions, Client, Notifications) { console.log('UsersPermissionsCtrl'); $scope.realm = realm; - UsersManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { + UsersManagementPermissions.get({realm: realm.realm}, function(data) { $scope.permissions = data; }); Client.query({realm: realm.realm, clientId: 'realm-management'}, function(data) { $scope.realmManagementClientId = data[0].id; }); - $scope.setEnabled = function() { + $scope.changeIt = function() { + console.log('before permissions.enabled=' + $scope.permissions.enabled); var param = { enabled: $scope.permissions.enabled}; - UsersManagementPermissions.update({realm: realm.realm, role:role.id}, param, function(data) { - $scope.permissions = data; - }) + $scope.permissions = UsersManagementPermissions.update({realm: realm.realm}, param); }; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 315b8a53cb..945fc5a699 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -1387,6 +1387,7 @@ module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, clien } $scope.changeFlag = function() { + console.log('changeFlag'); Client.update({ realm : realm.realm, client : client.id diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html index 860f20fbdb..82c841343b 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html @@ -9,11 +9,11 @@
    - +
    - {{:: 'permissions-enabled.tooltip' | translate}} + {{:: 'permissions-enabled-role.tooltip' | translate}}
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html index 03420d069e..92a90678dc 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html @@ -5,11 +5,11 @@
    - +
    - +
    - {{:: 'permissions-enabled.tooltip' | translate}} + {{:: 'permissions-enabled-users.tooltip' | translate}}
    From f114895cd283f9ab23d4f9d48cefc9f361a2292f Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 19 May 2017 11:29:26 -0400 Subject: [PATCH 06/18] for merge --- .../admin/ClientRoleMappingsResource.java | 12 ++++++++-- .../resources/admin/RoleMapperResource.java | 13 ++++++++-- .../admin/FineGrainAdminUnitTest.java | 24 +++++++++++++------ .../resources/js/authz/authz-controller.js | 12 ++++++++-- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java index 8cc456fd55..b5f199689e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java @@ -49,6 +49,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; /** * @resource Client Role Mappings @@ -150,6 +151,9 @@ public class ClientRoleMappingsResource { } Set available = client.getRoles(); + available = available.stream().filter(r -> + canMapRole(r) + ).collect(Collectors.toSet()); return getAvailableRoles(user, available); } @@ -205,12 +209,16 @@ public class ClientRoleMappingsResource { } private void checkMapRolePermission(RoleModel roleModel) { - if (!new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel)) { + if (!canMapRole(roleModel)) { throw new ForbiddenException(); } } - /** + private boolean canMapRole(RoleModel roleModel) { + return new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel); + } + + /** * Delete client-level roles from user role mapping * * @param roles diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java index 51b1847e75..93d4cb6223 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java @@ -52,11 +52,13 @@ import javax.ws.rs.core.UriInfo; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; /** * Base resource for managing users @@ -230,7 +232,10 @@ public class RoleMapperResource { } Set available = realm.getRoles(); - return ClientRoleMappingsResource.getAvailableRoles(roleMapper, available); + Set set = available.stream().filter(r -> + canMapRole(r) + ).collect(Collectors.toSet()); + return ClientRoleMappingsResource.getAvailableRoles(roleMapper, set); } /** @@ -321,11 +326,15 @@ public class RoleMapperResource { } private void checkMapRolePermission(RoleModel roleModel) { - if (!new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel)) { + if (!canMapRole(roleModel)) { throw new ForbiddenException(); } } + private boolean canMapRole(RoleModel roleModel) { + return new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel); + } + @Path("clients/{client}") public ClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("client") String client) { ClientModel clientModel = realm.getClientById(client); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index add91cc43c..e1488e02cc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -87,17 +87,17 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { // realm-role and role-namespace.client-role will have a role policy associated with their map-role permission { - permissions.roles().setPermissionsEnabled(realmRole, true); - Policy mapRolePermission = permissions.roles().mapRolePermission(realmRole); - ResourceServer server = permissions.roles().resourceServer(realmRole); + permissions.roles().setPermissionsEnabled(client1Role, true); + Policy mapRolePermission = permissions.roles().mapRolePermission(client1Role); + ResourceServer server = permissions.roles().resourceServer(client1Role); Policy mapperPolicy = permissions.roles().rolePolicy(server, mapperRole); mapRolePermission.addAssociatedPolicy(mapperPolicy); } { - permissions.roles().setPermissionsEnabled(client1Role, true); - Policy mapRolePermission = permissions.roles().mapRolePermission(client1Role); - ResourceServer server = permissions.roles().resourceServer(client1Role); + permissions.roles().setPermissionsEnabled(realmRole, true); + Policy mapRolePermission = permissions.roles().mapRolePermission(realmRole); + ResourceServer server = permissions.roles().resourceServer(realmRole); Policy mapperPolicy = permissions.roles().rolePolicy(server, mapperRole); mapRolePermission.addAssociatedPolicy(mapperPolicy); } @@ -128,6 +128,13 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleModel mapperRole = realm.getRole("mapper"); RoleModel managerRole = realm.getRole("manager"); RoleModel compositeRole = realm.getRole("composite-role"); + ClientModel realmManagementClient = realm.getClientByClientId("realm-management"); + RoleModel adminRole = realmManagementClient.getRole(AdminRoles.REALM_ADMIN); + + UserModel nomapAdmin = session.users().addUser(realm, "nomap-admin"); + nomapAdmin.setEnabled(true); + session.userCredentialManager().updateCredential(realm, nomapAdmin, UserCredentialModel.password("password")); + nomapAdmin.grantRole(adminRole); UserModel authorizedUser = session.users().addUser(realm, "authorized"); authorizedUser.setEnabled(true); @@ -220,7 +227,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } - @Test + //@Test public void testUI() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupPolices); testingClient.server().run(FineGrainAdminUnitTest::setupUsers); @@ -332,5 +339,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } + // testRestEvaluationMasterRealm + // testRestEvaluationMasterAdminTestRealm + } 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 69c4ec2605..32d07a8891 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 @@ -2355,6 +2355,14 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio } }); +getManageClientId = function(realm) { + if (realm.realm == masterRealm) { + return 'master-realm'; + } else { + return 'realm-management'; + } +} + module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $location, realm, role, RoleManagementPermissions, Client, Notifications) { console.log('RealmRolePermissionsCtrl'); $scope.role = role; @@ -2362,7 +2370,7 @@ module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $l RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { $scope.permissions = data; }); - Client.query({realm: realm.realm, clientId: 'realm-management'}, function(data) { + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { $scope.realmManagementClientId = data[0].id; }); $scope.setEnabled = function() { @@ -2394,7 +2402,7 @@ module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $locat UsersManagementPermissions.get({realm: realm.realm}, function(data) { $scope.permissions = data; }); - Client.query({realm: realm.realm, clientId: 'realm-management'}, function(data) { + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { $scope.realmManagementClientId = data[0].id; }); $scope.changeIt = function() { From c3ea847b3eb2d9081279ed168c2a5704216f3139 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 29 May 2017 09:53:17 -0400 Subject: [PATCH 07/18] auth changes --- .../client/resource/ClientsResource.java | 4 + .../admin/AuthorizationService.java | 10 +- .../admin/PermissionService.java | 4 +- .../admin/PolicyEvaluationService.java | 10 +- .../admin/PolicyResourceService.java | 20 +- .../authorization/admin/PolicyService.java | 16 +- .../admin/PolicyTypeResourceService.java | 4 +- .../admin/PolicyTypeService.java | 4 +- .../admin/ResourceServerService.java | 20 +- .../admin/ResourceSetService.java | 12 +- .../authorization/admin/ScopeService.java | 22 +- .../admin/permissions/MgmtPermissions.java | 118 ------ .../permissions/RoleMgmtPermissions.java | 208 ---------- .../admin/permissions/UsersPermissions.java | 246 ----------- .../services/resources/admin/AdminAuth.java | 3 + .../admin/AttackDetectionResource.java | 17 +- .../AuthenticationManagementResource.java | 68 ++-- .../ClientAttributeCertificateResource.java | 43 +- .../admin/ClientInitialAccessResource.java | 12 +- .../ClientRegistrationPolicyResource.java | 6 +- .../resources/admin/ClientResource.java | 113 ++---- .../admin/ClientRoleMappingsResource.java | 63 +-- .../admin/ClientTemplateResource.java | 31 +- .../admin/ClientTemplatesResource.java | 25 +- .../resources/admin/ClientsResource.java | 47 ++- .../resources/admin/ComponentResource.java | 23 +- .../resources/admin/GroupResource.java | 40 +- .../resources/admin/GroupsResource.java | 15 +- .../admin/IdentityProviderResource.java | 25 +- .../admin/IdentityProvidersResource.java | 18 +- .../services/resources/admin/KeyResource.java | 7 +- .../admin/ProtocolMappersResource.java | 58 +-- .../resources/admin/RealmAdminResource.java | 85 ++-- .../resources/admin/RealmsAdminResource.java | 10 +- .../resources/admin/RoleByIdResource.java | 63 ++- .../admin/RoleContainerResource.java | 109 ++--- .../resources/admin/RoleMapperResource.java | 92 ++--- .../resources/admin/RoleResource.java | 4 +- .../admin/ScopeMappedClientResource.java | 43 +- .../resources/admin/ScopeMappedResource.java | 35 +- .../admin/UserStorageProviderResource.java | 15 +- .../resources/admin/UsersResource.java | 98 +++-- .../permissions/AdminPermissionEvaluator.java | 55 +++ .../AdminPermissionManagement.java | 35 ++ .../admin/permissions/AdminPermissions.java | 43 ++ .../ClientPermissionEvaluator.java | 68 ++++ .../ClientPermissionManagement.java | 29 ++ .../admin/permissions/ClientPermissions.java | 309 ++++++++++++++ .../permissions/GroupPermissionEvaluator.java | 53 +++ .../GroupPermissionManagement.java | 37 ++ .../admin/permissions/GroupPermissions.java | 383 ++++++++++++++++++ .../resources}/admin/permissions/Helper.java | 4 +- .../admin/permissions/MgmtPermissions.java | 274 +++++++++++++ .../admin/{ => permissions}/RealmAuth.java | 23 +- .../permissions/RealmPermissionEvaluator.java | 59 +++ .../admin/permissions/RealmPermissions.java | 188 +++++++++ .../permissions/RolePermissionEvaluator.java | 47 +++ .../permissions/RolePermissionManagement.java | 56 +++ .../admin/permissions/RolePermissions.java | 378 +++++++++++++++++ .../permissions/UserPermissionEvaluator.java | 46 +++ .../permissions/UserPermissionManagement.java | 41 ++ .../admin/permissions/UserPermissions.java | 382 +++++++++++++++++ .../admin/FineGrainAdminUnitTest.java | 40 +- .../testsuite/admin/PermissionsTest.java | 203 ++++++---- 64 files changed, 3150 insertions(+), 1469 deletions(-) delete mode 100644 services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java delete mode 100644 services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java delete mode 100644 services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionEvaluator.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java rename services/src/main/java/org/keycloak/{authorization => services/resources}/admin/permissions/Helper.java (98%) create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java rename services/src/main/java/org/keycloak/services/resources/admin/{ => permissions}/RealmAuth.java (86%) create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissionEvaluator.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionManagement.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java index 47b4db4a01..3c8552efe1 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientsResource.java @@ -46,6 +46,10 @@ public interface ClientsResource { @Produces(MediaType.APPLICATION_JSON) public List findAll(); + @GET + @Produces(MediaType.APPLICATION_JSON) + public List findAll(@QueryParam("viewableOnly") boolean viewableOnly); + @GET @Produces(MediaType.APPLICATION_JSON) public List findByClientId(@QueryParam("clientId") String clientId); diff --git a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java index a4ff868276..d062856e2e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java @@ -23,7 +23,7 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Path; @@ -32,22 +32,18 @@ import javax.ws.rs.Path; */ public class AuthorizationService { - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private final ClientModel client; private final KeycloakSession session; private final ResourceServer resourceServer; private final AuthorizationProvider authorization; - public AuthorizationService(KeycloakSession session, ClientModel client, RealmAuth auth) { + public AuthorizationService(KeycloakSession session, ClientModel client, AdminPermissionEvaluator auth) { this.session = session; this.client = client; this.authorization = session.getProvider(AuthorizationProvider.class); this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().findByClient(this.client.getId()); this.auth = auth; - - if (auth != null) { - this.auth.init(RealmAuth.Resource.AUTHORIZATION); - } } @Path("/resource-server") diff --git a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java index 4ada87d316..e7f463302f 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java @@ -22,14 +22,14 @@ import java.util.Map; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; /** * @author Pedro Igor */ public class PermissionService extends PolicyService { - public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { super(resourceServer, authorization, auth); } 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 24e1a8943e..0a913dda03 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java @@ -63,14 +63,12 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.protocol.ProtocolMapper; -import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; 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.managers.AuthenticationManager; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.sessions.AuthenticationSessionModel; /** @@ -79,13 +77,13 @@ import org.keycloak.sessions.AuthenticationSessionModel; public class PolicyEvaluationService { private final AuthorizationProvider authorization; - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; @Context private HttpRequest httpRequest; private final ResourceServer resourceServer; - PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; @@ -117,7 +115,7 @@ public class PolicyEvaluationService { @Consumes("application/json") @Produces("application/json") public Response evaluate(PolicyEvaluationRequest evaluationRequest) throws Throwable { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); CloseableKeycloakIdentity identity = createIdentity(evaluationRequest); try { EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest, identity); diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java index 85e0943221..51bc2a6e6f 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java @@ -42,7 +42,7 @@ import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentati import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.util.JsonSerialization; /** @@ -53,9 +53,9 @@ public class PolicyResourceService { private final Policy policy; protected final ResourceServer resourceServer; protected final AuthorizationProvider authorization; - protected final RealmAuth auth; + protected final AdminPermissionEvaluator auth; - public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { this.policy = policy; this.resourceServer = resourceServer; this.authorization = authorization; @@ -67,7 +67,7 @@ public class PolicyResourceService { @Produces("application/json") @NoCache public Response update(String payload) { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); AbstractPolicyRepresentation representation = doCreateRepresentation(payload); @@ -84,7 +84,7 @@ public class PolicyResourceService { @DELETE public Response delete() { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); @@ -105,7 +105,7 @@ public class PolicyResourceService { @Produces("application/json") @NoCache public Response findById() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); @@ -123,7 +123,7 @@ public class PolicyResourceService { @Produces("application/json") @NoCache public Response getDependentPolicies() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); @@ -147,7 +147,7 @@ public class PolicyResourceService { @Produces("application/json") @NoCache public Response getScopes() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); @@ -168,7 +168,7 @@ public class PolicyResourceService { @Produces("application/json") @NoCache public Response getResources() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); @@ -189,7 +189,7 @@ public class PolicyResourceService { @Produces("application/json") @NoCache public Response getAssociatedPolicies() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index 011aa2d3a1..6ebed116c9 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -52,7 +52,7 @@ import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentati import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.services.ErrorResponseException; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.util.JsonSerialization; /** @@ -62,9 +62,9 @@ public class PolicyService { protected final ResourceServer resourceServer; protected final AuthorizationProvider authorization; - protected final RealmAuth auth; + protected final AdminPermissionEvaluator auth; - public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; @@ -92,7 +92,7 @@ public class PolicyService { @Produces(MediaType.APPLICATION_JSON) @NoCache public Response create(String payload) { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); AbstractPolicyRepresentation representation = doCreateRepresentation(payload); Policy policy = create(representation); @@ -130,7 +130,7 @@ public class PolicyService { @Produces(MediaType.APPLICATION_JSON) @NoCache public Response findByName(@QueryParam("name") String name) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); StoreFactory storeFactory = authorization.getStoreFactory(); if (name == null) { @@ -157,7 +157,7 @@ public class PolicyService { @QueryParam("permission") Boolean permission, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResult) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); Map search = new HashMap<>(); @@ -236,7 +236,7 @@ public class PolicyService { @Produces(MediaType.APPLICATION_JSON) @NoCache public Response findPolicyProviders() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); return Response.ok( authorization.getProviderFactories().stream() .map(provider -> { @@ -254,7 +254,7 @@ public class PolicyService { @Path("evaluate") public PolicyEvaluationService getPolicyEvaluateResource() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization, this.auth); ResteasyProviderFactory.getInstance().injectProperties(resource); diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java index 8756721e9d..71a66956c0 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java @@ -24,7 +24,7 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.util.JsonSerialization; /** @@ -32,7 +32,7 @@ import org.keycloak.util.JsonSerialization; */ public class PolicyTypeResourceService extends PolicyResourceService { - public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { super(policy, resourceServer, authorization, auth); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java index c2e4db5c55..d6868c5e5d 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java @@ -28,7 +28,7 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.util.JsonSerialization; /** @@ -38,7 +38,7 @@ public class PolicyTypeService extends PolicyService { private final String type; - PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { super(resourceServer, authorization, auth); this.type = type; } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java index 15f1db7a52..a9dc6e04a4 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java @@ -53,7 +53,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; /** * @author Pedro Igor @@ -61,12 +61,12 @@ import org.keycloak.services.resources.admin.RealmAuth; public class ResourceServerService { private final AuthorizationProvider authorization; - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private final KeycloakSession session; private ResourceServer resourceServer; private final ClientModel client; - public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, RealmAuth auth) { + public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, AdminPermissionEvaluator auth) { this.authorization = authorization; this.session = authorization.getKeycloakSession(); this.client = client; @@ -75,7 +75,7 @@ public class ResourceServerService { } public void create() { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); UserModel serviceAccount = this.session.users().getServiceAccount(client); @@ -92,7 +92,7 @@ public class ResourceServerService { @Consumes("application/json") @Produces("application/json") public Response update(ResourceServerRepresentation server) { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); this.resourceServer.setAllowRemoteResourceManagement(server.isAllowRemoteResourceManagement()); this.resourceServer.setPolicyEnforcementMode(server.getPolicyEnforcementMode()); @@ -100,7 +100,7 @@ public class ResourceServerService { } public void delete() { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); StoreFactory storeFactory = authorization.getStoreFactory(); ResourceStore resourceStore = storeFactory.getResourceStore(); String id = resourceServer.getId(); @@ -121,7 +121,7 @@ public class ResourceServerService { @GET @Produces("application/json") public Response findById() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); return Response.ok(toRepresentation(this.resourceServer, this.client)).build(); } @@ -129,7 +129,7 @@ public class ResourceServerService { @GET @Produces("application/json") public Response exportSettings() { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); return Response.ok(ExportUtils.exportAuthorizationSettings(session, client)).build(); } @@ -137,7 +137,7 @@ public class ResourceServerService { @POST @Consumes(MediaType.APPLICATION_JSON) public Response importSettings(@Context final UriInfo uriInfo, ResourceServerRepresentation rep) throws IOException { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); rep.setClientId(client.getId()); @@ -175,7 +175,7 @@ public class ResourceServerService { @Path("/permission") public Object getPermissionTypeResource() { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth); ResteasyProviderFactory.getInstance().injectProperties(resource); diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index 5d6592366d..c8eaafe04c 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -37,7 +37,7 @@ import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentatio import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponse; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -69,10 +69,10 @@ import static org.keycloak.models.utils.RepresentationToModel.toModel; public class ResourceSetService { private final AuthorizationProvider authorization; - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private ResourceServer resourceServer; - public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; @@ -272,7 +272,7 @@ public class ResourceSetService { @Produces("application/json") @NoCache public Response find(@QueryParam("name") String name) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); StoreFactory storeFactory = authorization.getStoreFactory(); if (name == null) { @@ -367,13 +367,13 @@ public class ResourceSetService { private void requireView() { if (this.auth != null) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); } } private void requireManage() { if (this.auth != null) { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); } } } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index 1ec9a139e0..157b3a0f37 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -30,7 +30,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponse; -import org.keycloak.services.resources.admin.RealmAuth; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -60,10 +60,10 @@ import static org.keycloak.models.utils.RepresentationToModel.toModel; public class ScopeService { private final AuthorizationProvider authorization; - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private ResourceServer resourceServer; - public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { + public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) { this.resourceServer = resourceServer; this.authorization = authorization; this.auth = auth; @@ -73,7 +73,7 @@ public class ScopeService { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response create(ScopeRepresentation scope) { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); Scope model = toModel(scope, this.resourceServer, authorization); scope.setId(model.getId()); @@ -86,7 +86,7 @@ public class ScopeService { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response update(@PathParam("id") String id, ScopeRepresentation scope) { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); scope.setId(id); StoreFactory storeFactory = authorization.getStoreFactory(); Scope model = storeFactory.getScopeStore().findById(scope.getId(), resourceServer.getId()); @@ -103,7 +103,7 @@ public class ScopeService { @Path("{id}") @DELETE public Response delete(@PathParam("id") String id) { - this.auth.requireManage(); + this.auth.realm().requireManageAuthorization(); StoreFactory storeFactory = authorization.getStoreFactory(); List resources = storeFactory.getResourceStore().findByScope(Arrays.asList(id), resourceServer.getId()); @@ -137,7 +137,7 @@ public class ScopeService { @GET @Produces(MediaType.APPLICATION_JSON) public Response findById(@PathParam("id") String id) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); Scope model = this.authorization.getStoreFactory().getScopeStore().findById(id, resourceServer.getId()); if (model == null) { @@ -151,7 +151,7 @@ public class ScopeService { @GET @Produces(MediaType.APPLICATION_JSON) public Response getResources(@PathParam("id") String id) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); StoreFactory storeFactory = this.authorization.getStoreFactory(); Scope model = storeFactory.getScopeStore().findById(id, resourceServer.getId()); @@ -173,7 +173,7 @@ public class ScopeService { @GET @Produces(MediaType.APPLICATION_JSON) public Response getPermissions(@PathParam("id") String id) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); StoreFactory storeFactory = this.authorization.getStoreFactory(); Scope model = storeFactory.getScopeStore().findById(id, resourceServer.getId()); @@ -199,7 +199,7 @@ public class ScopeService { @Produces(MediaType.APPLICATION_JSON) @NoCache public Response find(@QueryParam("name") String name) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); StoreFactory storeFactory = authorization.getStoreFactory(); if (name == null) { @@ -222,7 +222,7 @@ public class ScopeService { @QueryParam("deep") Boolean deep, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResult) { - this.auth.requireView(); + this.auth.realm().requireViewAuthorization(); if (deep == null) { deep = true; diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java deleted file mode 100644 index 78e41e373e..0000000000 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/MgmtPermissions.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.admin.permissions; - -import org.keycloak.Config; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.AuthorizationProviderFactory; -import org.keycloak.authorization.common.KeycloakIdentity; -import org.keycloak.authorization.common.UserModelIdentity; -import org.keycloak.authorization.identity.Identity; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.models.ClientModel; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.representations.AccessToken; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.admin.AdminAuth; -import org.keycloak.services.resources.admin.RealmAuth; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class MgmtPermissions { - public static final String MANAGE_SCOPE = "manage"; - - protected RealmModel realm; - protected KeycloakSession session; - protected AuthorizationProvider authz; - protected AdminAuth auth; - protected Identity identity; - - public MgmtPermissions(KeycloakSession session, RealmModel realm) { - this.session = session; - this.realm = realm; - KeycloakSessionFactory keycloakSessionFactory = session.getKeycloakSessionFactory(); - AuthorizationProviderFactory factory = (AuthorizationProviderFactory) keycloakSessionFactory.getProviderFactory(AuthorizationProvider.class); - this.authz = factory.create(session, realm); - } - public MgmtPermissions(KeycloakSession session, RealmModel realm, AdminAuth auth) { - this(session, realm); - this.auth = auth; - - } - - public ClientModel getRealmManagementClient() { - ClientModel client = null; - if (realm.getName().equals(Config.getAdminRealm())) { - client = realm.getClientByClientId(Config.getAdminRealm() + "-realm"); - } else { - client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); - - } - return client; - } - - public boolean isAdminSameRealm() { - return auth == null || realm.getId().equals(auth.getRealm().getId()); - } - - public RealmAuth getRealmAuth() { - if (auth == null) return null; - RealmManager realmManager = new RealmManager(session); - if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) { - return new RealmAuth(auth, realm.getMasterAdminClient()); - } else { - return new RealmAuth(auth, realm.getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm()))); - } - } - - public Identity identity() { - if (identity != null) return identity; - if (auth.getClient().getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID)) { - this.identity = new UserModelIdentity(realm, auth.getUser()); - - } else { - this.identity = new KeycloakIdentity(auth.getToken(), session); - } - return this.identity; - } - - public void setIdentity(UserModel user) { - this.identity = new UserModelIdentity(realm, user); - } - - - public RoleMgmtPermissions roles() { - return new RoleMgmtPermissions(session, realm, authz, this); - } - - public UsersPermissions users() { return new UsersPermissions(session, realm, authz, this); } - - public ResourceServer findOrCreateResourceServer(ClientModel client) { - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); - if (server == null) { - server = authz.getStoreFactory().getResourceServerStore().create(client.getId()); - } - return server; - } - -} diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java deleted file mode 100644 index 271ef0630a..0000000000 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/RoleMgmtPermissions.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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.admin.permissions; - -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.Decision; -import org.keycloak.authorization.common.DefaultEvaluationContext; -import org.keycloak.authorization.identity.Identity; -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.store.ResourceStore; -import org.keycloak.authorization.util.Permissions; -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 java.util.List; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class RoleMgmtPermissions { - private static final Logger logger = Logger.getLogger(RoleMgmtPermissions.class); - public static final String MAP_ROLE_SCOPE = "map-role"; - protected final KeycloakSession session; - protected final RealmModel realm; - protected final AuthorizationProvider authz; - protected final MgmtPermissions root; - - public RoleMgmtPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { - this.session = session; - this.realm = realm; - this.authz = authz; - this.root = root; - } - - public boolean isPermissionsEnabled(RoleModel role) { - return mapRolePermission(role) != null; - } - - public void setPermissionsEnabled(RoleModel role, boolean enable) { - if (enable) { - ResourceServer server = getResourceServer(role); - if (authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()) != null) { - return; - } - createResource(role); - } else { - ResourceServer server = resourceServer(role); - if (server == null) return; - Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); - if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), server.getId()); - if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId()); - } - } - - public Policy mapRolePermission(RoleModel role) { - ResourceServer server = resourceServer(role); - if (server == null) return null; - - Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); - if (resource == null) return null; - - return authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), server.getId()); - } - - public Resource resource(RoleModel role) { - ResourceStore resourceStore = authz.getStoreFactory().getResourceStore(); - ResourceServer server = resourceServer(role); - if (server == null) return null; - return resourceStore.findByName(getRoleResourceName(role), server.getId()); - } - - public ResourceServer resourceServer(RoleModel role) { - ClientModel client = getRoleClient(role); - return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); - } - - /** - * Is admin allowed to map this role? In Authz terms, does the user have the "map-role" scope for the role's Authz resource? - * - * This method is hardcoded to return TRUE if any of these conditions are met: - * - The admin is from the master realm managing a different realm - * - If the Authz objects are not set up correctly for this role (resource server, resource, permission) - * - If the role's mapRole permission does not have a policy associated with it. - * - * Otherwise, it will use the Authz policy engine to resolve this answer. - * - * @param role - * @return - */ - public boolean canMapRole(RoleModel role) { - if (!root.isAdminSameRealm()) { - return true; - } - if (!isPermissionsEnabled(role)) return true; // no authz permissions set up so just allow it. - - ResourceServer resourceServer = getResourceServer(role); - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleScopePermissionName(role), resourceServer.getId()); - if (policy.getAssociatedPolicies().isEmpty()) { - return true; // if no policies applied, just ignore - } - - RealmModel oldRealm = session.getContext().getRealm(); - try { - session.getContext().setRealm(realm); - Identity identity = root.identity(); - - EvaluationContext context = new DefaultEvaluationContext(identity, session); - DecisionResult decisionCollector = new DecisionResult(); - Resource roleResource = resource(role); - Scope mapRoleScope = getMapRoleScope(resourceServer); - - List permissions = Permissions.permission(resourceServer, roleResource, mapRoleScope); - PermissionEvaluator from = authz.evaluators().from(permissions, context); - from.evaluate(decisionCollector); - if (!decisionCollector.completed()) { - logger.error("Failed to run map role policy check", decisionCollector.getError()); - return false; - } - return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; - } finally { - session.getContext().setRealm(oldRealm); - } - } - - private ClientModel getRoleClient(RoleModel role) { - ClientModel client = null; - if (role.getContainer() instanceof ClientModel) { - client = (ClientModel)role.getContainer(); - } else { - client = root.getRealmManagementClient(); - } - return client; - } - - public Policy manageUsersPolicy(ResourceServer server) { - RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_USERS); - return rolePolicy(server, role); - } - - public Policy rolePolicy(ResourceServer server, RoleModel role) { - String policyName = Helper.getRolePolicyName(role); - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(policyName, server.getId()); - if (policy != null) return policy; - return Helper.createRolePolicy(authz, server, role, policyName); - } - - private Scope getMapRoleScope(ResourceServer server) { - Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_SCOPE, server.getId()); - if (scope == null) { - scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_SCOPE, server); - } - return scope; - } - - - private Resource createResource(RoleModel role) { - ResourceServer server = getResourceServer(role); - Resource resource = authz.getStoreFactory().getResourceStore().create(getRoleResourceName(role), server, server.getClientId()); - resource.setType("Role"); - Scope mapRoleScope = getMapRoleScope(server); - Helper.addEmptyScopePermission(authz, server, getMapRoleScopePermissionName(role), resource, mapRoleScope); - return resource; - } - - private String getMapRoleScopePermissionName(RoleModel role) { - return MAP_ROLE_SCOPE + ".permission." + role.getName(); - } - - private ResourceServer getResourceServer(RoleModel role) { - ClientModel client = getRoleClient(role); - return root.findOrCreateResourceServer(client); - } - - private static String getRoleResourceName(RoleModel role) { - return "role.resource." + role.getName(); - } - - -} diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java b/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java deleted file mode 100644 index c5698d97c7..0000000000 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/UsersPermissions.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.admin.permissions; - -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.Decision; -import org.keycloak.authorization.common.DefaultEvaluationContext; -import org.keycloak.authorization.identity.Identity; -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.store.ResourceServerStore; -import org.keycloak.authorization.util.Permissions; -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.services.resources.admin.RealmAuth; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Manages default policies for all users. - * - * - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class UsersPermissions { - private static final Logger logger = Logger.getLogger(UsersPermissions.class); - public static final String MANAGE_PERMISSION_USERS = "manage.permission.users"; - public static final String USERS_RESOURCE = "Users"; - protected final KeycloakSession session; - protected final RealmModel realm; - protected final AuthorizationProvider authz; - protected final MgmtPermissions root; - - public UsersPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { - this.session = session; - this.realm = realm; - this.authz = authz; - this.root = root; - } - - - private void initialize() { - ClientModel client = root.getRealmManagementClient(); - ResourceServer server = root.findOrCreateResourceServer(client); - Scope manageScope = authz.getStoreFactory().getScopeStore().findByName(MgmtPermissions.MANAGE_SCOPE, server.getId()); - if (manageScope == null) { - manageScope = authz.getStoreFactory().getScopeStore().create(MgmtPermissions.MANAGE_SCOPE, server); - - } - Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); - if (usersResource == null) { - usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getClientId()); - } - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); - if (policy == null) { - Set scopeset = new HashSet<>(); - scopeset.add(manageScope); - usersResource.updateScopes(scopeset); - Policy manageUsersPolicy = root.roles().manageUsersPolicy(server); - Helper.addScopePermission(authz, server, MANAGE_PERMISSION_USERS, usersResource, manageScope, manageUsersPolicy); - } - } - - public boolean isPermissionsEnabled() { - ResourceServer server = resourceServer(); - if (server == null) return false; - - Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); - if (resource == null) return false; - - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); - - return policy != null; - } - - public void setPermissionsEnabled(boolean enable) { - ClientModel client = root.getRealmManagementClient(); - if (enable) { - initialize(); - } else { - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); - if (server == null) return; - Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); - if (usersResource == null) { - authz.getStoreFactory().getResourceStore().delete(usersResource.getId()); - } - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); - if (policy == null) { - authz.getStoreFactory().getPolicyStore().delete(policy.getId()); - - } - } - } - - private Resource getUsersResource(ResourceServer server) { - Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); - if (usersResource == null) { - usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getClientId()); - } - return usersResource; - } - - private Scope getManageScope(ResourceServer server) { - Scope manageScope = authz.getStoreFactory().getScopeStore().findByName(MgmtPermissions.MANAGE_SCOPE, server.getId()); - if (manageScope == null) { - manageScope = authz.getStoreFactory().getScopeStore().create(MgmtPermissions.MANAGE_SCOPE, server); - - } - return manageScope; - } - - private ResourceServer getRealmManagementResourceServer() { - ClientModel client = root.getRealmManagementClient(); - return root.findOrCreateResourceServer(client); - } - - private boolean canManageDefault(UserModel admin) { - RealmAuth auth = root.getRealmAuth(); - if (auth != null) { - auth.init(RealmAuth.Resource.USER); - return auth.hasManage(); - } else { - ClientModel client = root.getRealmManagementClient(); - RoleModel manageUsers = client.getRole(AdminRoles.MANAGE_USERS); - return admin.hasRole(manageUsers); - } - - } - - public boolean canManage() { - if (root.getRealmAuth() == null) { - throw new NullPointerException("Realm auth null"); - } - return canManage(root.getRealmAuth().getAuth().getUser()); - } - - public Resource resource() { - ResourceServer server = resourceServer(); - if (server == null) return null; - - return authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); - } - - /** - * Is admin allowed to manage users? In Authz terms, does the admin have the "map-role" scope for the role's Authz resource? - * - * This method will follow the old default behavior (does the admin have the manage-users role) if any of these conditions - * are met.: - * - The admin is from the master realm managing a different realm - * - If the Authz objects are not set up correctly for the Users resource in Authz - * - The "manage" permission for the Users resource has an empty associatedPolicy list. - * - * Otherwise, it will use the Authz policy engine to resolve this answer. - * - * @param admin - * @return - */ - public boolean canManage(UserModel admin) { - if (!root.isAdminSameRealm()) { - return canManageDefault(admin); - } - - ResourceServer server = resourceServer(); - if (server == null) return canManageDefault(admin); - - Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); - if (resource == null) return canManageDefault(admin); - - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); - if (policy == null) { - return canManageDefault(admin); - } - - Set associatedPolicies = policy.getAssociatedPolicies(); - // if no policies attached to permission then just do default behavior - if (associatedPolicies == null || associatedPolicies.isEmpty()) { - return canManageDefault(admin); - } - - RealmModel oldRealm = session.getContext().getRealm(); - try { - session.getContext().setRealm(realm); - Identity identity = root.identity(); - EvaluationContext context = new DefaultEvaluationContext(identity, session); - DecisionResult decisionCollector = new DecisionResult(); - ResourceServer resourceServer = getRealmManagementResourceServer(); - Resource roleResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, resourceServer.getId()); - Scope manageScope = getManageScope(resourceServer); - - List permissions = Permissions.permission(resourceServer, roleResource, manageScope); - PermissionEvaluator from = authz.evaluators().from(permissions, context); - from.evaluate(decisionCollector); - if (!decisionCollector.completed()) { - logger.error("Failed to run Users manage check", decisionCollector.getError()); - return false; - } - return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; - } finally { - session.getContext().setRealm(oldRealm); - } - - } - - public ResourceServer resourceServer() { - ResourceServerStore resourceServerStore = authz.getStoreFactory().getResourceServerStore(); - ClientModel client = root.getRealmManagementClient(); - return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); - } - - public Policy managePermission() { - ResourceServer server = resourceServer(); - return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); - } - - -} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java index 924b35e40c..98d453830f 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java @@ -98,4 +98,7 @@ public class AdminAuth { return false; } + public enum Resource { + CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER, IMPERSONATION, AUTHORIZATION + } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java index 5ac7593e1c..2154ad6380 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java @@ -26,6 +26,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserLoginFailureModel; import org.keycloak.models.UserModel; import org.keycloak.services.managers.BruteForceProtector; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -48,7 +49,7 @@ import java.util.Map; */ public class AttackDetectionResource { protected static final Logger logger = Logger.getLogger(AttackDetectionResource.class); - protected RealmAuth auth; + protected AdminPermissionEvaluator auth; protected RealmModel realm; private AdminEventBuilder adminEvent; @@ -64,12 +65,10 @@ public class AttackDetectionResource { @Context protected HttpHeaders headers; - public AttackDetectionResource(RealmAuth auth, RealmModel realm, AdminEventBuilder adminEvent) { + public AttackDetectionResource(AdminPermissionEvaluator auth, RealmModel realm, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent.realm(realm).resource(ResourceType.USER_LOGIN_FAILURE); - - auth.init(RealmAuth.Resource.USER); } /** @@ -83,7 +82,8 @@ public class AttackDetectionResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Map bruteForceUserStatus(@PathParam("userId") String userId) { - auth.requireView(); + UserModel user = session.users().getUserById(userId, realm); + auth.users().requireView(user); Map data = new HashMap<>(); data.put("disabled", false); @@ -92,7 +92,6 @@ public class AttackDetectionResource { data.put("lastIPFailure", "n/a"); if (!realm.isBruteForceProtected()) return data; - UserModel user = session.users().getUserById(userId, realm); UserLoginFailureModel model = session.sessions().getUserLoginFailure(realm, userId); if (model == null) return data; @@ -115,10 +114,10 @@ public class AttackDetectionResource { @Path("brute-force/users/{userId}") @DELETE public void clearBruteForceForUser(@PathParam("userId") String userId) { - auth.requireManage(); - UserLoginFailureModel model = session.sessions().getUserLoginFailure(realm, userId); if (model != null) { + UserModel user = session.users().getUserById(userId, realm); + auth.users().requireView(user); session.sessions().removeUserLoginFailure(realm, userId); adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); } @@ -133,7 +132,7 @@ public class AttackDetectionResource { @Path("brute-force/users") @DELETE public void clearAllBruteForce() { - auth.requireManage(); + auth.users().requireManage(); session.sessions().removeAllUserLoginFailures(realm); adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 2f7362bb94..ba7ecccc4b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -50,6 +50,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.utils.CredentialHelper; import javax.ws.rs.Consumes; @@ -80,18 +81,17 @@ public class AuthenticationManagementResource { private final RealmModel realm; private final KeycloakSession session; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; @Context private UriInfo uriInfo; protected static final Logger logger = Logger.getLogger(AuthenticationManagementResource.class); - public AuthenticationManagementResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) { + public AuthenticationManagementResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.realm = realm; this.session = session; this.auth = auth; - this.auth.init(RealmAuth.Resource.REALM); this.adminEvent = adminEvent.resource(ResourceType.AUTH_FLOW); } @@ -105,7 +105,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getFormProviders() { - auth.requireView(); + auth.realm().requireViewRealm(); List factories = session.getKeycloakSessionFactory().getProviderFactories(FormAuthenticator.class); return buildProviderMetadata(factories); @@ -121,7 +121,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getAuthenticatorProviders() { - auth.requireView(); + auth.realm().requireViewRealm(); List factories = session.getKeycloakSessionFactory().getProviderFactories(Authenticator.class); return buildProviderMetadata(factories); @@ -137,7 +137,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getClientAuthenticatorProviders() { - auth.requireAny(); + auth.realm().requireViewRealm(); List factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class); return buildProviderMetadata(factories); @@ -167,7 +167,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getFormActionProviders() { - auth.requireView(); + auth.realm().requireViewRealm(); List factories = session.getKeycloakSessionFactory().getProviderFactories(FormAction.class); return buildProviderMetadata(factories); @@ -184,7 +184,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getFlows() { - auth.requireAny(); + auth.realm().requireViewRealm(); List flows = new LinkedList<>(); for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) { @@ -207,7 +207,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response createFlow(AuthenticationFlowRepresentation flow) { - auth.requireManage(); + auth.realm().requireManageRealm(); if (flow.getAlias() == null || flow.getAlias().isEmpty()) { return ErrorResponse.exists("Failed to create flow with empty alias name"); @@ -235,7 +235,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public AuthenticationFlowRepresentation getFlow(@PathParam("id") String id) { - auth.requireView(); + auth.realm().requireViewRealm(); AuthenticationFlowModel flow = realm.getAuthenticationFlowById(id); if (flow == null) { @@ -253,7 +253,7 @@ public class AuthenticationManagementResource { @DELETE @NoCache public void deleteFlow(@PathParam("id") String id) { - auth.requireManage(); + auth.realm().requireManageRealm(); deleteFlow(id, true); } @@ -292,7 +292,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response copy(@PathParam("flowAlias") String flowAlias, Map data) { - auth.requireManage(); + auth.realm().requireManageRealm(); String newName = data.get("newName"); if (realm.getFlowByAlias(newName) != null) { @@ -351,7 +351,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map data) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias); if (parentFlow == null) { @@ -403,7 +403,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public void addExecution(@PathParam("flowAlias") String flowAlias, Map data) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias); if (parentFlow == null) { @@ -450,7 +450,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Response getExecutions(@PathParam("flowAlias") String flowAlias) { - auth.requireView(); + auth.realm().requireViewRealm(); AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias); if (flow == null) { @@ -535,7 +535,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias); if (flow == null) { @@ -566,7 +566,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response addExecution(AuthenticationExecutionRepresentation execution) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationExecutionModel model = RepresentationToModel.toModel(realm, execution); AuthenticationFlowModel parentFlow = getParentFlow(model); @@ -601,7 +601,7 @@ public class AuthenticationManagementResource { @POST @NoCache public void raisePriority(@PathParam("executionId") String execution) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { @@ -647,7 +647,7 @@ public class AuthenticationManagementResource { @POST @NoCache public void lowerPriority(@PathParam("executionId") String execution) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { @@ -687,7 +687,7 @@ public class AuthenticationManagementResource { @DELETE @NoCache public void removeExecution(@PathParam("executionId") String execution) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { @@ -723,7 +723,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation json) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { @@ -753,7 +753,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) { - auth.requireView(); + auth.realm().requireViewRealm(); AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); if (config == null) { @@ -773,7 +773,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List> getUnregisteredRequiredActions() { - auth.requireView(); + auth.realm().requireViewRealm(); List factories = session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class); List> unregisteredList = new LinkedList<>(); @@ -807,7 +807,7 @@ public class AuthenticationManagementResource { @Consumes(MediaType.APPLICATION_JSON) @NoCache public void registerRequiredAction(Map data) { - auth.requireManage(); + auth.realm().requireManageRealm(); String providerId = data.get("providerId"); String name = data.get("name"); @@ -834,7 +834,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getRequiredActions() { - auth.requireAny(); + auth.realm().requireViewRealm(); List list = new LinkedList<>(); for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) { @@ -863,7 +863,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias) { - auth.requireView(); + auth.realm().requireViewRealm(); RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); if (model == null) { @@ -883,7 +883,7 @@ public class AuthenticationManagementResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); if (model == null) { @@ -909,7 +909,7 @@ public class AuthenticationManagementResource { @Path("required-actions/{alias}") @DELETE public void removeRequiredAction(@PathParam("alias") String alias) { - auth.requireManage(); + auth.realm().requireManageRealm(); RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); if (model == null) { @@ -928,7 +928,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public AuthenticatorConfigInfoRepresentation getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) { - auth.requireView(); + auth.realm().requireViewRealm(); ConfigurableAuthenticatorFactory factory = CredentialHelper.getConfigurableAuthenticatorFactory(session, providerId); if (factory == null) { @@ -959,7 +959,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public Map> getPerClientConfigDescription() { - auth.requireAny(); + auth.realm().requireViewRealm(); List factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class); @@ -991,7 +991,7 @@ public class AuthenticationManagementResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response createAuthenticatorConfig(AuthenticatorConfigRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep)); adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTHENTICATOR_CONFIG).resourcePath(uriInfo, config.getId()).representation(rep).success(); @@ -1007,7 +1007,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("id") String id) { - auth.requireView(); + auth.realm().requireViewRealm(); AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); if (config == null) { @@ -1025,7 +1025,7 @@ public class AuthenticationManagementResource { @DELETE @NoCache public void removeAuthenticatorConfig(@PathParam("id") String id) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); if (config == null) { @@ -1057,7 +1057,7 @@ public class AuthenticationManagementResource { @Consumes(MediaType.APPLICATION_JSON) @NoCache public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id); if (exists == null) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java index 1f4277b424..1de2c9dbc4 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java @@ -37,6 +37,7 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.KeyStoreConfig; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.util.CertificateInfoHelper; import org.keycloak.util.JWKSUtils; import org.keycloak.util.JsonSerialization; @@ -73,13 +74,13 @@ public class ClientAttributeCertificateResource { public static final String JSON_WEB_KEY_SET = "JSON Web Key Set"; protected RealmModel realm; - private RealmAuth auth; + private AdminPermissionEvaluator auth; protected ClientModel client; protected KeycloakSession session; protected AdminEventBuilder adminEvent; protected String attributePrefix; - public ClientAttributeCertificateResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, String attributePrefix, AdminEventBuilder adminEvent) { + public ClientAttributeCertificateResource(RealmModel realm, AdminPermissionEvaluator auth, ClientModel client, KeycloakSession session, String attributePrefix, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; this.client = client; @@ -97,11 +98,7 @@ public class ClientAttributeCertificateResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation getKeyInfo() { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); CertificateRepresentation info = CertificateInfoHelper.getCertificateFromClient(client, attributePrefix); return info; @@ -117,11 +114,7 @@ public class ClientAttributeCertificateResource { @Path("generate") @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation generate() { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); CertificateRepresentation info = KeycloakModelUtils.generateKeyPairCertificate(client.getClientId()); @@ -145,11 +138,7 @@ public class ClientAttributeCertificateResource { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); try { CertificateRepresentation info = getCertFromRequest(input); @@ -175,11 +164,7 @@ public class ClientAttributeCertificateResource { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); try { CertificateRepresentation info = getCertFromRequest(input); @@ -194,7 +179,7 @@ public class ClientAttributeCertificateResource { } private CertificateRepresentation getCertFromRequest(MultipartFormDataInput input) throws IOException { - auth.requireManage(); + auth.clients().requireManage(client); CertificateRepresentation info = new CertificateRepresentation(); Map> uploadForm = input.getFormDataMap(); String keystoreFormat = uploadForm.get("keystoreFormat").get(0).getBodyAsString(); @@ -279,11 +264,7 @@ public class ClientAttributeCertificateResource { @Produces(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_JSON) public byte[] getKeystore(final KeyStoreConfig config) { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) { throw new NotAcceptableException("Only support jks or pkcs12 format."); @@ -322,11 +303,7 @@ public class ClientAttributeCertificateResource { @Produces(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_JSON) public byte[] generateAndGetKeystore(final KeyStoreConfig config) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) { throw new NotAcceptableException("Only support jks or pkcs12 format."); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java index dfd28cc1c3..65941afbaa 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java @@ -25,6 +25,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; @@ -48,7 +49,7 @@ import java.util.List; */ public class ClientInitialAccessResource { - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private final RealmModel realm; private final AdminEventBuilder adminEvent; @@ -58,12 +59,11 @@ public class ClientInitialAccessResource { @Context protected UriInfo uriInfo; - public ClientInitialAccessResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public ClientInitialAccessResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent.resource(ResourceType.CLIENT_INITIAL_ACCESS_MODEL); - auth.init(RealmAuth.Resource.CLIENT); } /** @@ -76,7 +76,7 @@ public class ClientInitialAccessResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation config, @Context final HttpServletResponse response) { - auth.requireManage(); + auth.clients().requireManage(); int expiration = config.getExpiration() != null ? config.getExpiration() : 0; int count = config.getCount() != null ? config.getCount() : 1; @@ -99,7 +99,7 @@ public class ClientInitialAccessResource { @GET @Produces(MediaType.APPLICATION_JSON) public List list() { - auth.requireView(); + auth.clients().requireView(); List models = session.sessions().listClientInitialAccess(realm); List reps = new LinkedList<>(); @@ -113,7 +113,7 @@ public class ClientInitialAccessResource { @DELETE @Path("{id}") public void delete(final @PathParam("id") String id) { - auth.requireManage(); + auth.clients().requireManage(); session.sessions().removeClientInitialAccessModel(realm, id); adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java index f8c57e259c..92503267c8 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientRegistrationPolicyResource.java @@ -37,6 +37,7 @@ import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.ComponentTypeRepresentation; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; /** * @resource Client Registration Policy @@ -44,7 +45,7 @@ import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyF */ public class ClientRegistrationPolicyResource { - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private final RealmModel realm; private final AdminEventBuilder adminEvent; @@ -54,12 +55,11 @@ public class ClientRegistrationPolicyResource { @Context protected UriInfo uriInfo; - public ClientRegistrationPolicyResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public ClientRegistrationPolicyResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent.resource(ResourceType.CLIENT_INITIAL_ACCESS_MODEL); - auth.init(RealmAuth.Resource.CLIENT); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index ac5e6d1110..ba6bd03aea 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -51,6 +51,7 @@ import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.validation.ClientValidator; import org.keycloak.services.validation.PairwiseClientValidator; import org.keycloak.services.validation.ValidationMessages; @@ -89,7 +90,7 @@ import static java.lang.Boolean.TRUE; public class ClientResource { protected static final Logger logger = Logger.getLogger(ClientResource.class); protected RealmModel realm; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; protected ClientModel client; protected KeycloakSession session; @@ -104,19 +105,19 @@ public class ClientResource { return keycloak; } - public ClientResource(RealmModel realm, RealmAuth auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) { + public ClientResource(RealmModel realm, AdminPermissionEvaluator auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; this.client = clientModel; this.session = session; this.adminEvent = adminEvent.resource(ResourceType.CLIENT); - - auth.init(RealmAuth.Resource.CLIENT); } @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers() { - ProtocolMappersResource mappers = new ProtocolMappersResource(realm, client, auth, adminEvent); + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(client); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(client); + ProtocolMappersResource mappers = new ProtocolMappersResource(realm, client, auth, adminEvent, manageCheck, viewCheck); ResteasyProviderFactory.getInstance().injectProperties(mappers); return mappers; } @@ -129,15 +130,11 @@ public class ClientResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public Response update(final ClientRepresentation rep) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); ValidationMessages validationMessages = new ValidationMessages(); if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); throw new ErrorResponseException( validationMessages.getStringMessages(), validationMessages.getStringMessages(messages), @@ -187,11 +184,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public ClientRepresentation getClient() { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); ClientRepresentation representation = ModelToRepresentation.toRepresentation(client); @@ -217,11 +210,7 @@ public class ClientResource { @NoCache @Path("installation/providers/{providerId}") public Response getInstallationProvider(@PathParam("providerId") String providerId) { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); ClientInstallationProvider provider = session.getProvider(ClientInstallationProvider.class, providerId); if (provider == null) throw new NotFoundException("Unknown Provider"); @@ -235,7 +224,7 @@ public class ClientResource { @DELETE @NoCache public void deleteClient() { - auth.requireManage(); + auth.clients().requireManage(client); if (client == null) { throw new NotFoundException("Could not find client"); @@ -256,11 +245,7 @@ public class ClientResource { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public CredentialRepresentation regenerateSecret() { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); logger.debug("regenerateSecret"); UserCredentialModel cred = KeycloakModelUtils.generateSecret(client); @@ -279,11 +264,7 @@ public class ClientResource { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public ClientRepresentation regenerateRegistrationAccessToken() { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, realm, uriInfo, client, RegistrationAuth.AUTHENTICATED); @@ -304,11 +285,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public CredentialRepresentation getClientSecret() { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); logger.debug("getClientSecret"); UserCredentialModel model = UserCredentialModel.secret(client.getSecret()); @@ -323,12 +300,16 @@ public class ClientResource { */ @Path("scope-mappings") public ScopeMappedResource getScopeMappedResource() { - return new ScopeMappedResource(realm, auth, client, session, adminEvent); + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(client); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(client); + return new ScopeMappedResource(realm, auth, client, session, adminEvent, manageCheck, viewCheck); } @Path("roles") public RoleContainerResource getRoleContainerResource() { - return new RoleContainerResource(session, uriInfo, realm, auth, client, adminEvent); + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(client); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(client); + return new RoleContainerResource(session, uriInfo, realm, auth, client, adminEvent, manageCheck, viewCheck); } /** @@ -341,11 +322,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public UserRepresentation getServiceAccountUser() { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); UserModel user = session.users().getServiceAccount(client); if (user == null) { @@ -369,11 +346,7 @@ public class ClientResource { @POST @Produces(MediaType.APPLICATION_JSON) public GlobalRequestResult pushRevocation() { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).resource(ResourceType.CLIENT).success(); return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client); @@ -396,11 +369,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Map getApplicationSessionCount() { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); Map map = new HashMap<>(); map.put("count", session.sessions().getActiveUserSessions(client.getRealm(), client)); @@ -421,11 +390,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); firstResult = firstResult != null ? firstResult : -1; maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS; @@ -453,11 +418,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Map getOfflineSessionCount() { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); Map map = new HashMap<>(); map.put("count", session.sessions().getOfflineSessionsCount(client.getRealm(), client)); @@ -478,11 +439,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getOfflineUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) { - auth.requireView(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireView(client); firstResult = firstResult != null ? firstResult : -1; maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS; @@ -519,11 +476,7 @@ public class ClientResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void registerNode(Map formParams) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); String node = formParams.get("node"); if (node == null) { @@ -543,11 +496,7 @@ public class ClientResource { @DELETE @NoCache public void unregisterNode(final @PathParam("node") String node) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); if (logger.isDebugEnabled()) logger.debug("Unregister node: " + node); @@ -571,11 +520,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public GlobalRequestResult testNodesAvailable() { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + auth.clients().requireManage(client); logger.debug("Test availability of cluster nodes"); GlobalRequestResult result = new ResourceAdminManager(session).testNodesAvailability(uriInfo.getRequestUri(), realm, client); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java index b5f199689e..c619669cc4 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java @@ -19,7 +19,7 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.authorization.admin.permissions.MgmtPermissions; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; @@ -61,30 +61,29 @@ public class ClientRoleMappingsResource { protected KeycloakSession session; protected RealmModel realm; - protected RealmAuth auth; + protected AdminPermissionEvaluator auth; protected RoleMapperModel user; protected ClientModel client; protected AdminEventBuilder adminEvent; private UriInfo uriInfo; - private RoleMapperResource.ManageResourcePermissionCheck manageResourcePermissionCheck; + protected AdminPermissionEvaluator.RequirePermissionCheck managePermission; + protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission; - public ClientRoleMappingsResource(UriInfo uriInfo, KeycloakSession session, RealmModel realm, RealmAuth auth, RoleMapperModel user, ClientModel client, AdminEventBuilder adminEvent) { + public ClientRoleMappingsResource(UriInfo uriInfo, KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, + RoleMapperModel user, ClientModel client, AdminEventBuilder adminEvent, + AdminPermissionEvaluator.RequirePermissionCheck manageCheck, AdminPermissionEvaluator.RequirePermissionCheck viewCheck ) { this.uriInfo = uriInfo; this.session = session; this.realm = realm; this.auth = auth; this.user = user; this.client = client; + this.managePermission = manageCheck; + this.viewPermission = viewCheck; this.adminEvent = adminEvent.resource(ResourceType.CLIENT_ROLE_MAPPING); } - public void setManageCheck(RoleMapperResource.ManageResourcePermissionCheck mapperPermissions) { - this.manageResourcePermissionCheck = mapperPermissions; - } - - - /** * Get client-level role mappings for the user, and the app * @@ -94,11 +93,7 @@ public class ClientRoleMappingsResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getClientRoleMappings() { - auth.requireView(); - - if (user == null || client == null) { - throw new NotFoundException("Not found"); - } + viewPermission.require(); Set mappings = user.getClientRoleMappings(client); List mapRep = new ArrayList(); @@ -120,11 +115,8 @@ public class ClientRoleMappingsResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getCompositeClientRoleMappings() { - auth.requireView(); + viewPermission.require(); - if (user == null || client == null) { - throw new NotFoundException("Not found"); - } Set roles = client.getRoles(); List mapRep = new ArrayList(); @@ -144,11 +136,7 @@ public class ClientRoleMappingsResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getAvailableClientRoleMappings() { - auth.requireView(); - - if (user == null || client == null) { - throw new NotFoundException("Not found"); - } + viewPermission.require(); Set available = client.getRoles(); available = available.stream().filter(r -> @@ -171,17 +159,6 @@ public class ClientRoleMappingsResource { return mappings; } - private void checkManagePermission() { - if (manageResourcePermissionCheck == null) { - auth.requireManage(); - } else { - if (!manageResourcePermissionCheck.canManage()) { - throw new ForbiddenException(); - } - } - } - - /** * Add client-level roles to the user role mapping * @@ -190,11 +167,7 @@ public class ClientRoleMappingsResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addClientRoleMapping(List roles) { - checkManagePermission(); - - if (user == null || client == null) { - throw new NotFoundException("Not found"); - } + managePermission.require(); for (RoleRepresentation role : roles) { RoleModel roleModel = client.getRole(role.getName()); @@ -215,7 +188,7 @@ public class ClientRoleMappingsResource { } private boolean canMapRole(RoleModel roleModel) { - return new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel); + return auth.roles().canMapRole(roleModel); } /** @@ -226,11 +199,7 @@ public class ClientRoleMappingsResource { @DELETE @Consumes(MediaType.APPLICATION_JSON) public void deleteClientRoleMapping(List roles) { - checkManagePermission(); - - if (user == null || client == null) { - throw new NotFoundException("Not found"); - } + managePermission.require(); if (roles == null) { Set roleModels = user.getClientRoleMappings(client); @@ -257,7 +226,7 @@ public class ClientRoleMappingsResource { try { user.deleteRoleMapping(roleModel); } catch (ModelException me) { - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()), Response.Status.BAD_REQUEST); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java index f760a41c1b..f076850bef 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java @@ -31,6 +31,7 @@ import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -54,7 +55,7 @@ import javax.ws.rs.core.UriInfo; public class ClientTemplateResource { protected static final Logger logger = Logger.getLogger(ClientTemplateResource.class); protected RealmModel realm; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; protected ClientTemplateModel template; protected KeycloakSession session; @@ -62,19 +63,20 @@ public class ClientTemplateResource { @Context protected UriInfo uriInfo; - public ClientTemplateResource(RealmModel realm, RealmAuth auth, ClientTemplateModel template, KeycloakSession session, AdminEventBuilder adminEvent) { + public ClientTemplateResource(RealmModel realm, AdminPermissionEvaluator auth, ClientTemplateModel template, KeycloakSession session, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; this.template = template; this.session = session; this.adminEvent = adminEvent.resource(ResourceType.CLIENT_TEMPLATE); - auth.init(RealmAuth.Resource.CLIENT); } @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers() { - ProtocolMappersResource mappers = new ProtocolMappersResource(realm, template, auth, adminEvent); + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(template); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(template); + ProtocolMappersResource mappers = new ProtocolMappersResource(realm, template, auth, adminEvent, manageCheck, viewCheck); ResteasyProviderFactory.getInstance().injectProperties(mappers); return mappers; } @@ -86,7 +88,9 @@ public class ClientTemplateResource { */ @Path("scope-mappings") public ScopeMappedResource getScopeMappedResource() { - return new ScopeMappedResource(realm, auth, template, session, adminEvent); + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(template); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(template); + return new ScopeMappedResource(realm, auth, template, session, adminEvent, manageCheck, viewCheck); } /** @@ -97,11 +101,7 @@ public class ClientTemplateResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public Response update(final ClientTemplateRepresentation rep) { - auth.requireManage(); - - if (template == null) { - throw new NotFoundException("Could not find client template"); - } + auth.clients().requireManageTemplates(); try { RepresentationToModel.updateClientTemplate(rep, template); @@ -125,11 +125,8 @@ public class ClientTemplateResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public ClientTemplateRepresentation getClient() { - auth.requireView(); + auth.clients().requireView(template); - if (template == null) { - throw new NotFoundException("Could not find client template"); - } return ModelToRepresentation.toRepresentation(template); } @@ -141,11 +138,7 @@ public class ClientTemplateResource { @DELETE @NoCache public Response deleteClientTemplate() { - auth.requireManage(); - - if (template == null) { - throw new NotFoundException("Could not find client template"); - } + auth.clients().requireManage(template); try { realm.removeClientTemplate(template.getId()); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java index 5e27712846..954f0c8790 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java @@ -18,6 +18,7 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; @@ -29,6 +30,7 @@ import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -53,18 +55,16 @@ import java.util.List; public class ClientTemplatesResource { protected static final Logger logger = Logger.getLogger(ClientTemplatesResource.class); protected RealmModel realm; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; @Context protected KeycloakSession session; - public ClientTemplatesResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public ClientTemplatesResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; this.adminEvent = adminEvent.resource(ResourceType.CLIENT_TEMPLATE); - - auth.init(RealmAuth.Resource.CLIENT); } /** @@ -76,22 +76,14 @@ public class ClientTemplatesResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getClientTemplates() { - auth.requireView(); List rep = new ArrayList<>(); List clientModels = realm.getClientTemplates(); - boolean view = auth.hasView(); + boolean view = auth.clients().canViewTemplates(); for (ClientTemplateModel clientModel : clientModels) { - if (view) { + if (view || auth.clients().canView(clientModel)) { rep.add(ModelToRepresentation.toRepresentation(clientModel)); - } else { - ClientTemplateRepresentation client = new ClientTemplateRepresentation(); - client.setId(clientModel.getId()); - client.setName(clientModel.getName()); - client.setDescription(clientModel.getDescription()); - client.setProtocol(clientModel.getProtocol()); - rep.add(client); } } return rep; @@ -109,7 +101,7 @@ public class ClientTemplatesResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response createClientTemplate(final @Context UriInfo uriInfo, final ClientTemplateRepresentation rep) { - auth.requireManage(); + auth.clients().requireManageTemplates(); try { ClientTemplateModel clientModel = RepresentationToModel.createClientTemplate(session, realm, rep); @@ -131,6 +123,9 @@ public class ClientTemplatesResource { @Path("{id}") public ClientTemplateResource getClient(final @PathParam("id") String id) { ClientTemplateModel clientModel = realm.getClientTemplateById(id); + if (clientModel == null) { + throw new NotFoundException("Could not find client template"); + } ClientTemplateResource clientResource = new ClientTemplateResource(realm, auth, clientModel, session, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(clientResource); return clientResource; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index 3fa3c75158..e02f2251f7 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -26,6 +26,7 @@ import org.keycloak.common.Profile; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; @@ -34,14 +35,18 @@ import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.ForbiddenException; import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.validation.ClientValidator; import org.keycloak.services.validation.PairwiseClientValidator; import org.keycloak.services.validation.ValidationMessages; import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -65,18 +70,17 @@ import java.util.Properties; public class ClientsResource { protected static final Logger logger = Logger.getLogger(ClientsResource.class); protected RealmModel realm; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; @Context protected KeycloakSession session; - public ClientsResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public ClientsResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; this.adminEvent = adminEvent.resource(ResourceType.CLIENT); - auth.init(RealmAuth.Resource.CLIENT); } /** @@ -85,21 +89,20 @@ public class ClientsResource { * Returns a list of clients belonging to the realm * * @param clientId filter by clientId + * @param viewableOnly filter clients that cannot be viewed in full by admin */ @GET @Produces(MediaType.APPLICATION_JSON) @NoCache - public List getClients(@QueryParam("clientId") String clientId) { - auth.requireAny(); - + public List getClients(@QueryParam("clientId") String clientId, @QueryParam("viewableOnly") @DefaultValue("false") boolean viewableOnly) { List rep = new ArrayList<>(); if (clientId == null) { List clientModels = realm.getClients(); - - boolean view = auth.hasView(); + auth.clients().requireList(); + boolean view = auth.clients().canView(); for (ClientModel clientModel : clientModels) { - if (view) { + if (view || auth.clients().canView(clientModel)) { ClientRepresentation representation = ModelToRepresentation.toRepresentation(clientModel); if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) { @@ -111,7 +114,7 @@ public class ClientsResource { } rep.add(representation); - } else { + } else if (!viewableOnly) { ClientRepresentation client = new ClientRepresentation(); client.setId(clientModel.getId()); client.setClientId(clientModel.getClientId()); @@ -120,9 +123,20 @@ public class ClientsResource { } } } else { - ClientModel client = realm.getClientByClientId(clientId); - if (client != null) { - rep.add(ModelToRepresentation.toRepresentation(client)); + ClientModel clientModel = realm.getClientByClientId(clientId); + if (clientModel != null) { + if (auth.clients().canView(clientModel)) { + rep.add(ModelToRepresentation.toRepresentation(clientModel)); + } else if (!viewableOnly && auth.clients().canList()){ + ClientRepresentation client = new ClientRepresentation(); + client.setId(clientModel.getId()); + client.setClientId(clientModel.getClientId()); + client.setDescription(clientModel.getDescription()); + rep.add(client); + + } else { + throw new ForbiddenException(); + } } } return rep; @@ -144,11 +158,11 @@ public class ClientsResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response createClient(final @Context UriInfo uriInfo, final ClientRepresentation rep) { - auth.requireManage(); + auth.clients().requireManage(); ValidationMessages validationMessages = new ValidationMessages(); if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); throw new ErrorResponseException( validationMessages.getStringMessages(), validationMessages.getStringMessages(messages), @@ -190,6 +204,9 @@ public class ClientsResource { @Path("{id}") public ClientResource getClient(final @PathParam("id") String id) { ClientModel clientModel = realm.getClientById(id); + if (clientModel == null) { + throw new NotFoundException("Could not find client"); + } session.getContext().setClient(clientModel); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java index b39b773522..c532245e15 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java @@ -38,7 +38,7 @@ import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentTypeRepresentation; import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.services.ErrorResponse; -import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; @@ -63,7 +63,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.stream.Collectors; /** * @resource Component @@ -75,7 +74,7 @@ public class ComponentResource { protected RealmModel realm; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; @@ -91,12 +90,10 @@ public class ComponentResource { @Context protected HttpHeaders headers; - public ComponentResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public ComponentResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent.resource(ResourceType.COMPONENT); - - auth.init(RealmAuth.Resource.REALM); } @GET @@ -105,7 +102,7 @@ public class ComponentResource { public List getComponents(@QueryParam("parent") String parent, @QueryParam("type") String type, @QueryParam("name") String name) { - auth.requireView(); + auth.realm().requireViewRealm(); List components = Collections.EMPTY_LIST; if (parent == null && type == null) { components = realm.getComponents(); @@ -135,7 +132,7 @@ public class ComponentResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response create(ComponentRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); try { ComponentModel model = RepresentationToModel.toModel(session, rep); if (model.getParentId() == null) model.setParentId(realm.getId()); @@ -156,7 +153,7 @@ public class ComponentResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public ComponentRepresentation getComponent(@PathParam("id") String id) { - auth.requireView(); + auth.realm().requireViewRealm(); ComponentModel model = realm.getComponent(id); if (model == null) { throw new NotFoundException("Could not find component"); @@ -169,7 +166,7 @@ public class ComponentResource { @Path("{id}") @Consumes(MediaType.APPLICATION_JSON) public Response updateComponent(@PathParam("id") String id, ComponentRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); try { ComponentModel model = realm.getComponent(id); if (model == null) { @@ -188,7 +185,7 @@ public class ComponentResource { @DELETE @Path("{id}") public void removeComponent(@PathParam("id") String id) { - auth.requireManage(); + auth.realm().requireManageRealm(); ComponentModel model = realm.getComponent(id); if (model == null) { throw new NotFoundException("Could not find component"); @@ -199,7 +196,7 @@ public class ComponentResource { } private Response localizedErrorResponse(ComponentValidationException cve) { - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale(), "admin-messages", "messages"); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale(), "admin-messages", "messages"); Object[] localizedParameters = cve.getParameters()==null ? null : Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> { @@ -228,7 +225,7 @@ public class ComponentResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getSubcomponentConfig(@PathParam("id") String parentId, @QueryParam("type") String subtype) { - auth.requireView(); + auth.realm().requireViewRealm(); ComponentModel parent = realm.getComponent(parentId); if (parent == null) { throw new NotFoundException("Could not find parent component"); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java index c9fe194f94..a529a4895e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java @@ -49,6 +49,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; /** * @resource Groups @@ -58,11 +59,11 @@ public class GroupResource { private final RealmModel realm; private final KeycloakSession session; - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private final AdminEventBuilder adminEvent; private final GroupModel group; - public GroupResource(RealmModel realm, GroupModel group, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) { + public GroupResource(RealmModel realm, GroupModel group, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.realm = realm; this.session = session; this.auth = auth; @@ -81,11 +82,7 @@ public class GroupResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public GroupRepresentation getGroup() { - this.auth.requireView(); - - if (group == null) { - throw new NotFoundException("Could not find group by id"); - } + this.auth.groups().requireView(group); return ModelToRepresentation.toGroupHierarchy(group, true); } @@ -98,11 +95,7 @@ public class GroupResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public void updateGroup(GroupRepresentation rep) { - this.auth.requireManage(); - - if (group == null) { - throw new NotFoundException("Could not find group by id"); - } + this.auth.groups().requireManage(group); updateGroup(rep, group); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); @@ -112,11 +105,7 @@ public class GroupResource { @DELETE public void deleteGroup() { - this.auth.requireManage(); - - if (group == null) { - throw new NotFoundException("Could not find group by id"); - } + this.auth.groups().requireManage(group); realm.removeGroup(group); adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); @@ -135,12 +124,8 @@ public class GroupResource { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response addChild(GroupRepresentation rep) { - this.auth.requireManage(); + this.auth.groups().requireManage(group); - if (group == null) { - throw new NotFoundException("Could not find group by id"); - } - for (GroupModel group : group.getSubGroups()) { if (group.getName().equals(rep.getName())) { return ErrorResponse.exists("Parent already contains subgroup named '" + rep.getName() + "'"); @@ -191,9 +176,9 @@ public class GroupResource { @Path("role-mappings") public RoleMapperResource getRoleMappings() { - auth.init(RealmAuth.Resource.USER); - - RoleMapperResource resource = new RoleMapperResource(realm, auth, group, adminEvent); + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.groups().requireManage(group); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.groups().requireView(group); + RoleMapperResource resource = new RoleMapperResource(realm, auth, group, adminEvent, manageCheck, viewCheck); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; @@ -214,11 +199,8 @@ public class GroupResource { @Produces(MediaType.APPLICATION_JSON) public List getMembers(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) { - auth.requireView(); + this.auth.groups().requireViewMembers(group); - if (group == null) { - throw new NotFoundException("Could not find group by id"); - } firstResult = firstResult != null ? firstResult : 0; maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java index 5be1c0d958..2a1909b383 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java @@ -40,6 +40,7 @@ import javax.ws.rs.core.UriInfo; import java.net.URI; import java.util.List; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; /** * @resource Groups @@ -49,15 +50,14 @@ public class GroupsResource { private final RealmModel realm; private final KeycloakSession session; - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private final AdminEventBuilder adminEvent; - public GroupsResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) { + public GroupsResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.realm = realm; this.session = session; this.auth = auth; this.adminEvent = adminEvent.resource(ResourceType.GROUP); - auth.init(RealmAuth.Resource.USER); } @@ -72,7 +72,7 @@ public class GroupsResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getGroups() { - auth.requireView(); + auth.groups().requireList(); return ModelToRepresentation.toGroupHierarchy(realm, false); } @@ -85,9 +85,10 @@ public class GroupsResource { */ @Path("{id}") public GroupResource getGroupById(@PathParam("id") String id) { - auth.requireView(); - GroupModel group = realm.getGroupById(id); + if (group == null) { + throw new NotFoundException("Could not find group by id"); + } GroupResource resource = new GroupResource(realm, group, session, this.auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; @@ -102,7 +103,7 @@ public class GroupsResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response addTopLevelGroup(GroupRepresentation rep) { - auth.requireManage(); + auth.groups().requireManage(); for (GroupModel group : realm.getGroups()) { if (group.getName().equals(rep.getName())) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java index aa4a054118..4c08c204b2 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java @@ -44,6 +44,7 @@ import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperTypeRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -72,7 +73,7 @@ public class IdentityProviderResource { protected static final Logger logger = Logger.getLogger(IdentityProviderResource.class); - private final RealmAuth auth; + private final AdminPermissionEvaluator auth; private final RealmModel realm; private final KeycloakSession session; private final IdentityProviderModel identityProviderModel; @@ -80,7 +81,7 @@ public class IdentityProviderResource { @Context private UriInfo uriInfo; - public IdentityProviderResource(RealmAuth auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) { + public IdentityProviderResource(AdminPermissionEvaluator auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) { this.realm = realm; this.session = session; this.identityProviderModel = identityProviderModel; @@ -97,7 +98,7 @@ public class IdentityProviderResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public IdentityProviderRepresentation getIdentityProvider() { - this.auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -115,7 +116,7 @@ public class IdentityProviderResource { @DELETE @NoCache public Response delete() { - this.auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -138,7 +139,7 @@ public class IdentityProviderResource { @Consumes(MediaType.APPLICATION_JSON) @NoCache public Response update(IdentityProviderRepresentation providerRep) { - this.auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -229,7 +230,7 @@ public class IdentityProviderResource { @Path("export") @NoCache public Response export(@Context UriInfo uriInfo, @QueryParam("format") String format) { - this.auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -250,7 +251,7 @@ public class IdentityProviderResource { @Path("mapper-types") @NoCache public Map getMapperTypes() { - this.auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -289,7 +290,7 @@ public class IdentityProviderResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getMappers() { - this.auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -312,7 +313,7 @@ public class IdentityProviderResource { @Path("mappers") @Consumes(MediaType.APPLICATION_JSON) public Response addMapper(IdentityProviderMapperRepresentation mapper) { - auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -343,7 +344,7 @@ public class IdentityProviderResource { @Path("mappers/{id}") @Produces(MediaType.APPLICATION_JSON) public IdentityProviderMapperRepresentation getMapperById(@PathParam("id") String id) { - auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -365,7 +366,7 @@ public class IdentityProviderResource { @Path("mappers/{id}") @Consumes(MediaType.APPLICATION_JSON) public void update(@PathParam("id") String id, IdentityProviderMapperRepresentation rep) { - auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); @@ -388,7 +389,7 @@ public class IdentityProviderResource { @NoCache @Path("mappers/{id}") public void delete(@PathParam("id") String id) { - auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); if (identityProviderModel == null) { throw new javax.ws.rs.NotFoundException(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java index c5250ac35f..646b46364c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java @@ -37,6 +37,7 @@ import org.keycloak.models.utils.StripSecretsUtils; import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; @@ -65,14 +66,13 @@ public class IdentityProvidersResource { private final RealmModel realm; private final KeycloakSession session; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; - public IdentityProvidersResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) { + public IdentityProvidersResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.realm = realm; this.session = session; this.auth = auth; - this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER); this.adminEvent = adminEvent.resource(ResourceType.IDENTITY_PROVIDER); } @@ -87,7 +87,7 @@ public class IdentityProvidersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Response getIdentityProviders(@PathParam("provider_id") String providerId) { - this.auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); IdentityProviderFactory providerFactory = getProviderFactorytById(providerId); if (providerFactory != null) { return Response.ok(providerFactory).build(); @@ -108,7 +108,7 @@ public class IdentityProvidersResource { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public Map importFrom(@Context UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - this.auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); Map> formDataMap = input.getFormDataMap(); if (!(formDataMap.containsKey("providerId") && formDataMap.containsKey("file"))) { throw new BadRequestException(); @@ -134,7 +134,7 @@ public class IdentityProvidersResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Map importFrom(@Context UriInfo uriInfo, Map data) throws IOException { - this.auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); if (!(data.containsKey("providerId") && data.containsKey("fromUrl"))) { throw new BadRequestException(); } @@ -164,7 +164,7 @@ public class IdentityProvidersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getIdentityProviders() { - this.auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); List representations = new ArrayList(); @@ -185,7 +185,7 @@ public class IdentityProvidersResource { @Path("instances") @Consumes(MediaType.APPLICATION_JSON) public Response create(@Context UriInfo uriInfo, IdentityProviderRepresentation representation) { - this.auth.requireManage(); + this.auth.realm().requireManageIdentityProviders(); try { IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation); @@ -203,7 +203,7 @@ public class IdentityProvidersResource { @Path("instances/{alias}") public IdentityProviderResource getIdentityProvider(@PathParam("alias") String alias) { - this.auth.requireView(); + this.auth.realm().requireViewIdentityProviders(); IdentityProviderModel identityProviderModel = null; for (IdentityProviderModel storedIdentityProvider : this.realm.getIdentityProviders()) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java index f2c94015cd..d990fd109d 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java @@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeyManager; import org.keycloak.models.RealmModel; import org.keycloak.representations.idm.KeysMetadataRepresentation; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.GET; import javax.ws.rs.Produces; @@ -43,9 +44,9 @@ public class KeyResource { private RealmModel realm; private KeycloakSession session; - private RealmAuth auth; + private AdminPermissionEvaluator auth; - public KeyResource(RealmModel realm, KeycloakSession session, RealmAuth auth) { + public KeyResource(RealmModel realm, KeycloakSession session, AdminPermissionEvaluator auth) { this.realm = realm; this.session = session; this.auth = auth; @@ -55,7 +56,7 @@ public class KeyResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public KeysMetadataRepresentation getKeyMetadata() { - auth.requireView(); + auth.realm().requireViewRealm(); KeyManager keystore = session.keys(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java index ad473b9072..3aac3315d6 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java @@ -33,7 +33,7 @@ import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponseException; -import org.keycloak.services.resources.admin.RealmAuth.Resource; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -66,7 +66,9 @@ public class ProtocolMappersResource { protected ProtocolMapperContainerModel client; - protected RealmAuth auth; + protected AdminPermissionEvaluator auth; + protected AdminPermissionEvaluator.RequirePermissionCheck managePermission; + protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission; protected AdminEventBuilder adminEvent; @@ -76,13 +78,17 @@ public class ProtocolMappersResource { @Context protected KeycloakSession session; - public ProtocolMappersResource(RealmModel realm, ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) { + public ProtocolMappersResource(RealmModel realm, ProtocolMapperContainerModel client, AdminPermissionEvaluator auth, + AdminEventBuilder adminEvent, + AdminPermissionEvaluator.RequirePermissionCheck managePermission, + AdminPermissionEvaluator.RequirePermissionCheck viewPermission) { this.realm = realm; this.auth = auth; this.client = client; this.adminEvent = adminEvent.resource(ResourceType.PROTOCOL_MAPPER); + this.managePermission = managePermission; + this.viewPermission = viewPermission; - auth.init(Resource.CLIENT); } /** @@ -96,11 +102,7 @@ public class ProtocolMappersResource { @Path("protocol/{protocol}") @Produces(MediaType.APPLICATION_JSON) public List getMappersPerProtocol(@PathParam("protocol") String protocol) { - auth.requireAny(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + viewPermission.require(); List mappers = new LinkedList(); for (ProtocolMapperModel mapper : client.getProtocolMappers()) { @@ -119,11 +121,7 @@ public class ProtocolMappersResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public Response createMapper(ProtocolMapperRepresentation rep) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + managePermission.require(); ProtocolMapperModel model = null; try { @@ -147,11 +145,7 @@ public class ProtocolMappersResource { @NoCache @Consumes(MediaType.APPLICATION_JSON) public void createMapper(List reps) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + managePermission.require(); ProtocolMapperModel model = null; for (ProtocolMapperRepresentation rep : reps) { @@ -172,11 +166,7 @@ public class ProtocolMappersResource { @Path("models") @Produces(MediaType.APPLICATION_JSON) public List getMappers() { - auth.requireAny(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + viewPermission.require(); List mappers = new LinkedList(); for (ProtocolMapperModel mapper : client.getProtocolMappers()) { @@ -196,11 +186,7 @@ public class ProtocolMappersResource { @Path("models/{id}") @Produces(MediaType.APPLICATION_JSON) public ProtocolMapperRepresentation getMapperById(@PathParam("id") String id) { - auth.requireAny(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + viewPermission.require(); ProtocolMapperModel model = client.getProtocolMapperById(id); if (model == null) throw new NotFoundException("Model not found"); @@ -218,11 +204,7 @@ public class ProtocolMappersResource { @Path("models/{id}") @Consumes(MediaType.APPLICATION_JSON) public void update(@PathParam("id") String id, ProtocolMapperRepresentation rep) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + managePermission.require(); ProtocolMapperModel model = client.getProtocolMapperById(id); if (model == null) throw new NotFoundException("Model not found"); @@ -243,11 +225,7 @@ public class ProtocolMappersResource { @NoCache @Path("models/{id}") public void delete(@PathParam("id") String id) { - auth.requireManage(); - - if (client == null) { - throw new NotFoundException("Could not find client"); - } + managePermission.require(); ProtocolMapperModel model = client.getProtocolMapperById(id); if (model == null) throw new NotFoundException("Model not found"); @@ -264,7 +242,7 @@ public class ProtocolMappersResource { } } catch (ProtocolMapperConfigException ex) { logger.error(ex.getMessage()); - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); throw new ErrorResponseException(ex.getMessage(), MessageFormat.format(messages.getProperty(ex.getMessageKey(), ex.getMessage()), ex.getParameters()), Response.Status.BAD_REQUEST); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 510a605499..0a84001b99 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -23,9 +23,9 @@ import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; import org.keycloak.KeyPairVerifier; -import org.keycloak.authorization.admin.permissions.MgmtPermissions; -import org.keycloak.authorization.admin.permissions.RoleMgmtPermissions; -import org.keycloak.authorization.admin.permissions.UsersPermissions; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.common.ClientConnection; import org.keycloak.common.VerificationException; import org.keycloak.common.util.PemUtils; @@ -48,7 +48,6 @@ import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.UserCache; @@ -63,7 +62,6 @@ import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; -import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.ManagementPermissionReference; @@ -76,7 +74,6 @@ import org.keycloak.services.managers.LDAPConnectionTestManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.UserStorageSyncManager; -import org.keycloak.services.resources.admin.RealmAuth.Resource; import org.keycloak.storage.UserStorageProviderModel; import javax.ws.rs.Consumes; @@ -114,7 +111,7 @@ import java.util.regex.PatternSyntaxException; */ public class RealmAdminResource { protected static final Logger logger = Logger.getLogger(RealmAdminResource.class); - protected RealmAuth auth; + protected AdminPermissionEvaluator auth; protected RealmModel realm; private TokenManager tokenManager; private AdminEventBuilder adminEvent; @@ -131,13 +128,11 @@ public class RealmAdminResource { @Context protected HttpHeaders headers; - public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager, AdminEventBuilder adminEvent) { + public RealmAdminResource(AdminPermissionEvaluator auth, RealmModel realm, TokenManager tokenManager, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.tokenManager = tokenManager; this.adminEvent = adminEvent.realm(realm).resource(ResourceType.REALM); - - auth.init(RealmAuth.Resource.REALM); } /** @@ -150,7 +145,7 @@ public class RealmAdminResource { @POST @Produces(MediaType.APPLICATION_JSON) public ClientRepresentation convertClientDescription(String description) { - auth.init(Resource.CLIENT).requireManage(); + auth.clients().requireManage(); if (realm == null) { throw new NotFoundException("Realm not found."); @@ -239,7 +234,9 @@ public class RealmAdminResource { */ @Path("roles") public RoleContainerResource getRoleContainerResource() { - return new RoleContainerResource(session, uriInfo, realm, auth, realm, adminEvent); + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.realm().requireManageRealm(); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.realm().requireViewRealm(); + return new RoleContainerResource(session, uriInfo, realm, auth, realm, adminEvent, manageCheck, viewCheck); } /** @@ -253,15 +250,15 @@ public class RealmAdminResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public RealmRepresentation getRealm() { - if (auth.hasView()) { + if (auth.realm().canViewRealm()) { return ModelToRepresentation.toRepresentation(realm, false); } else { - auth.requireAny(); + auth.realm().requireViewRealmNameList(); RealmRepresentation rep = new RealmRepresentation(); rep.setRealm(realm.getName()); - if (auth.init(Resource.IDENTITY_PROVIDER).hasView()) { + if (auth.realm().canViewIdentityProviders()) { RealmRepresentation r = ModelToRepresentation.toRepresentation(realm, false); rep.setIdentityProviders(r.getIdentityProviders()); rep.setIdentityProviderMappers(r.getIdentityProviderMappers()); @@ -283,7 +280,7 @@ public class RealmAdminResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public Response updateRealm(final RealmRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); logger.debug("updating realm: " + realm.getName()); @@ -347,7 +344,7 @@ public class RealmAdminResource { */ @DELETE public void deleteRealm() { - auth.requireManage(); + auth.realm().requireManageRealm(); if (!new RealmManager(session).removeRealm(realm)) { throw new NotFoundException("Realm doesn't exist"); @@ -372,9 +369,9 @@ public class RealmAdminResource { @Produces(MediaType.APPLICATION_JSON) @Path("users-management-permissions") public ManagementPermissionReference getUserMgmtPermissions() { - auth.requireView(); + auth.realm().requireViewRealm(); - MgmtPermissions permissions = new MgmtPermissions(session, realm); + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); if (permissions.users().isPermissionsEnabled()) { return toUsersMgmtRef(permissions); } else { @@ -389,9 +386,9 @@ public class RealmAdminResource { @NoCache @Path("users-management-permissions") public ManagementPermissionReference setUsersManagementPermissionsEnabled(ManagementPermissionReference ref) { - auth.requireManage(); + auth.realm().requireManageRealm(); - MgmtPermissions permissions = new MgmtPermissions(session, realm); + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); permissions.users().setPermissionsEnabled(ref.isEnabled()); if (ref.isEnabled()) { return toUsersMgmtRef(permissions); @@ -401,12 +398,11 @@ public class RealmAdminResource { } - public static ManagementPermissionReference toUsersMgmtRef(MgmtPermissions permissions) { + public static ManagementPermissionReference toUsersMgmtRef(AdminPermissionManagement permissions) { ManagementPermissionReference ref = new ManagementPermissionReference(); ref.setEnabled(true); ref.setResource(permissions.users().resource().getId()); - Map scopes = new HashMap<>(); - scopes.put(MgmtPermissions.MANAGE_SCOPE, permissions.users().managePermission().getId()); + Map scopes = permissions.users().getPermissions(); ref.setScopePermissions(scopes); return ref; } @@ -436,7 +432,7 @@ public class RealmAdminResource { */ @Path("roles-by-id") public RoleByIdResource rolesById() { - RoleByIdResource resource = new RoleByIdResource(realm, auth, adminEvent); + RoleByIdResource resource = new RoleByIdResource(realm, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); //resourceContext.initResource(resource); return resource; @@ -449,7 +445,7 @@ public class RealmAdminResource { @Path("push-revocation") @POST public GlobalRequestResult pushRevocation() { - auth.requireManage(); + auth.realm().requireManageRealm(); GlobalRequestResult result = new ResourceAdminManager(session).pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm); adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(result).success(); @@ -464,7 +460,7 @@ public class RealmAdminResource { @Path("logout-all") @POST public GlobalRequestResult logoutAll() { - auth.init(RealmAuth.Resource.USER).requireManage(); + auth.users().requireManage(); session.sessions().removeUserSessions(realm); GlobalRequestResult result = new ResourceAdminManager(session).logoutAll(uriInfo.getRequestUri(), realm); @@ -481,7 +477,7 @@ public class RealmAdminResource { @Path("sessions/{session}") @DELETE public void deleteSession(@PathParam("session") String sessionId) { - auth.init(RealmAuth.Resource.USER).requireManage(); + auth.users().requireManage(); UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId); if (userSession == null) throw new NotFoundException("Sesssion not found"); @@ -503,7 +499,7 @@ public class RealmAdminResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getClientSessionStats() { - auth.requireView(); + auth.realm().requireViewRealm(); List> data = new LinkedList>(); for (ClientModel client : realm.getClients()) { @@ -530,7 +526,7 @@ public class RealmAdminResource { @Path("events/config") @Produces(MediaType.APPLICATION_JSON) public RealmEventsConfigRepresentation getRealmEventsConfig() { - auth.init(RealmAuth.Resource.EVENTS).requireView(); + auth.realm().requireViewEvents(); RealmEventsConfigRepresentation config = ModelToRepresentation.toEventsConfigReprensetation(realm); if (config.getEnabledEventTypes() == null || config.getEnabledEventTypes().isEmpty()) { @@ -555,7 +551,7 @@ public class RealmAdminResource { @Path("events/config") @Consumes(MediaType.APPLICATION_JSON) public void updateRealmEventsConfig(final RealmEventsConfigRepresentation rep) { - auth.init(RealmAuth.Resource.EVENTS).requireManage(); + auth.realm().requireManageEvents(); logger.debug("updating realm events config: " + realm.getName()); new RealmManager(session).updateRealmEventsConfig(rep, realm); @@ -584,7 +580,7 @@ public class RealmAdminResource { @QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo, @QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) { - auth.init(RealmAuth.Resource.EVENTS).requireView(); + auth.realm().requireViewEvents(); EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); @@ -677,7 +673,7 @@ public class RealmAdminResource { @QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults, @QueryParam("resourceTypes") List resourceTypes) { - auth.init(RealmAuth.Resource.EVENTS).requireView(); + auth.realm().requireViewEvents(); EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); AdminEventQuery query = eventStore.createAdminQuery().realm(realm.getId());; @@ -770,7 +766,7 @@ public class RealmAdminResource { @Path("events") @DELETE public void clearEvents() { - auth.init(RealmAuth.Resource.EVENTS).requireManage(); + auth.realm().requireManageEvents(); EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); eventStore.clear(realm.getId()); @@ -783,7 +779,7 @@ public class RealmAdminResource { @Path("admin-events") @DELETE public void clearAdminEvents() { - auth.init(RealmAuth.Resource.EVENTS).requireManage(); + auth.realm().requireManageEvents(); EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); eventStore.clearAdmin(realm.getId()); @@ -805,7 +801,7 @@ public class RealmAdminResource { @QueryParam("bindDn") String bindDn, @QueryParam("bindCredential") String bindCredential, @QueryParam("useTruststoreSpi") String useTruststoreSpi, @QueryParam("connectionTimeout") String connectionTimeout, @QueryParam("componentId") String componentId) { - auth.init(RealmAuth.Resource.REALM).requireManage(); + auth.realm().requireManageRealm(); if (componentId != null && bindCredential.equals(ComponentRepresentation.SECRET_VALUE)) { bindCredential = realm.getComponent(componentId).getConfig().getFirst(LDAPConstants.BIND_CREDENTIAL); @@ -830,7 +826,7 @@ public class RealmAdminResource { @Produces(MediaType.APPLICATION_JSON) @Path("default-groups") public List getDefaultGroups() { - auth.requireView(); + auth.realm().requireViewRealm(); List defaults = new LinkedList<>(); for (GroupModel group : realm.getDefaultGroups()) { @@ -842,7 +838,7 @@ public class RealmAdminResource { @NoCache @Path("default-groups/{groupId}") public void addDefaultGroup(@PathParam("groupId") String groupId) { - auth.requireManage(); + auth.realm().requireManageRealm(); GroupModel group = realm.getGroupById(groupId); if (group == null) { @@ -857,7 +853,7 @@ public class RealmAdminResource { @NoCache @Path("default-groups/{groupId}") public void removeDefaultGroup(@PathParam("groupId") String groupId) { - auth.requireManage(); + auth.realm().requireManageRealm(); GroupModel group = realm.getGroupById(groupId); if (group == null) { @@ -882,13 +878,12 @@ public class RealmAdminResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public GroupRepresentation getGroupByPath(@PathParam("path") String path) { - auth.requireView(); - GroupModel found = KeycloakModelUtils.findGroupByPath(realm, path); if (found == null) { throw new NotFoundException("Group path does not exist"); } + auth.groups().requireView(found); return ModelToRepresentation.toGroupHierarchy(found, true); } @@ -902,7 +897,7 @@ public class RealmAdminResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response partialImport(PartialImportRepresentation rep) { - auth.requireManage(); + auth.realm().requireManageRealm(); PartialImportManager partialImport = new PartialImportManager(rep, session, realm, adminEvent); return partialImport.saveResources(); @@ -915,7 +910,7 @@ public class RealmAdminResource { @Path("clear-realm-cache") @POST public void clearRealmCache() { - auth.requireManage(); + auth.realm().requireManageRealm(); CacheRealmProvider cache = session.getProvider(CacheRealmProvider.class); if (cache != null) { @@ -932,7 +927,7 @@ public class RealmAdminResource { @Path("clear-user-cache") @POST public void clearUserCache() { - auth.requireManage(); + auth.realm().requireManageRealm(); UserCache cache = session.getProvider(UserCache.class); if (cache != null) { @@ -949,7 +944,7 @@ public class RealmAdminResource { @Path("clear-keys-cache") @POST public void clearKeysCache() { - auth.requireManage(); + auth.realm().requireManageRealm(); PublicKeyStorageProvider cache = session.getProvider(PublicKeyStorageProvider.class); if (cache != null) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java index 7a948d9768..9e0a89e5b9 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java @@ -34,6 +34,8 @@ import org.keycloak.services.ErrorResponse; import org.keycloak.services.ForbiddenException; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -191,13 +193,7 @@ public class RealmsAdminResource { && !auth.getRealm().equals(realm)) { throw new ForbiddenException(); } - RealmAuth realmAuth; - - if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) { - realmAuth = new RealmAuth(auth, realm.getMasterAdminClient()); - } else { - realmAuth = new RealmAuth(auth, realm.getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm()))); - } + AdminPermissionEvaluator realmAuth = AdminPermissions.evaluator(session, realm, auth); AdminEventBuilder adminEvent = new AdminEventBuilder(realm, auth, session, clientConnection); session.getContext().setRealm(realm); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java index 9d3e1006cc..b2ae6ad776 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java @@ -19,8 +19,10 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.authorization.admin.permissions.MgmtPermissions; -import org.keycloak.authorization.admin.permissions.RoleMgmtPermissions; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; +import org.keycloak.services.resources.admin.permissions.RolePermissionManagement; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; @@ -57,7 +59,7 @@ import java.util.Set; public class RoleByIdResource extends RoleResource { protected static final Logger logger = Logger.getLogger(RoleByIdResource.class); private final RealmModel realm; - private final RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; @Context @@ -66,7 +68,7 @@ public class RoleByIdResource extends RoleResource { @Context private UriInfo uriInfo; - public RoleByIdResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public RoleByIdResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { super(realm); this.realm = realm; @@ -85,9 +87,9 @@ public class RoleByIdResource extends RoleResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public RoleRepresentation getRole(final @PathParam("role-id") String id) { - auth.requireAny(); RoleModel roleModel = getRoleModel(id); + auth.roles().requireView(roleModel); return getRole(roleModel); } @@ -96,17 +98,7 @@ public class RoleByIdResource extends RoleResource { if (roleModel == null) { throw new NotFoundException("Could not find role with id"); } - - RealmAuth.Resource r = null; - if (roleModel.getContainer() instanceof RealmModel) { - r = RealmAuth.Resource.REALM; - } else if (roleModel.getContainer() instanceof ClientModel) { - r = RealmAuth.Resource.CLIENT; - } else if (roleModel.getContainer() instanceof UserModel) { - r = RealmAuth.Resource.USER; - } - auth.init(r); - return roleModel; + return roleModel; } /** @@ -118,9 +110,8 @@ public class RoleByIdResource extends RoleResource { @DELETE @NoCache public void deleteRole(final @PathParam("role-id") String id) { - auth.requireManage(); - RoleModel role = getRoleModel(id); + auth.roles().requireManage(role); deleteRole(role); if (role.isClientRole()) { @@ -142,9 +133,8 @@ public class RoleByIdResource extends RoleResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public void updateRole(final @PathParam("role-id") String id, final RoleRepresentation rep) { - auth.requireManage(); - RoleModel role = getRoleModel(id); + auth.roles().requireManage(role); updateRole(rep, role); if (role.isClientRole()) { @@ -166,10 +156,9 @@ public class RoleByIdResource extends RoleResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addComposites(final @PathParam("role-id") String id, List roles) { - auth.requireManage(); - RoleModel role = getRoleModel(id); - addComposites(adminEvent, uriInfo, roles, role); + auth.roles().requireManage(role); + addComposites(auth, adminEvent, uriInfo, roles, role); } /** @@ -185,11 +174,10 @@ public class RoleByIdResource extends RoleResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Set getRoleComposites(final @PathParam("role-id") String id) { - auth.requireAny(); if (logger.isDebugEnabled()) logger.debug("*** getRoleComposites: '" + id + "'"); RoleModel role = getRoleModel(id); - auth.requireView(); + auth.roles().requireView(role); return getRoleComposites(role); } @@ -204,9 +192,9 @@ public class RoleByIdResource extends RoleResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Set getRealmRoleComposites(final @PathParam("role-id") String id) { - auth.requireAny(); - RoleModel role = getRoleModel(id); + auth.roles().requireView(role); + auth.roles().requireView(role); return getRealmRoleComposites(role); } @@ -223,9 +211,9 @@ public class RoleByIdResource extends RoleResource { @Produces(MediaType.APPLICATION_JSON) public Set getClientRoleComposites(final @PathParam("role-id") String id, final @PathParam("client") String client) { - auth.requireAny(); RoleModel role = getRoleModel(id); + auth.roles().requireView(role); ClientModel clientModel = realm.getClientById(client); if (clientModel == null) { throw new NotFoundException("Could not find client"); @@ -243,9 +231,8 @@ public class RoleByIdResource extends RoleResource { @DELETE @Consumes(MediaType.APPLICATION_JSON) public void deleteComposites(final @PathParam("role-id") String id, List roles) { - auth.requireManage(); - RoleModel role = getRoleModel(id); + auth.roles().requireManage(role); deleteComposites(adminEvent, uriInfo, roles, role); } @@ -261,24 +248,21 @@ public class RoleByIdResource extends RoleResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public ManagementPermissionReference getManagementPermissions(final @PathParam("role-id") String id) { - auth.requireView(); - RoleModel role = getRoleModel(id); + auth.roles().requireView(role); - MgmtPermissions permissions = new MgmtPermissions(session, realm); + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); if (!permissions.roles().isPermissionsEnabled(role)) { return new ManagementPermissionReference(); } return toMgmtRef(role, permissions); } - public static ManagementPermissionReference toMgmtRef(RoleModel role, MgmtPermissions permissions) { + public static ManagementPermissionReference toMgmtRef(RoleModel role, AdminPermissionManagement permissions) { ManagementPermissionReference ref = new ManagementPermissionReference(); ref.setEnabled(true); ref.setResource(permissions.roles().resource(role).getId()); - Map scopes = new HashMap<>(); - scopes.put(RoleMgmtPermissions.MAP_ROLE_SCOPE, permissions.roles().mapRolePermission(role).getId()); - ref.setScopePermissions(scopes); + ref.setScopePermissions(permissions.roles().getPermissions(role)); return ref; } @@ -295,11 +279,10 @@ public class RoleByIdResource extends RoleResource { @Consumes(MediaType.APPLICATION_JSON) @NoCache public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-id") String id, ManagementPermissionReference ref) { - auth.requireManage(); - RoleModel role = getRoleModel(id); + auth.roles().requireManage(role); - MgmtPermissions permissions = new MgmtPermissions(session, realm); + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); permissions.roles().setPermissionsEnabled(role, ref.isEnabled()); if (ref.isEnabled()) { return toMgmtRef(role, permissions); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java index 94e4defc86..7f4d49572b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java @@ -19,8 +19,9 @@ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.authorization.admin.permissions.MgmtPermissions; -import org.keycloak.authorization.admin.permissions.RoleMgmtPermissions; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; @@ -37,20 +38,20 @@ import org.keycloak.services.ErrorResponse; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; /** @@ -60,13 +61,19 @@ import java.util.Set; */ public class RoleContainerResource extends RoleResource { private final RealmModel realm; - private final RealmAuth auth; + protected AdminPermissionEvaluator auth; + protected AdminPermissionEvaluator.RequirePermissionCheck managePermission; + protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission; + protected RoleContainerModel roleContainer; private AdminEventBuilder adminEvent; private UriInfo uriInfo; private KeycloakSession session; - public RoleContainerResource(KeycloakSession session, UriInfo uriInfo, RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) { + public RoleContainerResource(KeycloakSession session, UriInfo uriInfo, RealmModel realm, + AdminPermissionEvaluator auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent, + AdminPermissionEvaluator.RequirePermissionCheck managePermission, + AdminPermissionEvaluator.RequirePermissionCheck viewPermission) { super(realm); this.uriInfo = uriInfo; this.realm = realm; @@ -74,6 +81,8 @@ public class RoleContainerResource extends RoleResource { this.roleContainer = roleContainer; this.adminEvent = adminEvent; this.session = session; + this.managePermission = managePermission; + this.viewPermission = viewPermission; } /** @@ -85,11 +94,7 @@ public class RoleContainerResource extends RoleResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getRoles() { - auth.requireAny(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } + auth.roles().requireList(roleContainer); Set roleModels = roleContainer.getRoles(); List roles = new ArrayList(); @@ -108,11 +113,7 @@ public class RoleContainerResource extends RoleResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response createRole(final RoleRepresentation rep) { - auth.requireManage(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } + managePermission.require(); if (rep.getName() == null) { throw new BadRequestException(); @@ -151,16 +152,12 @@ public class RoleContainerResource extends RoleResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public RoleRepresentation getRole(final @PathParam("role-name") String roleName) { - auth.requireView(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } RoleModel roleModel = roleContainer.getRole(roleName); if (roleModel == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireView(roleModel); return getRole(roleModel); } @@ -174,16 +171,11 @@ public class RoleContainerResource extends RoleResource { @DELETE @NoCache public void deleteRole(final @PathParam("role-name") String roleName) { - auth.requireManage(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireManage(role); deleteRole(role); if (role.isClientRole()) { @@ -207,16 +199,11 @@ public class RoleContainerResource extends RoleResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public Response updateRole(final @PathParam("role-name") String roleName, final RoleRepresentation rep) { - auth.requireManage(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireManage(role); try { updateRole(rep, role); @@ -244,17 +231,12 @@ public class RoleContainerResource extends RoleResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addComposites(final @PathParam("role-name") String roleName, List roles) { - auth.requireManage(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - - RoleModel role = roleContainer.getRole(roleName); + RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } - addComposites(adminEvent, uriInfo, roles, role); + auth.roles().requireManage(role); + addComposites(auth, adminEvent, uriInfo, roles, role); } /** @@ -268,16 +250,11 @@ public class RoleContainerResource extends RoleResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Set getRoleComposites(final @PathParam("role-name") String roleName) { - auth.requireView(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireView(role); return getRoleComposites(role); } @@ -292,16 +269,11 @@ public class RoleContainerResource extends RoleResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Set getRealmRoleComposites(final @PathParam("role-name") String roleName) { - auth.requireView(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireView(role); return getRealmRoleComposites(role); } @@ -319,16 +291,11 @@ public class RoleContainerResource extends RoleResource { public Set getClientRoleComposites(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName, final @PathParam("client") String client) { - auth.requireView(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireView(role); ClientModel clientModel = realm.getClientById(client); if (client == null) { throw new NotFoundException("Could not find client"); @@ -350,16 +317,12 @@ public class RoleContainerResource extends RoleResource { public void deleteComposites( final @PathParam("role-name") String roleName, List roles) { - auth.requireManage(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireManage(role); deleteComposites(adminEvent, uriInfo, roles, role); } @@ -375,18 +338,13 @@ public class RoleContainerResource extends RoleResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public ManagementPermissionReference getManagementPermissions(final @PathParam("role-name") String roleName) { - auth.requireView(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireView(role); - MgmtPermissions permissions = new MgmtPermissions(session, realm); + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); if (!permissions.roles().isPermissionsEnabled(role)) { return new ManagementPermissionReference(); } @@ -406,19 +364,14 @@ public class RoleContainerResource extends RoleResource { @Consumes(MediaType.APPLICATION_JSON) @NoCache public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-name") String roleName, ManagementPermissionReference ref) { - auth.requireManage(); - - if (roleContainer == null) { - throw new NotFoundException("Could not find client"); - } - RoleModel role = roleContainer.getRole(roleName); if (role == null) { throw new NotFoundException("Could not find role"); } + auth.roles().requireManage(role); if (ref.isEnabled()) { - MgmtPermissions permissions = new MgmtPermissions(session, realm); + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); permissions.roles().setPermissionsEnabled(role, ref.isEnabled()); return RoleByIdResource.toMgmtRef(role, permissions); } else { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java index 93d4cb6223..8db05913c0 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java @@ -19,7 +19,7 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.authorization.admin.permissions.MgmtPermissions; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.common.ClientConnection; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; @@ -52,7 +52,6 @@ import javax.ws.rs.core.UriInfo; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -69,27 +68,17 @@ import java.util.stream.Collectors; */ public class RoleMapperResource { - /** - * RoleMapperResource is reused bewteen GroupResource and UserResource to manage role mappings. - * We don't know what type of resource we're managing here (user or group), so we don't know how to query the policy engine to determine - * if an action is allowed. - * - */ - public interface ManageResourcePermissionCheck { - boolean canManage(); - } - protected static final Logger logger = Logger.getLogger(RoleMapperResource.class); protected RealmModel realm; - private RealmAuth auth; - private RoleMapperModel roleMapper; private AdminEventBuilder adminEvent; - private ManageResourcePermissionCheck manageResourcePermissionCheck; + protected AdminPermissionEvaluator.RequirePermissionCheck managePermission; + protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission; + private AdminPermissionEvaluator auth; @Context protected ClientConnection clientConnection; @@ -103,18 +92,21 @@ public class RoleMapperResource { @Context protected HttpHeaders headers; - public RoleMapperResource(RealmModel realm, RealmAuth auth, RoleMapperModel roleMapper, AdminEventBuilder adminEvent) { + public RoleMapperResource(RealmModel realm, + AdminPermissionEvaluator auth, + RoleMapperModel roleMapper, + AdminEventBuilder adminEvent, + AdminPermissionEvaluator.RequirePermissionCheck manageCheck, + AdminPermissionEvaluator.RequirePermissionCheck viewCheck) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent.resource(ResourceType.REALM_ROLE_MAPPING); this.roleMapper = roleMapper; + this.managePermission = manageCheck; + this.viewPermission = viewCheck; } - public void setManageCheck(ManageResourcePermissionCheck mapperPermissions) { - this.manageResourcePermissionCheck = mapperPermissions; - } - /** * Get role mappings * @@ -124,11 +116,7 @@ public class RoleMapperResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public MappingsRepresentation getRoleMappings() { - auth.requireView(); - - if (roleMapper == null) { - throw new NotFoundException("User not found"); - } + viewPermission.require(); MappingsRepresentation all = new MappingsRepresentation(); Set realmMappings = roleMapper.getRealmRoleMappings(); @@ -173,11 +161,7 @@ public class RoleMapperResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getRealmRoleMappings() { - auth.requireView(); - - if (roleMapper == null) { - throw new NotFoundException("User not found"); - } + viewPermission.require(); Set realmMappings = roleMapper.getRealmRoleMappings(); List realmMappingsRep = new ArrayList(); @@ -199,11 +183,7 @@ public class RoleMapperResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getCompositeRealmRoleMappings() { - auth.requireView(); - - if (roleMapper == null) { - throw new NotFoundException("User not found"); - } + viewPermission.require(); Set roles = realm.getRoles(); List realmMappingsRep = new ArrayList(); @@ -225,11 +205,7 @@ public class RoleMapperResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getAvailableRealmRoleMappings() { - auth.requireView(); - - if (roleMapper == null) { - throw new NotFoundException("User not found"); - } + viewPermission.require(); Set available = realm.getRoles(); Set set = available.stream().filter(r -> @@ -247,11 +223,7 @@ public class RoleMapperResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addRealmRoleMappings(List roles) { - checkManagePermission(); - - if (roleMapper == null) { - throw new NotFoundException("User not found"); - } + managePermission.require(); logger.debugv("** addRealmRoleMappings: {0}", roles); @@ -267,16 +239,6 @@ public class RoleMapperResource { adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success(); } - private void checkManagePermission() { - if (manageResourcePermissionCheck == null) { - auth.requireManage(); - } else { - if (!manageResourcePermissionCheck.canManage()) { - throw new ForbiddenException(); - } - } - } - /** * Delete realm-level role mappings * @@ -286,11 +248,7 @@ public class RoleMapperResource { @DELETE @Consumes(MediaType.APPLICATION_JSON) public void deleteRealmRoleMappings(List roles) { - checkManagePermission(); - - if (roleMapper == null) { - throw new NotFoundException("User not found"); - } + managePermission.require(); logger.debug("deleteRealmRoleMappings"); if (roles == null) { @@ -313,7 +271,7 @@ public class RoleMapperResource { try { roleMapper.deleteRoleMapping(roleModel); } catch (ModelException me) { - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()), Response.Status.BAD_REQUEST); } @@ -332,16 +290,18 @@ public class RoleMapperResource { } private boolean canMapRole(RoleModel roleModel) { - return new MgmtPermissions(session, realm, auth.getAuth()).roles().canMapRole(roleModel); + return auth.roles().canMapRole(roleModel); } @Path("clients/{client}") public ClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("client") String client) { ClientModel clientModel = realm.getClientById(client); - ClientRoleMappingsResource resource = new ClientRoleMappingsResource(uriInfo, session, realm, auth, roleMapper, clientModel, adminEvent); - resource.setManageCheck(() -> { - return new MgmtPermissions(session, realm, auth.getAuth()).users().canManage(); - }); + if (clientModel == null) { + throw new NotFoundException("Client not found"); + } + ClientRoleMappingsResource resource = new ClientRoleMappingsResource(uriInfo, session, realm, auth, roleMapper, + clientModel, adminEvent, + managePermission, viewPermission); return resource; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java index 5fb1d3423b..cdd9cd7a20 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java @@ -25,6 +25,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.core.UriInfo; import java.util.Collections; @@ -60,12 +61,13 @@ public abstract class RoleResource { if (rep.isScopeParamRequired() != null) role.setScopeParamRequired(rep.isScopeParamRequired()); } - protected void addComposites(AdminEventBuilder adminEvent, UriInfo uriInfo, List roles, RoleModel role) { + protected void addComposites(AdminPermissionEvaluator auth, AdminEventBuilder adminEvent, UriInfo uriInfo, List roles, RoleModel role) { for (RoleRepresentation rep : roles) { RoleModel composite = realm.getRoleById(rep.getId()); if (composite == null) { throw new NotFoundException("Could not find composite role"); } + auth.roles().requireManage(composite); role.addCompositeRole(composite); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java index 431e97cc98..4f7b5dcd6f 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java @@ -29,6 +29,7 @@ import org.keycloak.models.ScopeContainerModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -49,19 +50,25 @@ import java.util.Set; */ public class ScopeMappedClientResource { protected RealmModel realm; - private RealmAuth auth; + protected AdminPermissionEvaluator auth; + protected AdminPermissionEvaluator.RequirePermissionCheck managePermission; + protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission; protected ScopeContainerModel scopeContainer; protected KeycloakSession session; protected ClientModel scopedClient; protected AdminEventBuilder adminEvent; - public ScopeMappedClientResource(RealmModel realm, RealmAuth auth, ScopeContainerModel scopeContainer, KeycloakSession session, ClientModel scopedClient, AdminEventBuilder adminEvent) { + public ScopeMappedClientResource(RealmModel realm, AdminPermissionEvaluator auth, ScopeContainerModel scopeContainer, KeycloakSession session, ClientModel scopedClient, AdminEventBuilder adminEvent, + AdminPermissionEvaluator.RequirePermissionCheck managePermission, + AdminPermissionEvaluator.RequirePermissionCheck viewPermission) { this.realm = realm; this.auth = auth; this.scopeContainer = scopeContainer; this.session = session; this.scopedClient = scopedClient; this.adminEvent = adminEvent.resource(ResourceType.CLIENT_SCOPE_MAPPING); + this.managePermission = managePermission; + this.viewPermission = viewPermission; } /** @@ -75,11 +82,7 @@ public class ScopeMappedClientResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getClientScopeMappings() { - auth.requireView(); - - if (scopeContainer == null) { - throw new NotFoundException("Could not find client"); - } + viewPermission.require(); Set mappings = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer); //scopedClient.getClientScopeMappings(client); List mapRep = new ArrayList(); @@ -101,14 +104,10 @@ public class ScopeMappedClientResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getAvailableClientScopeMappings() { - auth.requireView(); - - if (scopeContainer == null) { - throw new NotFoundException("Could not find client"); - } + viewPermission.require(); Set roles = scopedClient.getRoles(); - return ScopeMappedResource.getAvailable(scopeContainer, roles); + return ScopeMappedResource.getAvailable(auth, scopeContainer, roles); } /** @@ -123,11 +122,7 @@ public class ScopeMappedClientResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getCompositeClientScopeMappings() { - auth.requireView(); - - if (scopeContainer == null) { - throw new NotFoundException("Could not find client"); - } + viewPermission.require(); Set roles = scopedClient.getRoles(); return ScopeMappedResource.getComposite(scopeContainer, roles); @@ -141,11 +136,7 @@ public class ScopeMappedClientResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addClientScopeMapping(List roles) { - auth.requireManage(); - - if (scopeContainer == null) { - throw new NotFoundException("Could not find client"); - } + managePermission.require(); for (RoleRepresentation role : roles) { RoleModel roleModel = scopedClient.getRole(role.getName()); @@ -166,11 +157,7 @@ public class ScopeMappedClientResource { @DELETE @Consumes(MediaType.APPLICATION_JSON) public void deleteClientScopeMapping(List roles) { - auth.requireManage(); - - if (scopeContainer == null) { - throw new NotFoundException("Could not find client"); - } + managePermission.require(); if (roles == null) { Set roleModels = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer);//scopedClient.getClientScopeMappings(client); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java index 19a32f9de7..286e22b5b5 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java @@ -31,6 +31,7 @@ import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.ClientMappingsRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -56,17 +57,25 @@ import java.util.Set; */ public class ScopeMappedResource { protected RealmModel realm; - private RealmAuth auth; + protected AdminPermissionEvaluator auth; + protected AdminPermissionEvaluator.RequirePermissionCheck managePermission; + protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission; + protected ScopeContainerModel scopeContainer; protected KeycloakSession session; protected AdminEventBuilder adminEvent; - public ScopeMappedResource(RealmModel realm, RealmAuth auth, ScopeContainerModel scopeContainer, KeycloakSession session, AdminEventBuilder adminEvent) { + public ScopeMappedResource(RealmModel realm, AdminPermissionEvaluator auth, ScopeContainerModel scopeContainer, + KeycloakSession session, AdminEventBuilder adminEvent, + AdminPermissionEvaluator.RequirePermissionCheck managePermission, + AdminPermissionEvaluator.RequirePermissionCheck viewPermission) { this.realm = realm; this.auth = auth; this.scopeContainer = scopeContainer; this.session = session; this.adminEvent = adminEvent.resource(ResourceType.REALM_SCOPE_MAPPING); + this.managePermission = managePermission; + this.viewPermission = viewPermission; } /** @@ -78,7 +87,7 @@ public class ScopeMappedResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public MappingsRepresentation getScopeMappings() { - auth.requireView(); + viewPermission.require(); if (scopeContainer == null) { throw new NotFoundException("Could not find client"); @@ -126,7 +135,7 @@ public class ScopeMappedResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getRealmScopeMappings() { - auth.requireView(); + viewPermission.require(); if (scopeContainer == null) { throw new NotFoundException("Could not find client"); @@ -150,20 +159,21 @@ public class ScopeMappedResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getAvailableRealmScopeMappings() { - auth.requireView(); + viewPermission.require(); if (scopeContainer == null) { throw new NotFoundException("Could not find client"); } Set roles = realm.getRoles(); - return getAvailable(scopeContainer, roles); + return getAvailable(auth, scopeContainer, roles); } - public static List getAvailable(ScopeContainerModel client, Set roles) { + public static List getAvailable(AdminPermissionEvaluator auth, ScopeContainerModel client, Set roles) { List available = new ArrayList(); for (RoleModel roleModel : roles) { if (client.hasScope(roleModel)) continue; + if (!auth.roles().canMapClientScope(roleModel)) continue; available.add(ModelToRepresentation.toRepresentation(roleModel)); } return available; @@ -183,7 +193,7 @@ public class ScopeMappedResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getCompositeRealmScopeMappings() { - auth.requireView(); + viewPermission.require(); if (scopeContainer == null) { throw new NotFoundException("Could not find client"); @@ -210,7 +220,7 @@ public class ScopeMappedResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void addRealmScopeMappings(List roles) { - auth.requireManage(); + managePermission.require(); if (scopeContainer == null) { throw new NotFoundException("Could not find client"); @@ -236,7 +246,7 @@ public class ScopeMappedResource { @DELETE @Consumes(MediaType.APPLICATION_JSON) public void deleteRealmScopeMappings(List roles) { - auth.requireManage(); + managePermission.require(); if (scopeContainer == null) { throw new NotFoundException("Could not find client"); @@ -268,6 +278,9 @@ public class ScopeMappedResource { @Path("clients/{client}") public ScopeMappedClientResource getClientByIdScopeMappings(@PathParam("client") String client) { ClientModel clientModel = realm.getClientById(client); - return new ScopeMappedClientResource(realm, auth, this.scopeContainer, session, clientModel, adminEvent); + if (clientModel == null) { + throw new NotFoundException("Could not find client"); + } + return new ScopeMappedClientResource(realm, auth, this.scopeContainer, session, clientModel, adminEvent, managePermission, viewPermission); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java index 4ffcf86032..638f57ba6f 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserStorageProviderResource.java @@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.UserStorageSyncManager; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.ldap.LDAPStorageProvider; @@ -55,7 +56,7 @@ public class UserStorageProviderResource { protected RealmModel realm; - protected RealmAuth auth; + protected AdminPermissionEvaluator auth; protected AdminEventBuilder adminEvent; @@ -71,12 +72,10 @@ public class UserStorageProviderResource { @Context protected HttpHeaders headers; - public UserStorageProviderResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public UserStorageProviderResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent; - - auth.init(RealmAuth.Resource.USER); } /** @@ -94,7 +93,7 @@ public class UserStorageProviderResource { @Produces(MediaType.APPLICATION_JSON) public SynchronizationResult syncUsers(@PathParam("id") String id, @QueryParam("action") String action) { - auth.requireManage(); + auth.users().requireManage(); ComponentModel model = realm.getComponent(id); if (model == null) { @@ -139,7 +138,7 @@ public class UserStorageProviderResource { @Path("{id}/remove-imported-users") @NoCache public void removeImportedUsers(@PathParam("id") String id) { - auth.requireManage(); + auth.users().requireManage(); ComponentModel model = realm.getComponent(id); if (model == null) { @@ -162,7 +161,7 @@ public class UserStorageProviderResource { @Path("{id}/unlink-users") @NoCache public void unlinkUsers(@PathParam("id") String id) { - auth.requireManage(); + auth.users().requireManage(); ComponentModel model = realm.getComponent(id); if (model == null) { @@ -187,7 +186,7 @@ public class UserStorageProviderResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public SynchronizationResult syncMapperData(@PathParam("parentId") String parentId, @PathParam("id") String mapperId, @QueryParam("direction") String direction) { - auth.requireManage(); + auth.users().requireManage(); ComponentModel parentModel = realm.getComponent(parentId); if (parentModel == null) throw new NotFoundException("Parent model not found"); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 2685e8ebf1..502fbe3ed0 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -22,8 +22,8 @@ import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.authentication.RequiredActionProvider; -import org.keycloak.authorization.admin.permissions.MgmtPermissions; import org.keycloak.authentication.actiontoken.execactions.ExecuteActionsActionToken; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.common.ClientConnection; import org.keycloak.common.Profile; import org.keycloak.common.util.Time; @@ -36,7 +36,6 @@ import org.keycloak.events.EventType; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; import org.keycloak.models.Constants; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.GroupModel; @@ -116,7 +115,7 @@ public class UsersResource { protected RealmModel realm; - private RealmAuth auth; + private AdminPermissionEvaluator auth; private AdminEventBuilder adminEvent; @@ -132,12 +131,10 @@ public class UsersResource { @Context protected HttpHeaders headers; - public UsersResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public UsersResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent.resource(ResourceType.USER); - - auth.init(RealmAuth.Resource.USER); } /** @@ -151,13 +148,13 @@ public class UsersResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public Response updateUser(final @PathParam("id") String id, final UserRepresentation rep) { - auth.requireManage(); try { UserModel user = session.users().getUserById(id, realm); if (user == null) { return Response.status(Status.NOT_FOUND).build(); } + auth.users().requireManage(user); Set attrsToRemove; if (rep.getAttributes() != null) { @@ -188,6 +185,8 @@ public class UsersResource { } catch (ModelException me) { logger.warn("Could not update user!", me); return ErrorResponse.exists("Could not update user!"); + } catch (ForbiddenException fe) { + throw fe; } catch (Exception me) { // JPA logger.warn("Could not update user!", me);// may be committed by JTA which can't return ErrorResponse.exists("Could not update user!"); @@ -206,7 +205,7 @@ public class UsersResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) { - auth.requireManage(); + auth.users().requireManage(); // Double-check duplicated username and email here due to federation if (session.users().getUserByUsername(rep.getUsername(), realm) != null) { @@ -291,13 +290,13 @@ public class UsersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public UserRepresentation getUser(final @PathParam("id") String id) { - auth.requireView(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireView(user); + UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, user); if (realm.isIdentityFederationEnabled()) { @@ -325,19 +324,17 @@ public class UsersResource { public Map impersonate(final @PathParam("id") String id) { ProfileHelper.requireFeature(Profile.Feature.IMPERSONATION); - auth.init(RealmAuth.Resource.IMPERSONATION); - auth.requireManage(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } - RealmModel authenticatedRealm = auth.getAuth().getRealm(); + auth.users().requireImpersonate(user); + RealmModel authenticatedRealm = auth.adminAuth().getRealm(); // if same realm logout before impersonation boolean sameRealm = false; if (authenticatedRealm.getId().equals(realm.getId())) { sameRealm = true; - UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.getAuth().getToken().getSessionState()); + UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.adminAuth().getToken().getSessionState()); AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection); AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection); AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true); @@ -355,7 +352,7 @@ public class UsersResource { .session(userSession) .user(user) .detail(Details.IMPERSONATOR_REALM,authenticatedRealm.getName()) - .detail(Details.IMPERSONATOR, auth.getAuth().getUser().getUsername()).success(); + .detail(Details.IMPERSONATOR, auth.adminAuth().getUser().getUsername()).success(); return result; } @@ -372,12 +369,11 @@ public class UsersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getSessions(final @PathParam("id") String id) { - auth.requireView(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireView(user); List sessions = session.sessions().getUserSessions(realm, user); List reps = new ArrayList(); for (UserSessionModel session : sessions) { @@ -398,12 +394,11 @@ public class UsersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getOfflineSessions(final @PathParam("id") String id, final @PathParam("clientId") String clientId) { - auth.requireView(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireView(user); ClientModel client = realm.getClientById(clientId); if (client == null) { throw new NotFoundException("Client not found"); @@ -439,12 +434,11 @@ public class UsersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getFederatedIdentity(final @PathParam("id") String id) { - auth.requireView(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireView(user); return getFederatedIdentities(user); } @@ -476,12 +470,12 @@ public class UsersResource { @POST @NoCache public Response addFederatedIdentity(final @PathParam("id") String id, final @PathParam("provider") String provider, FederatedIdentityRepresentation rep) { - auth.requireManage(); UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); if (session.users().getFederatedIdentity(user, provider, realm) != null) { return ErrorResponse.exists("User is already linked with provider"); } @@ -502,12 +496,11 @@ public class UsersResource { @DELETE @NoCache public void removeFederatedIdentity(final @PathParam("id") String id, final @PathParam("provider") String provider) { - auth.requireManage(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); if (!session.users().removeFederatedIdentity(realm, user, provider)) { throw new NotFoundException("Link not found"); } @@ -525,13 +518,11 @@ public class UsersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getConsents(final @PathParam("id") String id) { - auth.requireView(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } - + auth.users().requireView(user); List> result = new LinkedList<>(); Set offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user); @@ -580,14 +571,16 @@ public class UsersResource { @DELETE @NoCache public void revokeConsent(final @PathParam("id") String id, final @PathParam("client") String clientId) { - auth.requireManage(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); ClientModel client = realm.getClientByClientId(clientId); + if (client == null) { + throw new NotFoundException("Client not found"); + } boolean revokedConsent = session.users().revokeConsentForClient(realm, user.getId(), client.getId()); boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client); @@ -612,12 +605,11 @@ public class UsersResource { @Path("{id}/logout") @POST public void logout(final @PathParam("id") String id) { - auth.requireManage(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); List userSessions = session.sessions().getUserSessions(realm, user); for (UserSessionModel userSession : userSessions) { @@ -635,12 +627,11 @@ public class UsersResource { @DELETE @NoCache public Response deleteUser(final @PathParam("id") String id) { - auth.requireManage(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); boolean removed = new UserManager(session).removeUser(realm, user); if (removed) { @@ -675,7 +666,7 @@ public class UsersResource { @QueryParam("username") String username, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) { - auth.requireView(); + auth.users().requireQuery(); firstResult = firstResult != null ? firstResult : -1; maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS; @@ -703,7 +694,9 @@ public class UsersResource { userModels = session.users().getUsers(realm, firstResult, maxResults, false); } + boolean canViewGlobal = auth.users().canView(); for (UserModel user : userModels) { + if (!canViewGlobal && !auth.users().canView(user)) continue; results.add(ModelToRepresentation.toRepresentation(session, realm, user)); } return results; @@ -714,21 +707,23 @@ public class UsersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public Integer getUsersCount() { - auth.requireView(); + auth.users().requireView(); return session.users().getUsersCount(realm); } @Path("{id}/role-mappings") public RoleMapperResource getRoleMappings(@PathParam("id") String id) { - auth.init(RealmAuth.Resource.USER); UserModel user = session.users().getUserById(id, realm); - RoleMapperResource resource = new RoleMapperResource(realm, auth, user, adminEvent); - resource.setManageCheck(() -> { - return new MgmtPermissions(session, realm, auth.getAuth()).users().canManage(); - }); + if (user == null) { + throw new NotFoundException("User not found"); + } + + AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.users().requireManage(user); + AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.users().requireView(user); + RoleMapperResource resource = new RoleMapperResource(realm, auth, user, adminEvent, manageCheck, viewCheck); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; @@ -744,12 +739,12 @@ public class UsersResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public void disableCredentialType(@PathParam("id") String id, List credentialTypes) { - auth.requireManage(); UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); if (credentialTypes == null) return; for (String type : credentialTypes) { session.userCredentialManager().disableCredentialType(realm, user, type); @@ -771,12 +766,12 @@ public class UsersResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public void resetPassword(@PathParam("id") String id, CredentialRepresentation pass) { - auth.requireManage(); UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); if (pass == null || pass.getValue() == null || !CredentialRepresentation.PASSWORD.equals(pass.getType())) { throw new BadRequestException("No password provided"); } @@ -792,7 +787,7 @@ public class UsersResource { } catch (ReadOnlyException mre) { throw new BadRequestException("Can't reset password as account is read only"); } catch (ModelException e) { - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()), Status.BAD_REQUEST); } @@ -810,13 +805,15 @@ public class UsersResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public void removeTotp(@PathParam("id") String id) { - auth.requireManage(); UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); + + session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP); adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success(); } @@ -870,12 +867,12 @@ public class UsersResource { @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId, @QueryParam("lifespan") Integer lifespan, List actions) { - auth.requireManage(); UserModel user = session.users().getUserById(id, realm); if (user == null) { return ErrorResponse.error("User not found", Response.Status.NOT_FOUND); } + auth.users().requireManage(user); if (user.getEmail() == null) { return ErrorResponse.error("User email missing", Response.Status.BAD_REQUEST); @@ -964,12 +961,12 @@ public class UsersResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List groupMembership(@PathParam("id") String id) { - auth.requireView(); UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireView(user); List memberships = new LinkedList<>(); for (GroupModel group : user.getGroups()) { memberships.add(ModelToRepresentation.toRepresentation(group, false)); @@ -981,12 +978,13 @@ public class UsersResource { @Path("{id}/groups/{groupId}") @NoCache public void removeMembership(@PathParam("id") String id, @PathParam("groupId") String groupId) { - auth.requireManage(); UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); + GroupModel group = session.realms().getGroupById(groupId, realm); if (group == null) { throw new NotFoundException("Group not found"); @@ -998,7 +996,7 @@ public class UsersResource { adminEvent.operation(OperationType.DELETE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success(); } } catch (ModelException me) { - Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()), Response.Status.BAD_REQUEST); } @@ -1008,12 +1006,12 @@ public class UsersResource { @Path("{id}/groups/{groupId}") @NoCache public void joinGroup(@PathParam("id") String id, @PathParam("groupId") String groupId) { - auth.requireManage(); UserModel user = session.users().getUserById(id, realm); if (user == null) { throw new NotFoundException("User not found"); } + auth.users().requireManage(user); GroupModel group = session.realms().getGroupById(groupId, realm); if (group == null) { throw new NotFoundException("Group not found"); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionEvaluator.java new file mode 100644 index 0000000000..56be4cf588 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionEvaluator.java @@ -0,0 +1,55 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.services.resources.admin.AdminAuth; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface AdminPermissionEvaluator { + RealmPermissionEvaluator realm(); + + AdminAuth adminAuth(); + + RolePermissionEvaluator roles(); + UserPermissionEvaluator users(); + ClientPermissionEvaluator clients(); + GroupPermissionEvaluator groups(); + + /** + * Useful as a function pointer, i.e. RoleMapperResource is reused bewteen GroupResource and UserResource to manage role mappings. + * We don't know what type of resource we're managing here (user or group), so we don't know how to query the policy engine to determine + * if an action is allowed. + * + */ + interface PermissionCheck { + boolean evaluate(); + } + /** + * Useful as a function pointer, i.e. RoleMapperResource is reused bewteen GroupResource and UserResource to manage role mappings. + * We don't know what type of resource we're managing here (user or group), so we don't know how to query the policy engine to determine + * if an action is allowed. + * + * throws appropriate exception if permission is deny + * + */ + interface RequirePermissionCheck { + void require(); + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java new file mode 100644 index 0000000000..1f0a34dd91 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java @@ -0,0 +1,35 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.authorization.model.ResourceServer; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface AdminPermissionManagement { + public static final String MANAGE_SCOPE = "manage"; + public static final String VIEW_SCOPE = "view"; + + RolePermissionManagement roles(); + UserPermissionManagement users(); + GroupPermissionManagement groups(); + ClientPermissionManagement clients(); + + ResourceServer realmResourceServer(); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java new file mode 100644 index 0000000000..61a8bffe5e --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java @@ -0,0 +1,43 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.resources.admin.AdminAuth; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class AdminPermissions { + + + public static AdminPermissionEvaluator evaluator(KeycloakSession session, RealmModel realm, AdminAuth auth) { + return new MgmtPermissions(session, realm, auth); + } + public static AdminPermissionEvaluator evaluator(KeycloakSession session, RealmModel realm, RealmModel adminsRealm, UserModel admin) { + return new MgmtPermissions(session, realm, adminsRealm, admin); + } + + public static AdminPermissionManagement management(KeycloakSession session, RealmModel realm) { + return new MgmtPermissions(session, realm); + } + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java new file mode 100644 index 0000000000..8f0af4b93c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java @@ -0,0 +1,68 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientTemplateModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface ClientPermissionEvaluator { + boolean isPermissionsEnabled(ClientModel client); + + void setPermissionsEnabled(ClientModel client, boolean enable); + + boolean canManage(); + + void requireManage(); + + boolean canManageTemplates(); + + void requireManageTemplates(); + + boolean canView(); + + boolean canList(); + + boolean canViewTemplates(); + + void requireList(); + + boolean canListTemplates(); + + void requireView(); + + void requireViewTemplates(); + + boolean canManage(ClientModel client); + + void requireManage(ClientModel client); + + boolean canView(ClientModel client); + + void requireView(ClientModel client); + + boolean canManage(ClientTemplateModel template); + + void requireManage(ClientTemplateModel template); + + boolean canView(ClientTemplateModel template); + + void requireView(ClientTemplateModel template); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java new file mode 100644 index 0000000000..27f55272e8 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java @@ -0,0 +1,29 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.models.ClientModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface ClientPermissionManagement { + boolean isPermissionsEnabled(ClientModel client); + + void setPermissionsEnabled(ClientModel client, boolean enable); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java new file mode 100644 index 0000000000..da1d24be53 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java @@ -0,0 +1,309 @@ +/* + * 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.services.resources.admin.permissions; + +import org.jboss.logging.Logger; +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.ClientTemplateModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.services.ForbiddenException; + +import java.util.HashSet; +import java.util.Set; + +/** + * Manages default policies for all users. + * + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionManagement { + private static final Logger logger = Logger.getLogger(ClientPermissions.class); + protected final KeycloakSession session; + protected final RealmModel realm; + protected final AuthorizationProvider authz; + protected final MgmtPermissions root; + + public ClientPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { + this.session = session; + this.realm = realm; + this.authz = authz; + this.root = root; + } + + private String getResourceName(ClientModel client) { + return "group.resource." + client.getId(); + } + + private String getManagePermissionName(ClientModel client) { + return "manage.permission.client." + client.getId(); + } + private String getViewPermissionName(ClientModel client) { + return "view.permission.client." + client.getId(); + } + + private void initialize(ClientModel client) { + ResourceServer server = root.findOrCreateResourceServer(client); + Scope manageScope = manageScope(server); + if (manageScope == null) { + authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.MANAGE_SCOPE, server); + } + Scope viewScope = viewScope(server); + if (manageScope == null) { + authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.VIEW_SCOPE, server); + } + + String resourceName = getResourceName(client); + Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId()); + if (resource == null) { + resource = authz.getStoreFactory().getResourceStore().create(resourceName, server, server.getClientId()); + Set scopeset = new HashSet<>(); + scopeset.add(manageScope); + scopeset.add(viewScope); + resource.updateScopes(scopeset); + } + String managePermissionName = getManagePermissionName(client); + Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId()); + if (managePermission == null) { + RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_CLIENTS); + Policy manageClientsPolicy = root.roles().rolePolicy(server, role); + Helper.addScopePermission(authz, server, managePermissionName, resource, manageScope, manageClientsPolicy); + } + String viewPermissionName = getViewPermissionName(client); + Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId()); + if (viewPermission == null) { + RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.VIEW_CLIENTS); + Policy viewClientsPolicy = root.roles().rolePolicy(server, role); + Helper.addScopePermission(authz, server, viewPermissionName, resource, viewScope, viewClientsPolicy); + } + } + + private void deletePermissions(ClientModel client) { + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return; + Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId()); + if (managePermission != null) { + authz.getStoreFactory().getPolicyStore().delete(managePermission.getId()); + } + Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId()); + if (viewPermission != null) { + authz.getStoreFactory().getPolicyStore().delete(viewPermission.getId()); + } + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());; + if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); + } + + @Override + public boolean isPermissionsEnabled(ClientModel client) { + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return false; + + return authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()) != null; + } + + @Override + public void setPermissionsEnabled(ClientModel client, boolean enable) { + if (enable) { + initialize(client); + } else { + deletePermissions(client); + } + } + + + + private Scope manageScope(ResourceServer server) { + return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.MANAGE_SCOPE, server.getId()); + } + + private Scope viewScope(ResourceServer server) { + return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.VIEW_SCOPE, server.getId()); + } + + @Override + public boolean canList() { + return root.hasAnyAdminRole(); + } + + @Override + public void requireList() { + if (!canList()) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canListTemplates() { + return root.hasAnyAdminRole(); + } + + public boolean canManageClientDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS); + } + public boolean canViewClientDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.VIEW_CLIENTS); + } + + @Override + public boolean canManage() { + return canManageClientDefault(); + } + + @Override + public void requireManage() { + if (!canManage()) { + throw new ForbiddenException(); + } + } + @Override + public boolean canView() { + return canManageClientDefault() || canViewClientDefault(); + } + + @Override + public void requireView() { + if (!canView()) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canManage(ClientModel client) { + if (!root.isAdminSameRealm()) { + return canManage(); + } + + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return canManage(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return canManage(); + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId()); + if (policy == null) { + return canManage(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return canManage(); + } + + Scope scope = manageScope(server); + return root.evaluatePermission(resource, scope, server); + } + + @Override + public void requireManage(ClientModel client) { + if (!canManage(client)) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canView(ClientModel client) { + if (!root.isAdminSameRealm()) { + return canView(); + } + + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return canView(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return canView(); + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId()); + if (policy == null) { + return canView(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return canView(); + } + + Scope scope = viewScope(server); + return root.evaluatePermission(resource, scope, server); + } + + @Override + public void requireView(ClientModel client) { + if (!canView(client)) { + throw new ForbiddenException(); + } + } + + // templates + + @Override + public boolean canViewTemplates() { + return canView(); + } + + @Override + public boolean canManageTemplates() { + return canManageClientDefault(); + } + + @Override + public void requireManageTemplates() { + if (!canManageTemplates()) { + throw new ForbiddenException(); + } + } + @Override + public void requireViewTemplates() { + if (!canViewTemplates()) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canManage(ClientTemplateModel template) { + return canManageClientDefault(); + } + + @Override + public void requireManage(ClientTemplateModel template) { + if (!canManage(template)) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canView(ClientTemplateModel template) { + return canViewClientDefault(); + } + + @Override + public void requireView(ClientTemplateModel template) { + if (!canView(template)) { + throw new ForbiddenException(); + } + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java new file mode 100644 index 0000000000..91ca5c5e9d --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java @@ -0,0 +1,53 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.models.GroupModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface GroupPermissionEvaluator { + boolean canList(); + + void requireList(); + + boolean canManage(GroupModel group); + + void requireManage(GroupModel group); + + boolean canView(GroupModel group); + + void requireView(GroupModel group); + + boolean canManage(); + + void requireManage(); + + boolean canView(); + + void requireView(); + + boolean canViewMembers(GroupModel group); + + void requireViewMembers(GroupModel group); + + boolean canManageMembers(GroupModel group); + + void requireManageMembers(GroupModel group); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java new file mode 100644 index 0000000000..5f4097be27 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java @@ -0,0 +1,37 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.authorization.model.Policy; +import org.keycloak.models.GroupModel; +import org.keycloak.models.RoleModel; + +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface GroupPermissionManagement { + boolean isPermissionsEnabled(GroupModel group); + void setPermissionsEnabled(GroupModel group, boolean enable); + + Policy viewMembersPermission(GroupModel group); + Policy manageMembersPermission(GroupModel group); + Policy viewPermissionGroup(GroupModel group); + Policy managePermissionGroup(GroupModel group); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java new file mode 100644 index 0000000000..46d8fb63fe --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java @@ -0,0 +1,383 @@ +/* + * 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.services.resources.admin.permissions; + +import org.jboss.logging.Logger; +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.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.services.ForbiddenException; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManagement { + private static final Logger logger = Logger.getLogger(GroupPermissions.class); + public static final String MAP_ROLE_SCOPE = "map-role"; + public static final String MANAGE_MEMBERS_SCOPE = "manage.members"; + public static final String VIEW_MEMBERS_SCOPE = "view.members"; + protected final KeycloakSession session; + protected final RealmModel realm; + protected final AuthorizationProvider authz; + protected final MgmtPermissions root; + + public GroupPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { + this.session = session; + this.realm = realm; + this.authz = authz; + this.root = root; + } + + private static String getGroupResourceName(GroupModel group) { + return "group.resource." + getGroupSuffix(group); + } + + + public static String getManagePermissionGroup(GroupModel group) { + return "manage.permission.group." + getGroupSuffix(group); + } + + public static String getManageMembersPermissionGroup(GroupModel group) { + return "manage.members.permission.group." + getGroupSuffix(group); + } + + public static String getGroupSuffix(GroupModel group) { + return ModelToRepresentation.buildGroupPath(group).replace('/', '.'); + } + + public static String getViewPermissionGroup(GroupModel group) { + return "view.permission.group." + getGroupSuffix(group); + } + + public static String getViewMembersPermissionGroup(GroupModel group) { + return "view.members.permission.group." + getGroupSuffix(group); + } + + private void initialize(GroupModel group) { + root.initializeRealmResourceServer(); + root.initializeRealmDefaultScopes(); + ResourceServer server = root.realmResourceServer(); + Scope manageScope = root.realmManageScope(); + Scope viewScope = root.realmViewScope(); + Scope manageMembersScope = root.initializeRealmScope(MANAGE_MEMBERS_SCOPE); + Scope viewMembersScope = root.initializeRealmScope(VIEW_MEMBERS_SCOPE); + + String groupResourceName = getGroupResourceName(group); + Resource groupResource = authz.getStoreFactory().getResourceStore().findByName(groupResourceName, server.getId()); + if (groupResource == null) { + groupResource = authz.getStoreFactory().getResourceStore().create(groupResourceName, server, server.getClientId()); + Set scopeset = new HashSet<>(); + scopeset.add(manageScope); + scopeset.add(viewScope); + groupResource.updateScopes(scopeset); + } + String managePermissionName = getManagePermissionGroup(group); + Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId()); + if (managePermission == null) { + Policy manageUsersPolicy = root.roles().manageUsersPolicy(server); + Helper.addScopePermission(authz, server, managePermissionName, groupResource, manageScope, manageUsersPolicy); + } + String viewPermissionName = getManagePermissionGroup(group); + Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId()); + if (viewPermission == null) { + Policy viewUsersPolicy = root.roles().viewUsersPolicy(server); + Helper.addScopePermission(authz, server, viewPermissionName, groupResource, viewScope, viewUsersPolicy); + } + String manageMembersPermissionName = getManageMembersPermissionGroup(group); + Policy manageMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId()); + if (manageMembersPermission == null) { + Policy manageUsersPolicy = root.roles().manageUsersPolicy(server); + Helper.addScopePermission(authz, server, manageMembersPermissionName, groupResource, manageMembersScope, manageUsersPolicy); + } + String viewMembersPermissionName = getViewMembersPermissionGroup(group); + Policy viewMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId()); + if (viewMembersPermission == null) { + Policy viewUsersPolicy = root.roles().viewUsersPolicy(server); + Helper.addScopePermission(authz, server, viewMembersPermissionName, groupResource, viewMembersScope, viewUsersPolicy); + } + } + + @Override + public boolean canList() { + return root.hasOneAdminRole(AdminRoles.VIEW_USERS, AdminRoles.MANAGE_USERS); + } + + @Override + public void requireList() { + if (!canList()) { + throw new ForbiddenException(); + } + } + + + + @Override + public boolean isPermissionsEnabled(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return false; + + return authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()) != null; + } + + private Resource groupResource(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return null; + String groupResourceName = getGroupResourceName(group); + return authz.getStoreFactory().getResourceStore().findByName(groupResourceName, server.getId()); + } + + @Override + public void setPermissionsEnabled(GroupModel group, boolean enable) { + if (enable) { + initialize(group); + } else { + deletePermissions(group); + } + } + + private void deletePermissions(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return; + Policy managePermission = managePermissionGroup(group); + if (managePermission != null) { + authz.getStoreFactory().getPolicyStore().delete(managePermission.getId()); + } + Policy viewPermission = viewPermissionGroup(group); + if (viewPermission != null) { + authz.getStoreFactory().getPolicyStore().delete(viewPermission.getId()); + } + Policy manageMembersPermission = manageMembersPermission(group); + if (manageMembersPermission != null) { + authz.getStoreFactory().getPolicyStore().delete(manageMembersPermission.getId()); + } + Policy viewMembersPermission = viewMembersPermission(group); + if (manageMembersPermission == null) { + authz.getStoreFactory().getPolicyStore().delete(viewMembersPermission.getId()); + } + Resource resource = groupResource(group); + if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); + } + + @Override + public Policy viewMembersPermission(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return null; + String viewMembersPermissionName = getViewMembersPermissionGroup(group); + return authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId()); + } + + @Override + public Policy manageMembersPermission(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return null; + String manageMembersPermissionName = getManageMembersPermissionGroup(group); + return authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId()); + } + + @Override + public Policy viewPermissionGroup(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return null; + String viewPermissionName = getViewPermissionGroup(group); + return authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId()); + } + + @Override + public Policy managePermissionGroup(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return null; + String managePermissionName = getManagePermissionGroup(group); + return authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId()); + } + + + @Override + public boolean canManage(GroupModel group) { + if (!root.isAdminSameRealm()) { + return canManage(); + } + + ResourceServer server = root.realmResourceServer(); + if (server == null) return canManage(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); + if (resource == null) return canManage(); + + Policy policy = managePermissionGroup(group); + if (policy == null) { + return canManage(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return canManage(); + } + + Scope scope = root.realmManageScope(); + return root.evaluatePermission(resource, scope, server); + } + + @Override + public void requireManage(GroupModel group) { + if (!canManage(group)) { + throw new ForbiddenException(); + } + } + @Override + public boolean canView(GroupModel group) { + if (!root.isAdminSameRealm()) { + return canView(); + } + + ResourceServer server = root.realmResourceServer(); + if (server == null) return canView(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); + if (resource == null) return canView(); + + Policy policy = viewPermissionGroup(group); + if (policy == null) { + return canView(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then abort + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return canView(); + } + + Scope scope = root.realmViewScope(); + return root.evaluatePermission(resource, scope, server); + } + + @Override + public void requireView(GroupModel group) { + if (!canView(group)) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canManage() { + return root.users().canManageDefault(); + } + + @Override + public void requireManage() { + if (!canManage()) { + throw new ForbiddenException(); + } + } + @Override + public boolean canView() { + return root.users().canViewDefault(); + } + + @Override + public void requireView() { + if (!canView()) { + throw new ForbiddenException(); + } + } + + + + @Override + public boolean canViewMembers(GroupModel group) { + if (!root.isAdminSameRealm()) { + return root.users().canView(); + } + + ResourceServer server = root.realmResourceServer(); + if (server == null) return root.users().canView(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); + if (resource == null) return root.users().canView(); + + Policy policy = viewMembersPermission(group); + if (policy == null) { + return root.users().canView(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return root.users().canView(); + } + + Scope scope = authz.getStoreFactory().getScopeStore().findByName(VIEW_MEMBERS_SCOPE, server.getId()); + + return root.evaluatePermission(resource, scope, server); + } + + @Override + public void requireViewMembers(GroupModel group) { + if (!canViewMembers(group)) { + throw new ForbiddenException(); + } + } + + + @Override + public boolean canManageMembers(GroupModel group) { + if (!root.isAdminSameRealm()) { + return root.users().canManage(); + } + + ResourceServer server = root.realmResourceServer(); + if (server == null) return root.users().canManage(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); + if (resource == null) return root.users().canManage(); + + Policy policy = manageMembersPermission(group); + if (policy == null) { + return root.users().canManage(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return root.users().canManage(); + } + + Scope scope = authz.getStoreFactory().getScopeStore().findByName(MANAGE_MEMBERS_SCOPE, server.getId()); + return root.evaluatePermission(resource, scope, server); + } + + @Override + public void requireManageMembers(GroupModel group) { + if (!canManageMembers(group)) { + throw new ForbiddenException(); + } + } + + + +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/permissions/Helper.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/Helper.java similarity index 98% rename from services/src/main/java/org/keycloak/authorization/admin/permissions/Helper.java rename to services/src/main/java/org/keycloak/services/resources/admin/permissions/Helper.java index a45ea7f79a..2e7942d640 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/permissions/Helper.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/Helper.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.authorization.admin.permissions; +package org.keycloak.services.resources.admin.permissions; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; @@ -35,7 +35,7 @@ import java.util.Map; * @author Bill Burke * @version $Revision: 1 $ */ -public class Helper { +class Helper { public static Policy addScopePermission(AuthorizationProvider authz, ResourceServer resourceServer, String name, Resource resource, Scope scope, Policy policy) { ScopePermissionRepresentation representation = new ScopePermissionRepresentation(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java new file mode 100644 index 0000000000..943428364d --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java @@ -0,0 +1,274 @@ +/* + * 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.services.resources.admin.permissions; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.AuthorizationProviderFactory; +import org.keycloak.authorization.Decision; +import org.keycloak.authorization.common.DefaultEvaluationContext; +import org.keycloak.authorization.common.KeycloakIdentity; +import org.keycloak.authorization.common.UserModelIdentity; +import org.keycloak.authorization.identity.Identity; +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.store.ResourceServerStore; +import org.keycloak.authorization.util.Permissions; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.ForbiddenException; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.resources.admin.AdminAuth; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManagement { + private static final Logger logger = Logger.getLogger(MgmtPermissions.class); + + protected RealmModel realm; + protected KeycloakSession session; + protected AuthorizationProvider authz; + protected AdminAuth auth; + protected Identity identity; + protected UserModel admin; + protected RealmModel adminsRealm; + protected ResourceServer realmResourceServer; + protected UserPermissions users; + protected GroupPermissions groups; + protected RealmPermissions realmPermissions; + protected ClientPermissions clientPermissions; + + + MgmtPermissions(KeycloakSession session, RealmModel realm) { + this.session = session; + this.realm = realm; + KeycloakSessionFactory keycloakSessionFactory = session.getKeycloakSessionFactory(); + AuthorizationProviderFactory factory = (AuthorizationProviderFactory) keycloakSessionFactory.getProviderFactory(AuthorizationProvider.class); + this.authz = factory.create(session, realm); + } + + MgmtPermissions(KeycloakSession session, RealmModel realm, AdminAuth auth) { + this(session, realm); + this.auth = auth; + this.admin = auth.getUser(); + this.adminsRealm = auth.getRealm(); + if (!auth.getRealm().equals(realm) + && !auth.getRealm().equals(new RealmManager(session).getKeycloakAdminstrationRealm())) { + throw new ForbiddenException(); + } + if (auth.getClient().getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID)) { + this.identity = new UserModelIdentity(auth.getRealm(), auth.getUser()); + + } else { + this.identity = new KeycloakIdentity(auth.getToken(), session); + } + } + MgmtPermissions(KeycloakSession session, RealmModel realm, RealmModel adminsRealm, UserModel admin) { + this(session, realm); + this.admin = admin; + this.adminsRealm = adminsRealm; + this.identity = new UserModelIdentity(realm, admin); + } + + public ClientModel getRealmManagementClient() { + ClientModel client = null; + if (realm.getName().equals(Config.getAdminRealm())) { + client = realm.getClientByClientId(Config.getAdminRealm() + "-realm"); + } else { + client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + + } + return client; + } + + + + public boolean hasAnyAdminRole() { + return hasOneAdminRole(AdminRoles.ALL_REALM_ROLES); + } + + public boolean hasOneAdminRole(String... adminRoles) { + String clientId; + RealmManager realmManager = new RealmManager(session); + if (adminsRealm.equals(realmManager.getKeycloakAdminstrationRealm())) { + clientId = realm.getMasterAdminClient().getClientId(); + } else { + clientId = realm.getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm())).getClientId(); + } + for (String adminRole : adminRoles) { + if (identity.hasClientRole(clientId, adminRole)) return true; + } + return false; + } + + + + public boolean isAdminSameRealm() { + return auth == null || realm.getId().equals(auth.getRealm().getId()); + } + + @Override + public AdminAuth adminAuth() { + return auth; + } + + public Identity identity() { + return identity; + } + + public UserModel admin() { + return admin; + } + + + @Override + public RolePermissions roles() { + return new RolePermissions(session, realm, authz, this); + } + + @Override + public UserPermissions users() { + if (users != null) return users; + users = new UserPermissions(session, realm, authz, this); + return users; + } + + @Override + public RealmPermissions realm() { + if (realmPermissions != null) return realmPermissions; + realmPermissions = new RealmPermissions(session, realm, authz, this); + return realmPermissions; + } + + @Override + public ClientPermissions clients() { + if (clientPermissions != null) return clientPermissions; + clientPermissions = new ClientPermissions(session, realm, authz, this); + return clientPermissions; + } + + @Override + public GroupPermissions groups() { + if (groups != null) return groups; + groups = new GroupPermissions(session, realm, authz, this); + return groups; + } + + public ResourceServer findOrCreateResourceServer(ClientModel client) { + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) { + server = authz.getStoreFactory().getResourceServerStore().create(client.getId()); + } + return server; + } + + @Override + public ResourceServer realmResourceServer() { + if (realmResourceServer != null) return realmResourceServer; + ResourceServerStore resourceServerStore = authz.getStoreFactory().getResourceServerStore(); + ClientModel client = getRealmManagementClient(); + realmResourceServer = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + return realmResourceServer; + + } + + public ResourceServer initializeRealmResourceServer() { + if (realmResourceServer != null) return realmResourceServer; + ClientModel client = getRealmManagementClient(); + return findOrCreateResourceServer(client); + } + + protected Scope manageScope; + protected Scope viewScope; + + public void initializeRealmDefaultScopes() { + ResourceServer server = initializeRealmResourceServer(); + manageScope = initializeRealmScope(MgmtPermissions.MANAGE_SCOPE); + viewScope = initializeRealmScope(MgmtPermissions.VIEW_SCOPE); + } + + public Scope initializeRealmScope(String name) { + ResourceServer server = initializeRealmResourceServer(); + Scope scope = authz.getStoreFactory().getScopeStore().findByName(name, server.getId()); + if (scope == null) { + scope = authz.getStoreFactory().getScopeStore().create(name, server); + } + return scope; + } + + + + public Scope realmManageScope() { + if (manageScope != null) return manageScope; + manageScope = realmScope(MgmtPermissions.MANAGE_SCOPE); + return manageScope; + } + + + public Scope realmViewScope() { + if (viewScope != null) return viewScope; + viewScope = realmScope(MgmtPermissions.VIEW_SCOPE); + return viewScope; + } + + public Scope realmScope(String scope) { + ResourceServer server = realmResourceServer(); + if (server == null) return null; + return authz.getStoreFactory().getScopeStore().findByName(scope, server.getId()); + } + + public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer) { + Identity identity = identity(); + if (identity == null) { + throw new RuntimeException("Identity of admin is not set for permission query"); + } + RealmModel oldRealm = session.getContext().getRealm(); + try { + session.getContext().setRealm(realm); + EvaluationContext context = new DefaultEvaluationContext(identity, session); + DecisionResult decisionCollector = new DecisionResult(); + List permissions = Permissions.permission(resourceServer, resource, scope); + PermissionEvaluator from = authz.evaluators().from(permissions, context); + from.evaluate(decisionCollector); + if (!decisionCollector.completed()) { + logger.error("Failed to run permission check", decisionCollector.getError()); + return false; + } + return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT; + } finally { + session.getContext().setRealm(oldRealm); + } + } + + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmAuth.java similarity index 86% rename from services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java rename to services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmAuth.java index 176c480382..61f304111b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmAuth.java @@ -15,24 +15,21 @@ * limitations under the License. */ -package org.keycloak.services.resources.admin; +package org.keycloak.services.resources.admin.permissions; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; import org.keycloak.models.ImpersonationConstants; import org.keycloak.services.ForbiddenException; +import org.keycloak.services.resources.admin.AdminAuth; /** * @author Stian Thorgersen */ -public class RealmAuth { +class RealmAuth { - private Resource resource; - - public enum Resource { - CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER, IMPERSONATION, AUTHORIZATION - } + private AdminAuth.Resource resource; private AdminAuth auth; private ClientModel realmAdminApp; @@ -42,7 +39,7 @@ public class RealmAuth { this.realmAdminApp = realmAdminApp; } - public RealmAuth init(Resource resource) { + public RealmAuth init(AdminAuth.Resource resource) { this.resource = resource; return this; } @@ -52,11 +49,15 @@ public class RealmAuth { } public void requireAny() { - if (!auth.hasOneOfAppRole(realmAdminApp, AdminRoles.ALL_REALM_ROLES)) { + if (!hasAny()) { throw new ForbiddenException(); } } + public boolean hasAny() { + return auth.hasOneOfAppRole(realmAdminApp, AdminRoles.ALL_REALM_ROLES); + } + public boolean hasView() { return auth.hasOneOfAppRole(realmAdminApp, getViewRole(resource), getManageRole(resource)); } @@ -77,7 +78,7 @@ public class RealmAuth { } } - private String getViewRole(Resource resource) { + private String getViewRole(AdminAuth.Resource resource) { switch (resource) { case CLIENT: return AdminRoles.VIEW_CLIENTS; @@ -96,7 +97,7 @@ public class RealmAuth { } } - private String getManageRole(Resource resource) { + private String getManageRole(AdminAuth.Resource resource) { switch (resource) { case CLIENT: return AdminRoles.MANAGE_CLIENTS; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissionEvaluator.java new file mode 100644 index 0000000000..cf350a1e31 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissionEvaluator.java @@ -0,0 +1,59 @@ +/* + * 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.services.resources.admin.permissions; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface RealmPermissionEvaluator { + boolean canListRealm(); + + void requireViewRealmNameList(); + + boolean canManageRealm(); + + void requireManageRealm(); + + boolean canViewRealm(); + + void requireViewRealm(); + + boolean canManageIdentityProviders(); + + boolean canViewIdentityProviders(); + + void requireViewIdentityProviders(); + + void requireManageIdentityProviders(); + + boolean canManageAuthorization(); + + boolean canViewAuthorization(); + + void requireManageAuthorization(); + + void requireViewAuthorization(); + + boolean canManageEvents(); + + void requireManageEvents(); + + boolean canViewEvents(); + + void requireViewEvents(); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java new file mode 100644 index 0000000000..84b6ceb6eb --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java @@ -0,0 +1,188 @@ +/* + * 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.services.resources.admin.permissions; + +import org.jboss.logging.Logger; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.ForbiddenException; + +/** + * Manages default policies for all users. + * + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +class RealmPermissions implements RealmPermissionEvaluator { + private static final Logger logger = Logger.getLogger(RealmPermissions.class); + protected final KeycloakSession session; + protected final RealmModel realm; + protected final AuthorizationProvider authz; + protected final MgmtPermissions root; + + public RealmPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { + this.session = session; + this.realm = realm; + this.authz = authz; + this.root = root; + } + + + public boolean canManageRealmDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_REALM); + + } + public boolean canViewRealmDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_REALM, AdminRoles.VIEW_REALM); + } + + public boolean canManageIdentityProvidersDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_IDENTITY_PROVIDERS); + + } + public boolean canViewIdentityProvidersDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_IDENTITY_PROVIDERS, AdminRoles.VIEW_IDENTITY_PROVIDERS); + } + + public boolean canManageAuthorizationDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_AUTHORIZATION); + + } + public boolean canViewAuthorizationDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_AUTHORIZATION, AdminRoles.VIEW_AUTHORIZATION); + } + public boolean canManageEventsDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_EVENTS); + } + public boolean canViewEventsDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_EVENTS, AdminRoles.VIEW_EVENTS); + } + + @Override + public boolean canListRealm() { + return root.hasAnyAdminRole(); + } + + @Override + public void requireViewRealmNameList() { + if (!canListRealm()) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canManageRealm() { + return canManageRealmDefault(); + } + + @Override + public void requireManageRealm() { + if (!canManageRealm()) { + throw new ForbiddenException(); + } + } + @Override + public boolean canViewRealm() { + return canViewRealmDefault(); + } + + @Override + public void requireViewRealm() { + if (!canViewRealm()) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canManageIdentityProviders() { + return canManageIdentityProvidersDefault(); + } + + @Override + public boolean canViewIdentityProviders() { + return canViewIdentityProvidersDefault(); + } + + @Override + public void requireViewIdentityProviders() { + if (!canViewIdentityProviders()) { + throw new ForbiddenException(); + } + } + + + @Override + public void requireManageIdentityProviders() { + if (!canManageIdentityProviders()) { + throw new ForbiddenException(); + } + } + + + @Override + public boolean canManageAuthorization() { + return canManageAuthorizationDefault(); + } + + @Override + public boolean canViewAuthorization() { + return canViewAuthorizationDefault(); + } + + @Override + public void requireManageAuthorization() { + if (!canManageEvents()) { + throw new ForbiddenException(); + } + } + @Override + public void requireViewAuthorization() { + if (!canManageEvents()) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canManageEvents() { + return canManageEventsDefault(); + } + + @Override + public void requireManageEvents() { + if (!canManageEvents()) { + throw new ForbiddenException(); + } + } + @Override + public boolean canViewEvents() { + return canViewEventsDefault(); + } + + @Override + public void requireViewEvents() { + if (!canViewEvents()) { + throw new ForbiddenException(); + } + } + + + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java new file mode 100644 index 0000000000..3a87cb3d47 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java @@ -0,0 +1,47 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.models.RoleContainerModel; +import org.keycloak.models.RoleModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface RolePermissionEvaluator { + boolean canList(RoleContainerModel container); + + void requireList(RoleContainerModel container); + + boolean canMapRole(RoleModel role); + void requireMapRole(RoleModel role); + + boolean canManage(RoleModel role); + + void requireManage(RoleModel role); + + boolean canView(RoleModel role); + + void requireView(RoleModel role); + + boolean canMapClientScope(RoleModel role); + void requireMapClientScope(RoleModel role); + + boolean canMapComposite(RoleModel role); + void requireMapComposite(RoleModel role); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionManagement.java new file mode 100644 index 0000000000..977e109d3e --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionManagement.java @@ -0,0 +1,56 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.models.RoleContainerModel; +import org.keycloak.models.RoleModel; + +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface RolePermissionManagement { + public static final String MAP_ROLE_SCOPE = "map-role"; + public static final String MAP_ROLE_CLIENT_SCOPE_SCOPE = "map-role-client-scope"; + public static final String MAP_ROLE_COMPOSITE_SCOPE = "map-role-composite"; + + boolean isPermissionsEnabled(RoleModel role); + void setPermissionsEnabled(RoleModel role, boolean enable); + + Map getPermissions(RoleModel role); + + Policy mapRolePermission(RoleModel role); + + Policy mapCompositePermission(RoleModel role); + + Policy mapClientScopePermission(RoleModel role); + + Resource resource(RoleModel role); + + ResourceServer resourceServer(RoleModel role); + + Policy manageUsersPolicy(ResourceServer server); + + Policy viewUsersPolicy(ResourceServer server); + + Policy rolePolicy(ResourceServer server, RoleModel role); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java new file mode 100644 index 0000000000..d2531ec02d --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java @@ -0,0 +1,378 @@ +/* + * 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.services.resources.admin.permissions; + +import org.jboss.logging.Logger; +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.authorization.store.ResourceStore; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleContainerModel; +import org.keycloak.models.RoleModel; +import org.keycloak.services.ForbiddenException; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +class RolePermissions implements RolePermissionEvaluator, RolePermissionManagement { + private static final Logger logger = Logger.getLogger(RolePermissions.class); + protected final KeycloakSession session; + protected final RealmModel realm; + protected final AuthorizationProvider authz; + protected final MgmtPermissions root; + + public RolePermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { + this.session = session; + this.realm = realm; + this.authz = authz; + this.root = root; + } + + @Override + public boolean isPermissionsEnabled(RoleModel role) { + return mapRolePermission(role) != null; + } + + @Override + public void setPermissionsEnabled(RoleModel role, boolean enable) { + if (enable) { + ResourceServer server = getResourceServer(role); + if (authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()) != null) { + return; + } + createResource(role); + } else { + ResourceServer server = resourceServer(role); + if (server == null) return; + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); + if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), server.getId()); + if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId()); + } + } + + @Override + public Map getPermissions(RoleModel role) { + Map scopes = new HashMap<>(); + scopes.put(RolePermissionManagement.MAP_ROLE_SCOPE, mapRolePermission(role).getId()); + scopes.put(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, mapClientScopePermission(role).getId()); + scopes.put(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, mapCompositePermission(role).getId()); + return scopes; + } + + @Override + public Policy mapRolePermission(RoleModel role) { + ResourceServer server = resourceServer(role); + if (server == null) return null; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); + if (resource == null) return null; + + return authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), server.getId()); + } + + @Override + public Policy mapCompositePermission(RoleModel role) { + ResourceServer server = resourceServer(role); + if (server == null) return null; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); + if (resource == null) return null; + + return authz.getStoreFactory().getPolicyStore().findByName(getMapCompositePermissionName(role), server.getId()); + } + + @Override + public Policy mapClientScopePermission(RoleModel role) { + ResourceServer server = resourceServer(role); + if (server == null) return null; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()); + if (resource == null) return null; + + return authz.getStoreFactory().getPolicyStore().findByName(getMapClientScopePermissionName(role), server.getId()); + } + + @Override + public Resource resource(RoleModel role) { + ResourceStore resourceStore = authz.getStoreFactory().getResourceStore(); + ResourceServer server = resourceServer(role); + if (server == null) return null; + return resourceStore.findByName(getRoleResourceName(role), server.getId()); + } + + @Override + public ResourceServer resourceServer(RoleModel role) { + ClientModel client = getRoleClient(role); + return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + } + + /** + * Is admin allowed to map this role? + * + * @param role + * @return + */ + @Override + public boolean canMapRole(RoleModel role) { + if (!root.isAdminSameRealm()) { + return root.users().canManage(); + } + if (!isPermissionsEnabled(role)){ + return root.users().canManage(); + } + + ResourceServer resourceServer = getResourceServer(role); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), resourceServer.getId()); + if (policy.getAssociatedPolicies().isEmpty()) { + return root.users().canManage(); // if no policies applied, just do default + } + + Resource roleResource = resource(role); + Scope mapRoleScope = getMapRoleScope(resourceServer); + return root.evaluatePermission(roleResource, mapRoleScope, resourceServer); + } + + @Override + public void requireMapRole(RoleModel role) { + if (!canMapRole(role)) { + throw new ForbiddenException(); + } + + } + + @Override + public boolean canList(RoleContainerModel container) { + return root.hasAnyAdminRole(); + } + + @Override + public void requireList(RoleContainerModel container) { + if (!canList(container)) { + throw new ForbiddenException(); + } + + } + + @Override + public boolean canMapComposite(RoleModel role) { + if (!root.isAdminSameRealm()) { + return canManage(role); + } + if (!isPermissionsEnabled(role)){ + return canManage(role); + } + + ResourceServer resourceServer = getResourceServer(role); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapCompositePermissionName(role), resourceServer.getId()); + if (policy.getAssociatedPolicies().isEmpty()) { + return canManage(role); + } + + Resource roleResource = resource(role); + Scope scope = getMapCompositeScope(resourceServer); + return root.evaluatePermission(roleResource, scope, resourceServer); + } + + @Override + public void requireMapComposite(RoleModel role) { + if (!canMapComposite(role)) { + throw new ForbiddenException(); + } + + } + + + @Override + public boolean canMapClientScope(RoleModel role) { + if (!root.isAdminSameRealm()) { + return root.clients().canManage(); + } + if (!isPermissionsEnabled(role)){ + return root.clients().canManage(); + } + + ResourceServer resourceServer = getResourceServer(role); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapClientScopePermissionName(role), resourceServer.getId()); + if (policy.getAssociatedPolicies().isEmpty()) { + return root.clients().canManage(); + } + + Resource roleResource = resource(role); + Scope scope = getMapClientScope(resourceServer); + return root.evaluatePermission(roleResource, scope, resourceServer); + } + + @Override + public void requireMapClientScope(RoleModel role) { + if (!canMapClientScope(role)) { + throw new ForbiddenException(); + } + } + + + @Override + public boolean canManage(RoleModel role) { + if (role.getContainer() instanceof RealmModel) { + return root.realm().canManageRealm(); + } else if (role.getContainer() instanceof ClientModel) { + ClientModel client = (ClientModel)role.getContainer(); + return root.clients().canManage(client); + } + return false; + } + + @Override + public void requireManage(RoleModel role) { + if (!canManage(role)) { + throw new ForbiddenException(); + } + + } + + @Override + public boolean canView(RoleModel role) { + if (role.getContainer() instanceof RealmModel) { + return root.realm().canViewRealm(); + } else if (role.getContainer() instanceof ClientModel) { + ClientModel client = (ClientModel)role.getContainer(); + return root.clients().canView(client); + } + return false; + } + + @Override + public void requireView(RoleModel role) { + if (!canView(role)) { + throw new ForbiddenException(); + } + + } + + private ClientModel getRoleClient(RoleModel role) { + ClientModel client = null; + if (role.getContainer() instanceof ClientModel) { + client = (ClientModel)role.getContainer(); + } else { + client = root.getRealmManagementClient(); + } + return client; + } + + @Override + public Policy manageUsersPolicy(ResourceServer server) { + RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_USERS); + return rolePolicy(server, role); + } + + @Override + public Policy viewUsersPolicy(ResourceServer server) { + RoleModel role = root.getRealmManagementClient().getRole(AdminRoles.VIEW_USERS); + return rolePolicy(server, role); + } + + @Override + public Policy rolePolicy(ResourceServer server, RoleModel role) { + String policyName = Helper.getRolePolicyName(role); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(policyName, server.getId()); + if (policy != null) return policy; + return Helper.createRolePolicy(authz, server, role, policyName); + } + + private Scope getMapRoleScope(ResourceServer server) { + Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_SCOPE, server.getId()); + if (scope == null) { + scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_SCOPE, server); + } + return scope; + } + + private Scope getMapClientScope(ResourceServer server) { + Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_CLIENT_SCOPE_SCOPE, server.getId()); + if (scope == null) { + scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_CLIENT_SCOPE_SCOPE, server); + } + return scope; + } + + private Scope getMapCompositeScope(ResourceServer server) { + Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_COMPOSITE_SCOPE, server.getId()); + if (scope == null) { + scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_COMPOSITE_SCOPE, server); + } + return scope; + } + + + private Resource createResource(RoleModel role) { + ResourceServer server = getResourceServer(role); + Resource resource = authz.getStoreFactory().getResourceStore().create(getRoleResourceName(role), server, server.getClientId()); + resource.setType("Role"); + Scope mapRoleScope = getMapRoleScope(server); + Policy policy = manageUsersPolicy(server); + Helper.addScopePermission(authz, server, getMapRolePermissionName(role), resource, mapRoleScope, policy); + + Scope mapClientScope = getMapClientScope(server); + RoleModel mngClients = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_CLIENTS); + Policy mngClientsPolicy = rolePolicy(server, mngClients); + Helper.addScopePermission(authz, server, getMapClientScopePermissionName(role), resource, mapClientScope, mngClientsPolicy); + + Scope mapCompositeScope = getMapCompositeScope(server); + if (role.getContainer() instanceof RealmModel) { + RoleModel mngRealm = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_REALM); + policy = rolePolicy(server, mngRealm); + } else { + policy = mngClientsPolicy; + + } + Helper.addScopePermission(authz, server, getMapCompositePermissionName(role), resource, mapCompositeScope, policy); + return resource; + } + + private String getMapRolePermissionName(RoleModel role) { + return MAP_ROLE_SCOPE + ".permission." + role.getName(); + } + + private String getMapClientScopePermissionName(RoleModel role) { + return MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission." + role.getName(); + } + + private String getMapCompositePermissionName(RoleModel role) { + return MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission." + role.getName(); + } + + private ResourceServer getResourceServer(RoleModel role) { + ClientModel client = getRoleClient(role); + return root.findOrCreateResourceServer(client); + } + + private static String getRoleResourceName(RoleModel role) { + return "role.resource." + role.getName(); + } + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java new file mode 100644 index 0000000000..488825b559 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java @@ -0,0 +1,46 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.models.UserModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserPermissionEvaluator { + boolean canManage(); + + void requireManage(); + + boolean canManage(UserModel user); + void requireManage(UserModel user); + + boolean canQuery(); + + void requireQuery(); + + boolean canView(); + boolean canView(UserModel user); + void requireView(UserModel user); + + void requireView(); + + boolean canImpersonate(UserModel user); + + void requireImpersonate(UserModel user); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java new file mode 100644 index 0000000000..b57b710d0d --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java @@ -0,0 +1,41 @@ +/* + * 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.services.resources.admin.permissions; + +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; +import org.keycloak.models.RoleModel; + +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserPermissionManagement { + boolean isPermissionsEnabled(); + + void setPermissionsEnabled(boolean enable); + + Map getPermissions(); + + Resource resource(); + + Policy managePermission(); + + Policy viewPermission(); +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java new file mode 100644 index 0000000000..a0a7281e27 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -0,0 +1,382 @@ +/* + * 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.services.resources.admin.permissions; + +import org.jboss.logging.Logger; +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.GroupModel; +import org.keycloak.models.ImpersonationConstants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.ForbiddenException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Manages default policies for all users. + * + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +class UserPermissions implements UserPermissionEvaluator, UserPermissionManagement { + private static final Logger logger = Logger.getLogger(UserPermissions.class); + public static final String MANAGE_PERMISSION_USERS = "manage.permission.users"; + public static final String VIEW_PERMISSION_USERS = "view.permission.users"; + public static final String USERS_RESOURCE = "Users"; + protected final KeycloakSession session; + protected final RealmModel realm; + protected final AuthorizationProvider authz; + protected final MgmtPermissions root; + + public UserPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { + this.session = session; + this.realm = realm; + this.authz = authz; + this.root = root; + } + + + private void initialize() { + root.initializeRealmResourceServer(); + root.initializeRealmDefaultScopes(); + ResourceServer server = root.realmResourceServer(); + Scope manageScope = root.realmManageScope(); + Scope viewScope = root.realmViewScope(); + + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (usersResource == null) { + usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getClientId()); + Set scopeset = new HashSet<>(); + scopeset.add(manageScope); + scopeset.add(viewScope); + usersResource.updateScopes(scopeset); + } + Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); + if (managePermission == null) { + Policy manageUsersPolicy = root.roles().manageUsersPolicy(server); + Helper.addScopePermission(authz, server, MANAGE_PERMISSION_USERS, usersResource, manageScope, manageUsersPolicy); + } + Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId()); + if (viewPermission == null) { + Policy viewUsersPolicy = root.roles().viewUsersPolicy(server); + Helper.addScopePermission(authz, server, VIEW_PERMISSION_USERS, usersResource, viewScope, viewUsersPolicy); + } + } + + @Override + public Map getPermissions() { + Map scopes = new HashMap<>(); + scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId()); + scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission().getId()); + return scopes; + } + + @Override + public boolean isPermissionsEnabled() { + ResourceServer server = root.realmResourceServer(); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); + + return policy != null; + } + + @Override + public void setPermissionsEnabled(boolean enable) { + ClientModel client = root.getRealmManagementClient(); + if (enable) { + initialize(); + } else { + ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + if (server == null) return; + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); + if (policy == null) { + authz.getStoreFactory().getPolicyStore().delete(policy.getId()); + + } + Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (usersResource == null) { + authz.getStoreFactory().getResourceStore().delete(usersResource.getId()); + } + } + } + + public boolean canManageDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_USERS); + } + + @Override + public Resource resource() { + ResourceServer server = root.realmResourceServer(); + if (server == null) return null; + + return authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + } + + @Override + public Policy managePermission() { + ResourceServer server = root.realmResourceServer(); + return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); + } + + @Override + public Policy viewPermission() { + ResourceServer server = root.realmResourceServer(); + return authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId()); + } + + + + /** + * Is admin allowed to manage all users? In Authz terms, does the admin have the "manage" scope for the Users Authz resource? + * + * This method will follow the old default behavior (does the admin have the manage-users role) if any of these conditions + * are met.: + * - The admin is from the master realm managing a different realm + * - If the Authz objects are not set up correctly for the Users resource in Authz + * - The "manage" permission for the Users resource has an empty associatedPolicy list. + * + * Otherwise, it will use the Authz policy engine to resolve this answer. + * + * @return + */ + @Override + public boolean canManage() { + if (!root.isAdminSameRealm()) { + return canManageDefault(); + } + + ResourceServer server = root.realmResourceServer(); + if (server == null) return canManageDefault(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (resource == null) return canManageDefault(); + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); + if (policy == null) { + return canManageDefault(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return canManageDefault(); + } + + Scope scope = root.realmManageScope(); + return root.evaluatePermission(resource, scope, server); + + } + + @Override + public void requireManage() { + if (!canManage()) { + throw new ForbiddenException(); + } + } + + + /** + * Does current admin have manage permissions for this particular user? + * + * @param user + * @return + */ + @Override + public boolean canManage(UserModel user) { + return canManage() || canManageByGroup(user); + } + + @Override + public void requireManage(UserModel user) { + if (!canManage(user)) { + throw new ForbiddenException(); + } + } + + private interface EvaluateGroup { + boolean evaluate(GroupModel group); + } + + private boolean evaluateGroups(UserModel user, EvaluateGroup eval) { + for (GroupModel group : user.getGroups()) { + if (eval.evaluate(group)) return true; + } + return false; + } + + private boolean evaluateHierarchy(UserModel user, EvaluateGroup eval) { + Set visited = new HashSet<>(); + for (GroupModel group : user.getGroups()) { + if (evaluateHierarchy(eval, group, visited)) return true; + } + return false; + } + + private boolean evaluateHierarchy(EvaluateGroup eval, GroupModel group, Set visited) { + if (visited.contains(group)) return false; + if (eval.evaluate(group)) { + return true; + } + visited.add(group); + if (group.getParent() == null) return false; + return evaluateHierarchy(eval, group.getParent(), visited); + } + + private boolean canManageByGroup(UserModel user) { + /* no inheritance + return evaluateGroups(user, + (group) -> root.groups().canViewMembers(group) + ); + */ + + /* inheritance + */ + return evaluateHierarchy(user, (group) -> root.groups().canManageMembers(group)); + + } + private boolean canViewByGroup(UserModel user) { + /* no inheritance + return evaluateGroups(user, + (group) -> root.groups().canViewMembers(group) + ); + */ + + /* inheritance + */ + return evaluateHierarchy(user, (group) -> root.groups().canViewMembers(group)); + } + + public boolean canViewDefault() { + return root.hasOneAdminRole(AdminRoles.MANAGE_USERS, AdminRoles.VIEW_USERS); + } + + @Override + public boolean canQuery() { + return canViewDefault(); + } + + @Override + public void requireQuery() { + if (!canQuery()) { + throw new ForbiddenException(); + } + } + + + + /** + * Is admin allowed to view all users? In Authz terms, does the admin have the "view" scope for the Users Authz resource? + * + * This method will follow the old default behavior (does the admin have the view-users role) if any of these conditions + * are met.: + * - The admin is from the master realm managing a different realm + * - If the Authz objects are not set up correctly for the Users resource in Authz + * - The "view" permission for the Users resource has an empty associatedPolicy list. + * + * Otherwise, it will use the Authz policy engine to resolve this answer. + * + * @return + */ + @Override + public boolean canView() { + if (!root.isAdminSameRealm()) { + return canViewDefault(); + } + + ResourceServer server = root.realmResourceServer(); + if (server == null) return canViewDefault(); + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (resource == null) return canViewDefault(); + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId()); + if (policy == null) { + return canViewDefault(); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return canViewDefault(); + } + + Scope scope = root.realmViewScope(); + return root.evaluatePermission(resource, scope, server); + } + + /** + * Does current admin have view permissions for this particular user? + * + * Evaluates in this order. If any true, return true: + * - canViewUsers + * - canManageUsers + * + * + * @param user + * @return + */ + @Override + public boolean canView(UserModel user) { + return canView() || canManage() || canViewByGroup(user) || canManageByGroup(user); + } + + @Override + public void requireView(UserModel user) { + if (!canView(user)) { + throw new ForbiddenException(); + } + } + + @Override + public void requireView() { + if (!(canView() || canManage())) { + throw new ForbiddenException(); + } + } + + @Override + public boolean canImpersonate(UserModel user) { + return root.hasOneAdminRole(ImpersonationConstants.IMPERSONATION_ROLE); + } + + @Override + public void requireImpersonate(UserModel user) { + if (!canImpersonate(user)) { + throw new ForbiddenException(); + } + } + + + + + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index e001f60f38..b6bdc4c11d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -19,8 +19,9 @@ package org.keycloak.testsuite.admin; import org.junit.Assert; import org.junit.Test; import org.keycloak.admin.client.Keycloak; -import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.authorization.admin.permissions.MgmtPermissions; +import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.AdminRoles; @@ -35,24 +36,14 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; 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.PolicyEvaluationRequest; -import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse; import org.keycloak.testsuite.AbstractKeycloakTest; -import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.util.AdminClientUtil; import javax.ws.rs.ClientErrorException; -import javax.ws.rs.ForbiddenException; -import javax.ws.rs.core.Response; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Set; -import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN; -import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; /** @@ -73,7 +64,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { public static void setupPolices(KeycloakSession session) { RealmModel realm = session.realms().getRealmByName(TEST); - MgmtPermissions permissions = new MgmtPermissions(session, realm); + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); RoleModel realmRole = realm.addRole("realm-role"); RoleModel realmRole2 = realm.addRole("realm-role2"); ClientModel client1 = realm.addClient("role-namespace"); @@ -110,7 +101,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { // setup Users manage policies { permissions.users().setPermissionsEnabled(true); - ResourceServer server = permissions.users().resourceServer(); + ResourceServer server = permissions.realmResourceServer(); Policy managerPolicy = permissions.roles().rolePolicy(server, managerRole); Policy permission = permissions.users().managePermission(); permission.addAssociatedPolicy(managerPolicy); @@ -177,9 +168,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { // test authorized { UserModel user = session.users().getUserByUsername("authorized", realm); - MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); - permissionsForAdmin.setIdentity(user); - Assert.assertTrue(permissionsForAdmin.users().canManage(user)); + AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user); + Assert.assertTrue(permissionsForAdmin.users().canManage()); Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole)); Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2)); Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole)); @@ -187,9 +177,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { // test composite role { UserModel user = session.users().getUserByUsername("authorizedComposite", realm); - MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); - permissionsForAdmin.setIdentity(user); - Assert.assertTrue(permissionsForAdmin.users().canManage(user)); + AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user); + Assert.assertTrue(permissionsForAdmin.users().canManage()); Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole)); Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2)); Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole)); @@ -198,9 +187,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { // test unauthorized { UserModel user = session.users().getUserByUsername("unauthorized", realm); - MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); - permissionsForAdmin.setIdentity(user); - Assert.assertFalse(permissionsForAdmin.users().canManage(user)); + AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user); + Assert.assertFalse(permissionsForAdmin.users().canManage()); Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole)); Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole)); @@ -210,9 +198,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { // test unauthorized mapper { UserModel user = session.users().getUserByUsername("unauthorizedMapper", realm); - MgmtPermissions permissionsForAdmin = new MgmtPermissions(session, realm); - permissionsForAdmin.setIdentity(user); - Assert.assertTrue(permissionsForAdmin.users().canManage(user)); + AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user); + Assert.assertTrue(permissionsForAdmin.users().canManage()); Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole)); Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole)); // will result to true because realmRole2 does not have any policies attached to this permission @@ -391,6 +378,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } // testRestEvaluationMasterRealm // testRestEvaluationMasterAdminTestRealm + // test role deletion that it cleans up authz objects } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java index 531157daa3..e502ffaa3a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java @@ -51,7 +51,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; -import org.keycloak.services.resources.admin.RealmAuth.Resource; +import org.keycloak.services.resources.admin.AdminAuth.Resource; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; @@ -77,8 +77,8 @@ import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import static org.keycloak.services.resources.admin.RealmAuth.Resource.AUTHORIZATION; -import static org.keycloak.services.resources.admin.RealmAuth.Resource.CLIENT; +import static org.keycloak.services.resources.admin.AdminAuth.Resource.AUTHORIZATION; +import static org.keycloak.services.resources.admin.AdminAuth.Resource.CLIENT; import org.keycloak.testsuite.ProfileAssume; /** @@ -313,11 +313,18 @@ public class PermissionsTest extends AbstractKeycloakTest { realm.removeDefaultGroup("nosuch"); } }, Resource.REALM, true); + GroupRepresentation newGroup = new GroupRepresentation(); + newGroup.setName("sample"); + adminClient.realm(REALM_NAME).groups().add(newGroup); + GroupRepresentation group = adminClient.realms().realm(REALM_NAME).getGroupByPath("sample"); + invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.getGroupByPath("nosuch"); + realm.getGroupByPath("sample"); } - }, Resource.REALM, false); + }, Resource.USER, false); + + adminClient.realms().realm(REALM_NAME).groups().group(group.getId()).remove(); invoke(new InvocationWithResponse() { public void invoke(RealmResource realm, AtomicReference response) { @@ -954,6 +961,10 @@ public class PermissionsTest extends AbstractKeycloakTest { @Test public void roles() { + RoleRepresentation newRole = new RoleRepresentation(); + newRole.setName("sample-role"); + adminClient.realm(REALM_NAME).roles().create(newRole); + invoke(new Invocation() { public void invoke(RealmResource realm) { realm.roles().list(); @@ -961,12 +972,12 @@ public class PermissionsTest extends AbstractKeycloakTest { }, Resource.REALM, false, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").toRepresentation(); + realm.roles().get("sample-role").toRepresentation(); } }, Resource.REALM, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").update(new RoleRepresentation()); + realm.roles().get("sample-role").update(newRole); } }, Resource.REALM, true); invoke(new Invocation() { @@ -976,39 +987,42 @@ public class PermissionsTest extends AbstractKeycloakTest { }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().deleteRole("nosuch"); + realm.roles().deleteRole("sample-role"); + // need to recreate for other tests + realm.roles().create(newRole); } }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").getRoleComposites(); + realm.roles().get("sample-role").getRoleComposites(); } }, Resource.REALM, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").addComposites(Collections.emptyList()); + realm.roles().get("sample-role").addComposites(Collections.emptyList()); } }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").deleteComposites(Collections.emptyList()); + realm.roles().get("sample-role").deleteComposites(Collections.emptyList()); } }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").getRoleComposites(); + realm.roles().get("sample-role").getRoleComposites(); } }, Resource.REALM, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").getRealmRoleComposites(); + realm.roles().get("sample-role").getRealmRoleComposites(); } }, Resource.REALM, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.roles().get("nosuch").getClientRoleComposites("nosuch"); + realm.roles().get("sample-role").getClientRoleComposites("nosuch"); } }, Resource.REALM, false); + adminClient.realms().realm(REALM_NAME).roles().deleteRole("sample-role"); } @Test @@ -1175,51 +1189,61 @@ public class PermissionsTest extends AbstractKeycloakTest { @Test public void rolesById() { + RoleRepresentation newRole = new RoleRepresentation(); + newRole.setName("role-by-id"); + adminClient.realm(REALM_NAME).roles().create(newRole); + RoleRepresentation role = adminClient.realm(REALM_NAME).roles().get("role-by-id").toRepresentation(); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().getRole("nosuch"); + realm.rolesById().getRole(role.getId()); } }, Resource.REALM, false, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().updateRole("nosuch", new RoleRepresentation()); + realm.rolesById().updateRole(role.getId(), role); } }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().deleteRole("nosuch"); + realm.rolesById().deleteRole(role.getId()); + // need to recreate for other tests + realm.roles().create(newRole); + RoleRepresentation temp = realm.roles().get("role-by-id").toRepresentation(); + role.setId(temp.getId()); } }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().getRoleComposites("nosuch"); + realm.rolesById().getRoleComposites(role.getId()); } }, Resource.REALM, false, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().addComposites("nosuch", Collections.emptyList()); + realm.rolesById().addComposites(role.getId(), Collections.emptyList()); } }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().deleteComposites("nosuch", Collections.emptyList()); + realm.rolesById().deleteComposites(role.getId(), Collections.emptyList()); } }, Resource.REALM, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().getRoleComposites("nosuch"); + realm.rolesById().getRoleComposites(role.getId()); } }, Resource.REALM, false, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().getRealmRoleComposites("nosuch"); + realm.rolesById().getRealmRoleComposites(role.getId()); } }, Resource.REALM, false, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.rolesById().getClientRoleComposites("nosuch", "nosuch"); + realm.rolesById().getClientRoleComposites(role.getId(), "nosuch"); } }, Resource.REALM, false, true); + + adminClient.realm(REALM_NAME).roles().deleteRole("role-by-id"); } @Test @@ -1237,85 +1261,95 @@ public class PermissionsTest extends AbstractKeycloakTest { } }, Resource.USER, true); + GroupRepresentation group = adminClient.realms().realm(REALM_NAME).getGroupByPath("mygroup"); + ClientRepresentation realmAccessClient = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0); + + invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").toRepresentation(); + realm.groups().group(group.getId()).toRepresentation(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").update(new GroupRepresentation()); + realm.groups().group(group.getId()).update(group); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").remove(); - } - }, Resource.USER, true); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.groups().group("nosuch").members(0, 100); + realm.groups().group(group.getId()).members(0, 100); } }, Resource.USER, false); invoke(new InvocationWithResponse() { public void invoke(RealmResource realm, AtomicReference response) { - response.set(realm.groups().group("nosuch").subGroup(new GroupRepresentation())); + GroupRepresentation subgroup = new GroupRepresentation(); + subgroup.setName("sub"); + response.set(realm.groups().group(group.getId()).subGroup(subgroup)); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().getAll(); + realm.groups().group(group.getId()).roles().getAll(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().realmLevel().listAll(); + realm.groups().group(group.getId()).roles().realmLevel().listAll(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().realmLevel().listEffective(); + realm.groups().group(group.getId()).roles().realmLevel().listEffective(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().realmLevel().listAvailable(); + realm.groups().group(group.getId()).roles().realmLevel().listAvailable(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().realmLevel().add(Collections.emptyList()); + realm.groups().group(group.getId()).roles().realmLevel().add(Collections.emptyList()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().realmLevel().remove(Collections.emptyList()); + realm.groups().group(group.getId()).roles().realmLevel().remove(Collections.emptyList()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().clientLevel("nosuch").listAll(); + realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).listAll(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().clientLevel("nosuch").listEffective(); + realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).listEffective(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().clientLevel("nosuch").listAvailable(); + realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).listAvailable(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().clientLevel("nosuch").add(Collections.emptyList()); + realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).add(Collections.emptyList()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.groups().group("nosuch").roles().clientLevel("nosuch").remove(Collections.emptyList()); + realm.groups().group(group.getId()).roles().clientLevel(realmAccessClient.getId()).remove(Collections.emptyList()); + } + }, Resource.USER, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + realm.groups().group(group.getId()).remove(); + group.setId(null); + realm.groups().add(group); + GroupRepresentation temp = realm.getGroupByPath("mygroup"); + group.setId(temp.getId()); } }, Resource.USER, true); } @@ -1323,26 +1357,30 @@ public class PermissionsTest extends AbstractKeycloakTest { // Permissions for impersonation tested in ImpersonationTest @Test public void users() { - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.users().get("nosuch").toRepresentation(); - } - }, Resource.USER, false); invoke(new InvocationWithResponse() { public void invoke(RealmResource realm, AtomicReference response) { response.set(realm.users().create(UserBuilder.create().username("testuser").build())); } }, Resource.USER, true); + UserRepresentation user = adminClient.realms().realm(REALM_NAME).users().search("testUser").get(0); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").update(UserBuilder.create().enabled(true).build()); + realm.users().get(user.getId()).remove(); + realm.users().create(user); + UserRepresentation temp = realm.users().search("testUser").get(0); + user.setId(temp.getId()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().search("foo", 0, 1); + realm.users().get(user.getId()).toRepresentation(); } }, Resource.USER, false); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + realm.users().get(user.getId()).update(user); + } + }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { realm.users().count(); @@ -1350,149 +1388,150 @@ public class PermissionsTest extends AbstractKeycloakTest { }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").getUserSessions(); + realm.users().get(user.getId()).getUserSessions(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").getOfflineSessions("nosuch"); + realm.users().get(user.getId()).getOfflineSessions("nosuch"); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").getFederatedIdentity(); + realm.users().get(user.getId()).getFederatedIdentity(); } }, Resource.USER, false); invoke(new InvocationWithResponse() { public void invoke(RealmResource realm, AtomicReference response) { response.set(realm.users() - .get("nosuch") + .get(user.getId()) .addFederatedIdentity("nosuch", FederatedIdentityBuilder.create().identityProvider("nosuch").userId("nosuch").userName("nosuch").build())); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").removeFederatedIdentity("nosuch"); + realm.users().get(user.getId()).removeFederatedIdentity("nosuch"); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").getConsents(); + realm.users().get(user.getId()).getConsents(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").revokeConsent("testclient"); + realm.users().get(user.getId()).revokeConsent("testclient"); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").logout(); + realm.users().get(user.getId()).logout(); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").remove(); + realm.users().get(user.getId()).resetPassword(CredentialBuilder.create().password("password").build()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").resetPassword(CredentialBuilder.create().password("password").build()); + realm.users().get(user.getId()).removeTotp(); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").removeTotp(); + realm.users().get(user.getId()).resetPasswordEmail(); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").resetPasswordEmail(); + realm.users().get(user.getId()).executeActionsEmail(Collections.emptyList()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").executeActionsEmail(Collections.emptyList()); + realm.users().get(user.getId()).sendVerifyEmail(); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").sendVerifyEmail(); - } - }, Resource.USER, true); - invoke(new Invocation() { - public void invoke(RealmResource realm) { - realm.users().get("nosuch").groups(); + realm.users().get(user.getId()).groups(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").leaveGroup("nosuch"); + realm.users().get(user.getId()).leaveGroup("nosuch"); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").joinGroup("nosuch"); + realm.users().get(user.getId()).joinGroup("nosuch"); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().getAll(); + realm.users().get(user.getId()).roles().getAll(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().realmLevel().listAll(); + realm.users().get(user.getId()).roles().realmLevel().listAll(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().realmLevel().listAvailable(); + realm.users().get(user.getId()).roles().realmLevel().listAvailable(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().realmLevel().listEffective(); + realm.users().get(user.getId()).roles().realmLevel().listEffective(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().realmLevel().add(Collections.emptyList()); + realm.users().get(user.getId()).roles().realmLevel().add(Collections.emptyList()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().realmLevel().remove(Collections.emptyList()); + realm.users().get(user.getId()).roles().realmLevel().remove(Collections.emptyList()); } }, Resource.USER, true); + ClientRepresentation realmAccessClient = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().clientLevel("nosuch").listAll(); + realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).listAll(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().clientLevel("nosuch").listAvailable(); + realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).listAvailable(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().clientLevel("nosuch").listEffective(); + realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).listEffective(); } }, Resource.USER, false); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().clientLevel("nosuch").add(Collections.emptyList()); + realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).add(Collections.emptyList()); } }, Resource.USER, true); invoke(new Invocation() { public void invoke(RealmResource realm) { - realm.users().get("nosuch").roles().clientLevel("nosuch").remove(Collections.emptyList()); + realm.users().get(user.getId()).roles().clientLevel(realmAccessClient.getId()).remove(Collections.emptyList()); } }, Resource.USER, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + realm.users().search("foo", 0, 1); + } + }, Resource.USER, false); } @Test From a41d282e926633d69616912374651c87d54aefb5 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 2 Jun 2017 15:49:20 -0400 Subject: [PATCH 08/18] client permission tests --- .../resources/admin/ClientsResource.java | 5 +- .../resources/admin/UsersResource.java | 6 +- .../ClientPermissionEvaluator.java | 6 + .../ClientPermissionManagement.java | 8 + .../admin/permissions/ClientPermissions.java | 167 ++++++++++++++++-- .../admin/permissions/RolePermissions.java | 9 + .../permissions/UserPermissionEvaluator.java | 4 + .../admin/permissions/UserPermissions.java | 13 ++ .../admin/FineGrainAdminUnitTest.java | 90 +++++++++- 9 files changed, 278 insertions(+), 30 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index 5ff2e72c53..d62056014b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -203,11 +203,12 @@ public class ClientsResource { */ @Path("{id}") public ClientResource getClient(final @PathParam("id") String id) { - auth.clients().requireList(); ClientModel clientModel = realm.getClientById(id); if (clientModel == null) { - throw new NotFoundException("Could not find client"); + // we do this to make sure somebody can't phish ids + if (!auth.clients().canList()) throw new NotFoundException("Could not find client"); + else throw new ForbiddenException(); } session.getContext().setClient(clientModel); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index b1cd2e608f..fc4bfee770 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -192,11 +192,11 @@ public class UsersResource { */ @Path("{id}") public UserResource user(final @PathParam("id") String id) { - auth.users().requireQuery(); - UserModel user = session.users().getUserById(id, realm); if (user == null) { - throw new NotFoundException("User not found"); + // we do this to make sure somebody can't phish ids + if (auth.users().canQuery()) throw new NotFoundException("User not found"); + else throw new ForbiddenException(); } UserResource resource = new UserResource(realm, user, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java index 3da715a8b5..51e26c05da 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java @@ -67,4 +67,10 @@ public interface ClientPermissionEvaluator { boolean canView(ClientTemplateModel template); void requireView(ClientTemplateModel template); + + boolean canMapRoles(ClientModel client); + + boolean canMapCompositeRoles(ClientModel client); + + boolean canMapClientScopeRoles(ClientModel client); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java index 27f55272e8..26ae9514d8 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java @@ -16,6 +16,8 @@ */ package org.keycloak.services.resources.admin.permissions; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.ClientModel; /** @@ -23,7 +25,13 @@ import org.keycloak.models.ClientModel; * @version $Revision: 1 $ */ public interface ClientPermissionManagement { + public static final String MAP_ROLES_SCOPE = "map-roles"; + boolean isPermissionsEnabled(ClientModel client); void setPermissionsEnabled(ClientModel client, boolean enable); + + Policy mapRolesPermission(ClientModel client); + + ResourceServer resourceServer(ClientModel client); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java index 8209d2e7c1..3118f7a0f3 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java @@ -55,7 +55,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa } private String getResourceName(ClientModel client) { - return "group.resource." + client.getId(); + return "client.resource." + client.getId(); } private String getManagePermissionName(ClientModel client) { @@ -64,25 +64,50 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private String getViewPermissionName(ClientModel client) { return "view.permission.client." + client.getId(); } + private String getMapRolesPermissionName(ClientModel client) { + return MAP_ROLES_SCOPE + ".permission.client." + client.getId(); + } + private String getMapRoleClientScopePermissionName(ClientModel client) { + return RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission.client." + client.getId(); + } + private String getMapRoleCompositePermissionName(ClientModel client) { + return RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE + ".permission.client." + client.getId(); + } private void initialize(ClientModel client) { ResourceServer server = root.findOrCreateResourceServer(client); Scope manageScope = manageScope(server); if (manageScope == null) { - authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.MANAGE_SCOPE, server); + manageScope = authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.MANAGE_SCOPE, server); } Scope viewScope = viewScope(server); - if (manageScope == null) { - authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.VIEW_SCOPE, server); + if (viewScope == null) { + viewScope = authz.getStoreFactory().getScopeStore().create(AdminPermissionManagement.VIEW_SCOPE, server); + } + Scope mapRoleScope = mapRolesScope(server); + if (mapRoleScope == null) { + mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_SCOPE, server); + } + Scope mapRoleClientScope = mapRoleClientScope(server); + if (mapRoleClientScope == null) { + mapRoleClientScope = authz.getStoreFactory().getScopeStore().create(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, server); + } + Scope mapRoleCompositeScope = mapRoleCompositeScope(server); + if (mapRoleCompositeScope == null) { + mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().create(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, server); } String resourceName = getResourceName(client); Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId()); if (resource == null) { resource = authz.getStoreFactory().getResourceStore().create(resourceName, server, server.getClientId()); + resource.setType("Client"); Set scopeset = new HashSet<>(); scopeset.add(manageScope); scopeset.add(viewScope); + scopeset.add(mapRoleScope); + scopeset.add(mapRoleClientScope); + scopeset.add(mapRoleCompositeScope); resource.updateScopes(scopeset); } String managePermissionName = getManagePermissionName(client); @@ -99,26 +124,46 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa Policy viewClientsPolicy = root.roles().rolePolicy(server, role); Helper.addScopePermission(authz, server, viewPermissionName, resource, viewScope, viewClientsPolicy); } + String mapRolePermissionName = getMapRolesPermissionName(client); + Policy mapRolePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRolePermissionName, server.getId()); + if (mapRolePermission == null) { + Helper.addEmptyScopePermission(authz, server, mapRolePermissionName, resource, mapRoleScope); + } + String mapRoleClientScopePermissionName = getMapRoleClientScopePermissionName(client); + Policy mapRoleClientScopePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRoleClientScopePermissionName, server.getId()); + if (mapRoleClientScopePermission == null) { + Helper.addEmptyScopePermission(authz, server, mapRoleClientScopePermissionName, resource, mapRoleClientScope); + } + String mapRoleCompositePermissionName = getMapRoleCompositePermissionName(client); + Policy mapRoleCompositePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRoleCompositePermissionName, server.getId()); + if (mapRoleCompositePermission == null) { + Helper.addEmptyScopePermission(authz, server, mapRoleCompositePermissionName, resource, mapRoleCompositeScope); + } + } + + private void deletePolicy(String name, ClientModel client, ResourceServer server) { + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId()); + if (policy != null) { + authz.getStoreFactory().getPolicyStore().delete(policy.getId()); + } + } private void deletePermissions(ClientModel client) { - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + ResourceServer server = resourceServer(client); if (server == null) return; - Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId()); - if (managePermission != null) { - authz.getStoreFactory().getPolicyStore().delete(managePermission.getId()); - } - Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId()); - if (viewPermission != null) { - authz.getStoreFactory().getPolicyStore().delete(viewPermission.getId()); - } + deletePolicy(getManagePermissionName(client), client, server); + deletePolicy(getViewPermissionName(client), client, server); + deletePolicy(getMapRolesPermissionName(client), client, server); + deletePolicy(getMapRoleClientScopePermissionName(client), client, server); + deletePolicy(getMapRoleCompositePermissionName(client), client, server); Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());; if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); } @Override public boolean isPermissionsEnabled(ClientModel client) { - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + ResourceServer server = resourceServer(client); if (server == null) return false; return authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()) != null; @@ -142,6 +187,15 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private Scope viewScope(ResourceServer server) { return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.VIEW_SCOPE, server.getId()); } + private Scope mapRolesScope(ResourceServer server) { + return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_SCOPE, server.getId()); + } + private Scope mapRoleClientScope(ResourceServer server) { + return authz.getStoreFactory().getScopeStore().findByName(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, server.getId()); + } + private Scope mapRoleCompositeScope(ResourceServer server) { + return authz.getStoreFactory().getScopeStore().findByName(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, server.getId()); + } @Override public boolean canList() { @@ -202,7 +256,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return canManage(); } - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + ResourceServer server = resourceServer(client); if (server == null) return canManage(); Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); @@ -236,7 +290,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return canView(); } - ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + ResourceServer server = resourceServer(client); if (server == null) return canView(); Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); @@ -312,4 +366,85 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa throw new ForbiddenException(); } } + + @Override + public boolean canMapRoles(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolesPermissionName(client), server.getId()); + if (policy == null) { + return false; + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return false; + } + + Scope scope = mapRolesScope(server); + return root.evaluatePermission(resource, scope, server); + } + + @Override + public Policy mapRolesPermission(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + return authz.getStoreFactory().getPolicyStore().findByName(getMapRolesPermissionName(client), server.getId()); + } + + @Override + public ResourceServer resourceServer(ClientModel client) { + return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); + } + + @Override + public boolean canMapCompositeRoles(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleCompositePermissionName(client), server.getId()); + if (policy == null) { + return false; + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return false; + } + + Scope scope = mapRoleCompositeScope(server); + return root.evaluatePermission(resource, scope, server); + } + @Override + public boolean canMapClientScopeRoles(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleClientScopePermissionName(client), server.getId()); + if (policy == null) { + return false; + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return false; + } + + Scope scope = mapRoleClientScope(server); + return root.evaluatePermission(resource, scope, server); + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java index 4167a124e2..5f666c66b0 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java @@ -143,6 +143,9 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme if (!root.isAdminSameRealm()) { return root.users().canManage(); } + if (role.getContainer() instanceof ClientModel) { + if (root.clients().canMapRoles((ClientModel)role.getContainer())) return true; + } if (!isPermissionsEnabled(role)){ return root.users().canManage(); } @@ -216,6 +219,9 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme if (!root.isAdminSameRealm()) { return canManage(role); } + if (role.getContainer() instanceof ClientModel) { + if (root.clients().canMapCompositeRoles((ClientModel)role.getContainer())) return true; + } if (!isPermissionsEnabled(role)){ return canManage(role); } @@ -245,6 +251,9 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme if (!root.isAdminSameRealm()) { return root.clients().canManage(); } + if (role.getContainer() instanceof ClientModel) { + if (root.clients().canMapClientScopeRoles((ClientModel)role.getContainer())) return true; + } if (!isPermissionsEnabled(role)){ return root.clients().canManage(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java index 488825b559..e276ae29df 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java @@ -34,6 +34,10 @@ public interface UserPermissionEvaluator { void requireQuery(); + boolean canQuery(UserModel user); + + void requireQuery(UserModel user); + boolean canView(); boolean canView(UserModel user); void requireView(UserModel user); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java index 5e7931b750..b095592766 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -291,6 +291,19 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme } } + @Override + public boolean canQuery(UserModel user) { + return canView(user); + } + + @Override + public void requireQuery(UserModel user) { + if (!canQuery(user)) { + throw new ForbiddenException(); + } + + } + /** diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 2b34700dee..5d413d0abb 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -114,6 +114,9 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { { permissions.groups().setPermissionsEnabled(group, true); } + { + permissions.clients().setPermissionsEnabled(client1, true); + } } @@ -156,12 +159,6 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { UserModel user1 = session.users().addUser(realm, "user1"); user1.setEnabled(true); - UserModel user2 = session.users().addUser(realm, "user2"); - user2.setEnabled(true); - UserModel user3 = session.users().addUser(realm, "user3"); - user3.setEnabled(true); - UserModel user4 = session.users().addUser(realm, "user4"); - user4.setEnabled(true); // group management AdminPermissionManagement permissions = AdminPermissions.management(session, realm); @@ -188,6 +185,17 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { Policy groupManagerPermission = permissions.groups().manageMembersPermission(group); groupManagerPermission.addAssociatedPolicy(groupManagerPolicy); + UserModel clientMapper = session.users().addUser(realm, "clientMapper"); + clientMapper.setEnabled(true); + clientMapper.grantRole(managerRole); + session.userCredentialManager().updateCredential(realm, clientMapper, UserCredentialModel.password("password")); + Policy clientMapperPolicy = permissions.clients().mapRolesPermission(client); + UserPolicyRepresentation userRep = new UserPolicyRepresentation(); + userRep.setName("userClientMapper"); + userRep.addUser("clientMapper"); + Policy userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client)); + clientMapperPolicy.addAssociatedPolicy(userPolicy); + @@ -254,6 +262,17 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2)); } + // test client.mapRoles + { + UserModel admin = session.users().getUserByUsername("clientMapper", realm); + AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, admin); + UserModel user = session.users().getUserByUsername("authorized", realm); + Assert.assertTrue(permissionsForAdmin.users().canManage(user)); + Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole)); + Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole)); + Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2)); + + } } @@ -283,9 +302,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { testingClient.server().run(FineGrainAdminUnitTest::setupUsers); UserRepresentation user1 = adminClient.realm(TEST).users().search("user1").get(0); - UserRepresentation user2 = adminClient.realm(TEST).users().search("user2").get(0); - UserRepresentation user3 = adminClient.realm(TEST).users().search("user3").get(0); - UserRepresentation user4 = adminClient.realm(TEST).users().search("user4").get(0); + UserRepresentation groupMember = adminClient.realm(TEST).users().search("groupMember").get(0); RoleRepresentation realmRole = adminClient.realm(TEST).roles().get("realm-role").toRepresentation(); List realmRoleSet = new LinkedList<>(); realmRoleSet.add(realmRole); @@ -373,6 +390,61 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } } + { + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "groupManager", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + List roles = null; + realmClient.realm(TEST).users().get(groupMember.getId()).roles().clientLevel(client.getId()).add(clientRoleSet); + roles = realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + Assert.assertTrue(roles.stream().anyMatch((r) -> { + return r.getName().equals("client-role"); + })); + realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet); + + roles = realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAvailable(); + Assert.assertEquals(roles.size(), 1); + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().remove(realmRoleSet); + try { + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRole2Set); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + try { + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + } + + + // test client.mapRoles + { + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "clientMapper", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + List roles = null; + realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet); + roles = realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + Assert.assertTrue(roles.stream().anyMatch((r) -> { + return r.getName().equals("client-role"); + })); + roles = realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAvailable(); + Assert.assertTrue(roles.isEmpty()); + try { + realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + } + + } @Test From 536a57a514609670394b0f107e70fc1f8afa5475 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 5 Jun 2017 19:52:51 -0400 Subject: [PATCH 09/18] ui for permission reference --- .../resources/admin/ClientResource.java | 55 ++++++++++++ .../resources/admin/ClientsResource.java | 2 +- .../resources/admin/GroupResource.java | 55 ++++++++++++ .../ClientPermissionManagement.java | 17 ++++ .../admin/permissions/ClientPermissions.java | 89 ++++++++++++++----- .../GroupPermissionManagement.java | 5 ++ .../admin/permissions/GroupPermissions.java | 36 ++++++-- .../admin/permissions/RolePermissions.java | 6 +- .../testsuite/AbstractKeycloakTest.java | 6 +- .../admin/FineGrainAdminUnitTest.java | 21 +++-- .../messages/admin-messages_en.properties | 17 +++- .../admin/resources/js/authz/authz-app.js | 24 +++++ .../resources/js/authz/authz-controller.js | 32 +++++++ .../resources/js/authz/authz-services.js | 48 +++++++++- .../theme/base/admin/resources/js/services.js | 22 ----- .../authz/mgmt/client-permissions.html | 39 ++++++++ .../authz/mgmt/client-role-permissions.html | 3 +- .../authz/mgmt/group-permissions.html | 39 ++++++++ .../authz/mgmt/realm-role-permissions.html | 2 +- .../authz/mgmt/users-permissions.html | 2 +- .../resources/templates/kc-tabs-client.html | 4 + .../resources/templates/kc-tabs-group.html | 4 + 22 files changed, 456 insertions(+), 72 deletions(-) create mode 100644 themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html create mode 100644 themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 58ae6231b8..36362cb67b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -31,6 +31,7 @@ import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; @@ -41,6 +42,7 @@ import org.keycloak.protocol.ClientInstallationProvider; import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.services.ErrorResponse; @@ -52,6 +54,8 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.services.validation.ClientValidator; import org.keycloak.services.validation.PairwiseClientValidator; import org.keycloak.services.validation.ValidationMessages; @@ -536,4 +540,55 @@ public class ClientResource { return resource; } + + /** + * Return object stating whether client Authorization permissions have been initialized or not and a reference + * + * @return + */ + @Path("management/permissions") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference getManagementPermissions() { + auth.roles().requireView(client); + + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); + if (!permissions.clients().isPermissionsEnabled(client)) { + return new ManagementPermissionReference(); + } + return toMgmtRef(client, permissions); + } + + public static ManagementPermissionReference toMgmtRef(ClientModel client, AdminPermissionManagement permissions) { + ManagementPermissionReference ref = new ManagementPermissionReference(); + ref.setEnabled(true); + ref.setResource(permissions.clients().resource(client).getId()); + ref.setScopePermissions(permissions.clients().getPermissions(client)); + return ref; + } + + + /** + * Return object stating whether client Authorization permissions have been initialized or not and a reference + * + * + * @return initialized manage permissions reference + */ + @Path("management/permissions") + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference setManagementPermissionsEnabled(ManagementPermissionReference ref) { + auth.clients().requireManage(client); + if (ref.isEnabled()) { + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); + permissions.clients().setPermissionsEnabled(client, ref.isEnabled()); + return toMgmtRef(client, permissions); + } else { + return new ManagementPermissionReference(); + } + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index d62056014b..41b91befdf 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -207,7 +207,7 @@ public class ClientsResource { ClientModel clientModel = realm.getClientById(id); if (clientModel == null) { // we do this to make sure somebody can't phish ids - if (!auth.clients().canList()) throw new NotFoundException("Could not find client"); + if (auth.clients().canList()) throw new NotFoundException("Could not find client"); else throw new ForbiddenException(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java index a529a4895e..a5b7a6c77b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java @@ -21,6 +21,7 @@ import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; +import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -28,6 +29,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.UserRepresentation; import javax.ws.rs.Consumes; @@ -50,6 +52,8 @@ import java.util.Map; import java.util.Set; import org.keycloak.services.ErrorResponse; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; /** * @resource Groups @@ -214,4 +218,55 @@ public class GroupResource { return results; } + /** + * Return object stating whether client Authorization permissions have been initialized or not and a reference + * + * @return + */ + @Path("management/permissions") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference getManagementPermissions() { + auth.groups().requireView(group); + + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); + if (!permissions.groups().isPermissionsEnabled(group)) { + return new ManagementPermissionReference(); + } + return toMgmtRef(group, permissions); + } + + public static ManagementPermissionReference toMgmtRef(GroupModel group, AdminPermissionManagement permissions) { + ManagementPermissionReference ref = new ManagementPermissionReference(); + ref.setEnabled(true); + ref.setResource(permissions.groups().resource(group).getId()); + ref.setScopePermissions(permissions.groups().getPermissions(group)); + return ref; + } + + + /** + * Return object stating whether client Authorization permissions have been initialized or not and a reference + * + * + * @return initialized manage permissions reference + */ + @Path("management/permissions") + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @NoCache + public ManagementPermissionReference setManagementPermissionsEnabled(ManagementPermissionReference ref) { + auth.groups().requireManage(group); + if (ref.isEnabled()) { + AdminPermissionManagement permissions = AdminPermissions.management(session, realm); + permissions.groups().setPermissionsEnabled(group, ref.isEnabled()); + return toMgmtRef(group, permissions); + } else { + return new ManagementPermissionReference(); + } + } + } + diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java index 26ae9514d8..d967443264 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java @@ -17,21 +17,38 @@ package org.keycloak.services.resources.admin.permissions; import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.ClientModel; +import java.util.Map; + /** * @author Bill Burke * @version $Revision: 1 $ */ public interface ClientPermissionManagement { public static final String MAP_ROLES_SCOPE = "map-roles"; + public static final String MAP_ROLES_CLIENT_SCOPE = "map-roles-client-scope"; + public static final String MAP_ROLES_COMPOSITE_SCOPE = "map-roles-composite"; boolean isPermissionsEnabled(ClientModel client); void setPermissionsEnabled(ClientModel client, boolean enable); + Resource resource(ClientModel client); + + Map getPermissions(ClientModel client); + Policy mapRolesPermission(ClientModel client); + Policy mapRolesClientScopePermission(ClientModel client); + + Policy mapRolesCompositePermission(ClientModel client); + + Policy managePermission(ClientModel client); + + Policy viewPermission(ClientModel client); + ResourceServer resourceServer(ClientModel client); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java index 3118f7a0f3..06fa5d822e 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java @@ -30,7 +30,9 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.services.ForbiddenException; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** @@ -67,11 +69,11 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private String getMapRolesPermissionName(ClientModel client) { return MAP_ROLES_SCOPE + ".permission.client." + client.getId(); } - private String getMapRoleClientScopePermissionName(ClientModel client) { - return RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission.client." + client.getId(); + private String getMapRolesClientScopePermissionName(ClientModel client) { + return MAP_ROLES_CLIENT_SCOPE + ".permission.client." + client.getId(); } - private String getMapRoleCompositePermissionName(ClientModel client) { - return RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE + ".permission.client." + client.getId(); + private String getMapRolesCompositePermissionName(ClientModel client) { + return MAP_ROLES_COMPOSITE_SCOPE + ".permission.client." + client.getId(); } private void initialize(ClientModel client) { @@ -88,13 +90,13 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa if (mapRoleScope == null) { mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_SCOPE, server); } - Scope mapRoleClientScope = mapRoleClientScope(server); + Scope mapRoleClientScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId()); if (mapRoleClientScope == null) { - mapRoleClientScope = authz.getStoreFactory().getScopeStore().create(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, server); + mapRoleClientScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_CLIENT_SCOPE, server); } - Scope mapRoleCompositeScope = mapRoleCompositeScope(server); + Scope mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId()); if (mapRoleCompositeScope == null) { - mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().create(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, server); + mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_COMPOSITE_SCOPE, server); } String resourceName = getResourceName(client); @@ -129,12 +131,12 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa if (mapRolePermission == null) { Helper.addEmptyScopePermission(authz, server, mapRolePermissionName, resource, mapRoleScope); } - String mapRoleClientScopePermissionName = getMapRoleClientScopePermissionName(client); + String mapRoleClientScopePermissionName = getMapRolesClientScopePermissionName(client); Policy mapRoleClientScopePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRoleClientScopePermissionName, server.getId()); if (mapRoleClientScopePermission == null) { Helper.addEmptyScopePermission(authz, server, mapRoleClientScopePermissionName, resource, mapRoleClientScope); } - String mapRoleCompositePermissionName = getMapRoleCompositePermissionName(client); + String mapRoleCompositePermissionName = getMapRolesCompositePermissionName(client); Policy mapRoleCompositePermission = authz.getStoreFactory().getPolicyStore().findByName(mapRoleCompositePermissionName, server.getId()); if (mapRoleCompositePermission == null) { Helper.addEmptyScopePermission(authz, server, mapRoleCompositePermissionName, resource, mapRoleCompositeScope); @@ -155,8 +157,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa deletePolicy(getManagePermissionName(client), client, server); deletePolicy(getViewPermissionName(client), client, server); deletePolicy(getMapRolesPermissionName(client), client, server); - deletePolicy(getMapRoleClientScopePermissionName(client), client, server); - deletePolicy(getMapRoleCompositePermissionName(client), client, server); + deletePolicy(getMapRolesClientScopePermissionName(client), client, server); + deletePolicy(getMapRolesCompositePermissionName(client), client, server); Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());; if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); } @@ -190,12 +192,6 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private Scope mapRolesScope(ResourceServer server) { return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_SCOPE, server.getId()); } - private Scope mapRoleClientScope(ResourceServer server) { - return authz.getStoreFactory().getScopeStore().findByName(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, server.getId()); - } - private Scope mapRoleCompositeScope(ResourceServer server) { - return authz.getStoreFactory().getScopeStore().findByName(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, server.getId()); - } @Override public boolean canList() { @@ -250,6 +246,27 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa } } + @Override + public Resource resource(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return null; + return resource; + } + + @Override + public Map getPermissions(ClientModel client) { + Map scopes = new HashMap<>(); + scopes.put(MAP_ROLES_SCOPE, mapRolesPermission(client).getId()); + scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId()); + scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId()); + scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(client).getId()); + scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(client).getId()); + return scopes; + } + + @Override public boolean canManage(ClientModel client) { if (!root.isAdminSameRealm()) { @@ -397,6 +414,34 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return authz.getStoreFactory().getPolicyStore().findByName(getMapRolesPermissionName(client), server.getId()); } + @Override + public Policy mapRolesClientScopePermission(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + return authz.getStoreFactory().getPolicyStore().findByName(getMapRolesClientScopePermissionName(client), server.getId()); + } + + @Override + public Policy mapRolesCompositePermission(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + return authz.getStoreFactory().getPolicyStore().findByName(getMapRolesCompositePermissionName(client), server.getId()); + } + + @Override + public Policy managePermission(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + return authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId()); + } + + @Override + public Policy viewPermission(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + return authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId()); + } + @Override public ResourceServer resourceServer(ClientModel client) { return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId()); @@ -410,7 +455,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); if (resource == null) return false; - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleCompositePermissionName(client), server.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolesCompositePermissionName(client), server.getId()); if (policy == null) { return false; } @@ -421,7 +466,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return false; } - Scope scope = mapRoleCompositeScope(server); + Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId()); return root.evaluatePermission(resource, scope, server); } @Override @@ -432,7 +477,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); if (resource == null) return false; - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRoleClientScopePermissionName(client), server.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolesClientScopePermissionName(client), server.getId()); if (policy == null) { return false; } @@ -443,7 +488,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return false; } - Scope scope = mapRoleClientScope(server); + Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId()); return root.evaluatePermission(resource, scope, server); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java index 5f4097be27..a3277ba316 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java @@ -17,6 +17,7 @@ package org.keycloak.services.resources.admin.permissions; import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; import org.keycloak.models.GroupModel; import org.keycloak.models.RoleModel; @@ -34,4 +35,8 @@ public interface GroupPermissionManagement { Policy manageMembersPermission(GroupModel group); Policy viewPermissionGroup(GroupModel group); Policy managePermissionGroup(GroupModel group); + + Resource resource(GroupModel group); + + Map getPermissions(GroupModel group); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java index e1c035622e..acf80c13d1 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java @@ -23,13 +23,16 @@ 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.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.ForbiddenException; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** @@ -54,16 +57,16 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag } private static String getGroupResourceName(GroupModel group) { - return "group.resource." + getGroupSuffix(group); + return "group.resource." + group.getId(); } public static String getManagePermissionGroup(GroupModel group) { - return "manage.permission.group." + getGroupSuffix(group); + return "manage.permission.group." + group.getId(); } public static String getManageMembersPermissionGroup(GroupModel group) { - return "manage.members.permission.group." + getGroupSuffix(group); + return "manage.members.permission.group." + group.getId(); } public static String getGroupSuffix(GroupModel group) { @@ -71,11 +74,11 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag } public static String getViewPermissionGroup(GroupModel group) { - return "view.permission.group." + getGroupSuffix(group); + return "view.permission.group." + group.getId(); } public static String getViewMembersPermissionGroup(GroupModel group) { - return "view.members.permission.group." + getGroupSuffix(group); + return "view.members.permission.group." + group.getId(); } private void initialize(GroupModel group) { @@ -102,7 +105,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag Policy manageUsersPolicy = root.roles().manageUsersPolicy(server); Helper.addScopePermission(authz, server, managePermissionName, groupResource, manageScope, manageUsersPolicy); } - String viewPermissionName = getManagePermissionGroup(group); + String viewPermissionName = getViewPermissionGroup(group); Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId()); if (viewPermission == null) { Policy viewUsersPolicy = root.roles().viewUsersPolicy(server); @@ -213,6 +216,27 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag return authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId()); } + @Override + public Resource resource(GroupModel group) { + ResourceServer server = root.realmResourceServer(); + if (server == null) return null; + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); + if (resource == null) return null; + return resource; + } + + @Override + public Map getPermissions(GroupModel group) { + Map scopes = new HashMap<>(); + scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermissionGroup(group).getId()); + scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermissionGroup(group).getId()); + scopes.put(MANAGE_MEMBERS_SCOPE, manageMembersPermission(group).getId()); + scopes.put(VIEW_MEMBERS_SCOPE, viewMembersPermission(group).getId()); + return scopes; + } + + + @Override public boolean canManage(GroupModel group) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java index 5f666c66b0..83838de94b 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java @@ -141,19 +141,19 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme @Override public boolean canMapRole(RoleModel role) { if (!root.isAdminSameRealm()) { - return root.users().canManage(); + return root.users().canManageDefault(); } if (role.getContainer() instanceof ClientModel) { if (root.clients().canMapRoles((ClientModel)role.getContainer())) return true; } if (!isPermissionsEnabled(role)){ - return root.users().canManage(); + return root.users().canManageDefault(); } ResourceServer resourceServer = getResourceServer(role); Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), resourceServer.getId()); if (policy.getAssociatedPolicies().isEmpty()) { - return root.users().canManage(); // if no policies applied, just do default + return root.users().canManageDefault(); // if no policies applied, just do default } Resource roleResource = resource(role); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 1601189c40..d6d2ad8e15 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -175,7 +175,11 @@ public abstract class AbstractKeycloakTest { // Cleanup objects for (TestCleanup cleanup : testContext.getCleanups().values()) { - if (cleanup != null) cleanup.executeCleanup(); + try { + if (cleanup != null) cleanup.executeCleanup(); + } catch (Exception e) { + throw new RuntimeException(e); + } } testContext.getCleanups().clear(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 5d413d0abb..72f3df93af 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -282,7 +282,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } - //@Test + @Test public void testUI() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupPolices); testingClient.server().run(FineGrainAdminUnitTest::setupUsers); @@ -308,7 +308,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { realmRoleSet.add(realmRole); RoleRepresentation realmRole2 = adminClient.realm(TEST).roles().get("realm-role2").toRepresentation(); List realmRole2Set = new LinkedList<>(); - realmRole2Set.add(realmRole); + realmRole2Set.add(realmRole2); ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId("role-namespace").get(0); RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation(); List clientRoleSet = new LinkedList<>(); @@ -395,18 +395,18 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { TEST, "groupManager", "password", Constants.ADMIN_CLI_CLIENT_ID, null); List roles = null; realmClient.realm(TEST).users().get(groupMember.getId()).roles().clientLevel(client.getId()).add(clientRoleSet); - roles = realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + roles = realmClient.realm(TEST).users().get(groupMember.getId()).roles().clientLevel(client.getId()).listAll(); Assert.assertTrue(roles.stream().anyMatch((r) -> { return r.getName().equals("client-role"); })); - realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet); + realmClient.realm(TEST).users().get(groupMember.getId()).roles().clientLevel(client.getId()).remove(clientRoleSet); - roles = realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().listAvailable(); + roles = realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().listAvailable(); Assert.assertEquals(roles.size(), 1); - realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRoleSet); - realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().remove(realmRoleSet); + realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().add(realmRoleSet); + realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().remove(realmRoleSet); try { - realmClient.realm(TEST).users().get(user1.getId()).roles().realmLevel().add(realmRole2Set); + realmClient.realm(TEST).users().get(groupMember.getId()).roles().realmLevel().add(realmRole2Set); Assert.fail("should fail with forbidden exception"); } catch (ClientErrorException e) { Assert.assertEquals(e.getResponse().getStatus(), 403); @@ -428,6 +428,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), TEST, "clientMapper", "password", Constants.ADMIN_CLI_CLIENT_ID, null); List roles = null; + roles = realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); + Assert.assertTrue(roles.isEmpty()); realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).add(clientRoleSet); roles = realmClient.realm(TEST).users().get(user1.getId()).roles().clientLevel(client.getId()).listAll(); Assert.assertTrue(roles.stream().anyMatch((r) -> { @@ -455,9 +457,6 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { testingClient.server().run(FineGrainAdminUnitTest::setupUsers); UserRepresentation user1 = adminClient.realm(TEST).users().search("user1").get(0); - UserRepresentation user2 = adminClient.realm(TEST).users().search("user2").get(0); - UserRepresentation user3 = adminClient.realm(TEST).users().search("user3").get(0); - UserRepresentation user4 = adminClient.realm(TEST).users().search("user4").get(0); RoleRepresentation realmRole = adminClient.realm(TEST).roles().get("realm-role").toRepresentation(); List realmRoleSet = new LinkedList<>(); realmRoleSet.add(realmRole); 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 1dd53fa6af..11fe2c5c06 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 @@ -1306,7 +1306,8 @@ credential-reset-actions-timeout.tooltip=Maximum time before the action permit e ldap-mappers=LDAP Mappers create-ldap-mapper=Create LDAP mapper map-role-mgmt-scope-description=Policies that decide if an admin can map this role to a user or group -manage-mgmt-scope-description=Policies that decide if an admin can manage this resource or resources +manage-authz-users-scope-description=Policies that decide if an admin can manage all users in the realm +view-authz-users-scope-description=Policies that decide if an admin can view all users in realm permissions-enabled-role=Permissions Enabled permissions-enabled-role.tooltip=Whether or not to enable fine grain permissions for managing this role. Disabling will delete all current permissions that have been set up. manage-permissions-role.tooltip=Fine grain permissions for managing roles. For example, you can define different policies for who is allowed to map a role. @@ -1314,6 +1315,20 @@ lookup=Lookup manage-permissions-users.tooltip=Fine grain permssions for managing all users in realm. You can define different policies for who is allowed to manage users in the realm. permissions-enabled-users=Permissions Enabled permissions-enabled-users.tooltip=Whether or not to enable fine grain permissions for managing users. Disabling will delete all current permissions that have been set up. +manage-permissions-client.tooltip=Fine grain permssions for admins that want to manage this client or apply roles defined by this client. +manage-permissions-group.tooltip=Fine grain permssions for admins that want to manage this group or the members of this group. +manage-authz-group-scope-description=Policies that decide if an admin can manage this group +view-authz-group-scope-description=Policies that decide if an admin can view this group +view.members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group +manage.members-authz-group-scope-description=Policies that decide if an admin can view the members of this group +manage-authz-client-scope-description=Policies that decide if an admin can manage this client +view-authz-client-scope-description=Policies that decide if an admin can view this client +map-roles-authz-client-scope-description=Policies that decide if an admin can map roles defined by this client +map-roles-client-scope-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client to the client scope of another client +map-roles-composite-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client as a composite to another role +map-role-authz-role-scope-description=Policies that decide if an admin can map role this role to a user or group +map-role-client-scope-authz-role-scope-description=Policies that decide if an admin can apply this role to the client scope of a client +map-role-composite-authz-role-scope-description=Policies that decide if an admin can apply this role as a composite to another role diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js index 455891dc5e..047fa492d4 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js @@ -424,6 +424,30 @@ module.config(['$routeProvider', function ($routeProvider) { }, controller : 'UsersPermissionsCtrl' }) + .when('/realms/:realm/clients/:client/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/client-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientPermissionsCtrl' + }) + .when('/realms/:realm/groups/:group/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/group-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupPermissionsCtrl' + }) ; }]); 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 5eb0ba2d88..37b4984dfe 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 @@ -2419,3 +2419,35 @@ module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $locat }); +module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $location, realm, client, ClientManagementPermissions, Notifications) { + $scope.client = client; + $scope.realm = realm; + ClientManagementPermissions.get({realm: realm.realm, client: client.id}, function(data) { + $scope.permissions = data; + }); + $scope.setEnabled = function() { + var param = { enabled: $scope.permissions.enabled}; + $scope.permissions = ClientManagementPermissions.update({realm: realm.realm, client: client.id}, param); + }; + + +}); + +module.controller('GroupPermissionsCtrl', function($scope, $http, $route, $location, realm, group, GroupManagementPermissions, Client, Notifications) { + $scope.group = group; + $scope.realm = realm; + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); + GroupManagementPermissions.get({realm: realm.realm, group: group.id}, function(data) { + $scope.permissions = data; + }); + $scope.setEnabled = function() { + var param = { enabled: $scope.permissions.enabled}; + $scope.permissions = GroupManagementPermissions.update({realm: realm.realm, group: group.id}, param); + }; + + +}); + + diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js index 92219bb570..f56ca8f695 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js @@ -144,4 +144,50 @@ module.service('AuthzDialog', function($modal) { } return dialog; -}); \ No newline at end of file +}); + +module.factory('RoleManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/management/permissions', { + realm : '@realm', + role : '@role' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('UsersManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users-management-permissions', { + realm : '@realm' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('ClientManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/management/permissions', { + realm : '@realm', + client : '@client' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('GroupManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:group/management/permissions', { + realm : '@realm', + group : '@group' + }, { + update: { + method: 'PUT' + } + }); +}); + + + diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index 50b50aba91..e850b3bc4b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -842,28 +842,6 @@ module.factory('Role', function($resource) { }); }); -module.factory('RoleManagementPermissions', function($resource) { - return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/management/permissions', { - realm : '@realm', - role : '@role' - }, { - update: { - method: 'PUT' - } - }); -}); - -module.factory('UsersManagementPermissions', function($resource) { - return $resource(authUrl + '/admin/realms/:realm/users-management-permissions', { - realm : '@realm' - }, { - update: { - method: 'PUT' - } - }); -}); - - module.factory('RoleById', function($resource) { return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role', { realm : '@realm', diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html new file mode 100644 index 0000000000..50241b2139 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html @@ -0,0 +1,39 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'permissions-enabled-role.tooltip' | translate}} +
    +
    + +
    + + + + + + + + + + + + + + + + +
    + + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html index d2d6c140e1..988c1fa384 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html @@ -2,7 +2,6 @@ @@ -30,7 +29,7 @@ {{scopeName}} - + {{:: 'edit' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html new file mode 100644 index 0000000000..61770c91cf --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html @@ -0,0 +1,39 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'permissions-enabled-role.tooltip' | translate}} +
    +
    +
    + + + + + + + + + + + + + + + + + +
    + + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html index 82c841343b..9c03333397 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html @@ -28,7 +28,7 @@ {{scopeName}} - + {{:: 'edit' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html index 92a90678dc..4a5661f28b 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/users-permissions.html @@ -24,7 +24,7 @@ {{scopeName}} - + {{:: 'edit' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html index 2a3be322bb..a18c31fbd1 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html @@ -43,5 +43,9 @@ {{:: 'service-account-roles' | translate}} {{:: 'service-account-roles.tooltip' | translate}} +
  • + {{:: 'authz-permissions' | translate}} + {{:: 'manage-permissions-client.tooltip' | translate}} +
  • \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html index c2423363ba..3aa12bc2f0 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html @@ -9,5 +9,9 @@
  • {{:: 'attributes' | translate}}
  • {{:: 'role-mappings' | translate}}
  • {{:: 'members' | translate}}
  • +
  • + {{:: 'authz-permissions' | translate}} + {{:: 'manage-permissions-group.tooltip' | translate}} +
  • \ No newline at end of file From 94528976d404837efc3d5b727af0919fd5fe9c5b Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 7 Jun 2017 16:29:43 -0400 Subject: [PATCH 10/18] console work --- .../idm/ClientRepresentation.java | 9 + .../idm/GroupRepresentation.java | 9 + .../idm/UserRepresentation.java | 9 + .../migration/MigrationModelManager.java | 4 +- .../migration/migrators/MigrateTo3_2_0.java | 70 +++++++ .../java/org/keycloak/models/AdminRoles.java | 9 +- .../models/utils/ModelToRepresentation.java | 1 - .../services/managers/RealmManager.java | 19 +- .../resources/admin/ClientResource.java | 1 + .../resources/admin/ClientsResource.java | 5 +- .../resources/admin/GroupResource.java | 8 +- .../resources/admin/UserResource.java | 3 + .../resources/admin/UsersResource.java | 4 +- .../ClientPermissionEvaluator.java | 4 + .../admin/permissions/ClientPermissions.java | 9 + .../permissions/GroupPermissionEvaluator.java | 4 + .../GroupPermissionManagement.java | 4 +- .../admin/permissions/GroupPermissions.java | 28 ++- .../permissions/UserPermissionEvaluator.java | 4 + .../admin/permissions/UserPermissions.java | 10 +- .../admin/FineGrainAdminUnitTest.java | 36 +++- .../theme/base/admin/resources/js/app.js | 3 - .../admin/resources/js/controllers/clients.js | 4 +- .../admin/resources/js/controllers/realm.js | 184 ++++++++++-------- .../authz/mgmt/client-permissions.html | 2 +- .../authz/mgmt/client-role-permissions.html | 2 +- .../authz/mgmt/group-permissions.html | 2 +- .../partials/client-clustering-node.html | 6 +- .../resources/partials/client-clustering.html | 6 +- .../partials/client-credentials-generic.html | 4 +- .../client-credentials-jwt-key-export.html | 4 +- .../client-credentials-jwt-key-import.html | 2 +- .../partials/client-credentials-jwt.html | 4 +- .../partials/client-credentials-secret.html | 4 +- .../partials/client-credentials.html | 2 +- .../resources/partials/client-detail.html | 4 +- .../admin/resources/partials/client-keys.html | 4 +- .../admin/resources/partials/client-list.html | 2 +- .../partials/client-mappers-add.html | 2 +- .../resources/partials/client-mappers.html | 6 +- .../resources/partials/client-revocation.html | 4 +- .../partials/client-role-detail.html | 6 +- .../resources/partials/client-role-list.html | 4 +- .../partials/client-saml-key-export.html | 4 +- .../partials/client-saml-key-import.html | 2 +- .../resources/partials/client-saml-keys.html | 6 +- .../partials/client-scope-mappings.html | 4 +- .../client-service-account-roles.html | 4 +- .../resources/partials/user-attributes.html | 4 +- .../admin/resources/partials/user-detail.html | 8 +- .../admin/resources/partials/user-list.html | 2 +- .../resources/partials/user-sessions.html | 8 +- .../admin/resources/templates/kc-menu.html | 10 +- .../resources/templates/kc-tabs-client.html | 2 +- .../resources/templates/kc-tabs-user.html | 2 +- .../resources/templates/kc-tabs-users.html | 2 +- 56 files changed, 396 insertions(+), 173 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index cc9e54b7ea..a7ca83e1a6 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -65,6 +65,7 @@ public class ClientRepresentation { private Boolean useTemplateScope; private Boolean useTemplateMappers; private ResourceServerRepresentation authorizationSettings; + private Map access; public String getId() { @@ -366,4 +367,12 @@ public class ClientRepresentation { public void setAuthorizationSettings(ResourceServerRepresentation authorizationSettings) { this.authorizationSettings = authorizationSettings; } + + public Map getAccess() { + return access; + } + + public void setAccess(Map access) { + this.access = access; + } } diff --git a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java index 180db64677..9c96970970 100755 --- a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java @@ -34,6 +34,7 @@ public class GroupRepresentation { protected List realmRoles; protected Map> clientRoles; protected List subGroups; + private Map access; public String getId() { return id; @@ -97,4 +98,12 @@ public class GroupRepresentation { public void setSubGroups(List subGroups) { this.subGroups = subGroups; } + + public Map getAccess() { + return access; + } + + public void setAccess(Map access) { + this.access = access; + } } diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java index 2a4ec62836..4dcea953df 100755 --- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java @@ -62,6 +62,7 @@ public class UserRepresentation { protected List socialLinks; protected List groups; + private Map access; public String getSelf() { return self; @@ -264,4 +265,12 @@ public class UserRepresentation { public void setDisableableCredentialTypes(Set disableableCredentialTypes) { this.disableableCredentialTypes = disableableCredentialTypes; } + + public Map getAccess() { + return access; + } + + public void setAccess(Map access) { + this.access = access; + } } diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java index 1a36d7a853..4b620bc561 100755 --- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java +++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java @@ -34,6 +34,7 @@ import org.keycloak.migration.migrators.MigrateTo2_3_0; import org.keycloak.migration.migrators.MigrateTo2_5_0; import org.keycloak.migration.migrators.MigrateTo3_0_0; import org.keycloak.migration.migrators.MigrateTo3_1_0; +import org.keycloak.migration.migrators.MigrateTo3_2_0; import org.keycloak.migration.migrators.Migration; import org.keycloak.models.KeycloakSession; @@ -60,7 +61,8 @@ public class MigrationModelManager { new MigrateTo2_3_0(), new MigrateTo2_5_0(), new MigrateTo3_0_0(), - new MigrateTo3_1_0() + new MigrateTo3_1_0(), + new MigrateTo3_2_0() }; public static void migrate(KeycloakSession session) { diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java new file mode 100644 index 0000000000..0cb3eb6244 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java @@ -0,0 +1,70 @@ +/* + * 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.migration.migrators; + +import org.keycloak.migration.ModelVersion; +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; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class MigrateTo3_2_0 implements Migration { + public static final ModelVersion VERSION = new ModelVersion("3.1.0"); + + @Override + public void migrate(KeycloakSession session) { + for (RealmModel realm : session.realms().getRealms()) { + ClientModel realmAccess = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + if (realmAccess != null) { + addRoles(realmAccess); + } + ClientModel masterAdminClient = realm.getMasterAdminClient(); + if (masterAdminClient != null) { + addRoles(masterAdminClient); + + } + + } + } + + public void addRoles(ClientModel realmAccess) { + RoleModel queryClients = realmAccess.addRole(AdminRoles.QUERY_CLIENTS); + RoleModel queryUsers = realmAccess.addRole(AdminRoles.QUERY_USERS); + RoleModel queryGroups = realmAccess.addRole(AdminRoles.QUERY_GROUPS); + + RoleModel viewClients = realmAccess.getRole(AdminRoles.VIEW_CLIENTS); + if (viewClients != null) { + viewClients.addCompositeRole(queryClients); + } + RoleModel viewUsers = realmAccess.getRole(AdminRoles.VIEW_USERS); + if (viewUsers != null) { + viewUsers.addCompositeRole(queryUsers); + viewUsers.addCompositeRole(queryGroups); + } + } + + @Override + public ModelVersion getVersion() { + return VERSION; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java b/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java index 24455b8bfd..cf605d16c5 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java +++ b/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java @@ -17,6 +17,8 @@ package org.keycloak.models; +import java.util.Set; + /** * @author Stian Thorgersen */ @@ -46,6 +48,11 @@ public class AdminRoles { public static String MANAGE_EVENTS = "manage-events"; public static String MANAGE_AUTHORIZATION = "manage-authorization"; - public static String[] ALL_REALM_ROLES = {CREATE_CLIENT, VIEW_REALM, VIEW_USERS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, VIEW_AUTHORIZATION, MANAGE_REALM, MANAGE_USERS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS, MANAGE_AUTHORIZATION}; + public static String QUERY_USERS = "query-users"; + public static String QUERY_CLIENTS = "query-clients"; + public static String QUERY_REALMS = "query-realms"; + public static String QUERY_GROUPS = "query-groups"; + + public static String[] ALL_REALM_ROLES = {CREATE_CLIENT, VIEW_REALM, VIEW_USERS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, VIEW_AUTHORIZATION, MANAGE_REALM, MANAGE_USERS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS, MANAGE_AUTHORIZATION, QUERY_USERS, QUERY_CLIENTS, QUERY_REALMS, QUERY_GROUPS}; } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 6637bd8393..5ae590444e 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -168,7 +168,6 @@ public class ModelToRepresentation { return rep; } - public static UserRepresentation toRepresentation(KeycloakSession session, RealmModel realm, UserModel user) { UserRepresentation rep = new UserRepresentation(); rep.setId(user.getId()); diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index e94ff3c00f..21df21f91b 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -198,6 +198,18 @@ public class RealmManager { } } + public void addQueryCompositeRoles(ClientModel realmAccess) { + RoleModel queryClients = realmAccess.getRole(AdminRoles.QUERY_CLIENTS); + RoleModel queryUsers = realmAccess.getRole(AdminRoles.QUERY_USERS); + RoleModel queryGroups = realmAccess.getRole(AdminRoles.QUERY_GROUPS); + + RoleModel viewClients = realmAccess.getRole(AdminRoles.VIEW_CLIENTS); + viewClients.addCompositeRole(queryClients); + RoleModel viewUsers = realmAccess.getRole(AdminRoles.VIEW_USERS); + viewUsers.addCompositeRole(queryUsers); + viewUsers.addCompositeRole(queryGroups); + } + public String getRealmAdminClientId(RealmModel realm) { return Constants.REALM_MANAGEMENT_CLIENT_ID; @@ -327,6 +339,7 @@ public class RealmManager { role.setScopeParamRequired(false); adminRole.addCompositeRole(role); } + addQueryCompositeRoles(realmAdminApp); } private void checkMasterAdminManagementRoles(RealmModel realm) { @@ -340,6 +353,7 @@ public class RealmManager { addAndSetAdminRole(r, masterAdminClient, adminRole); } } + addQueryCompositeRoles(masterAdminClient); } @@ -362,6 +376,7 @@ public class RealmManager { for (String r : AdminRoles.ALL_REALM_ROLES) { addAndSetAdminRole(r, realmAdminClient, adminRole); } + addQueryCompositeRoles(realmAdminClient); } private void addAndSetAdminRole(String roleName, ClientModel parentClient, RoleModel parentRole) { @@ -385,6 +400,7 @@ public class RealmManager { addAndSetAdminRole(r, realmAdminClient, adminRole); } } + addQueryCompositeRoles(realmAdminClient); } @@ -502,7 +518,8 @@ public class RealmManager { // I need to postpone impersonation because it needs "realm-management" client and its roles set if (postponeImpersonationSetup) { setupImpersonationService(realm); - } + String realmAdminClientId = getRealmAdminClientId(realm); + } if (postponeAdminCliSetup) { setupAdminCli(realm); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 36362cb67b..32a86be828 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -195,6 +195,7 @@ public class ClientResource { if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) { representation.setAuthorizationServicesEnabled(authorization().isEnabled()); } + representation.setAccess(auth.clients().getAccess(client)); return representation; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index 41b91befdf..88b5fdddf1 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -114,6 +114,7 @@ public class ClientsResource { } rep.add(representation); + representation.setAccess(auth.clients().getAccess(clientModel)); } else if (!viewableOnly) { ClientRepresentation client = new ClientRepresentation(); client.setId(clientModel.getId()); @@ -126,7 +127,9 @@ public class ClientsResource { ClientModel clientModel = realm.getClientByClientId(clientId); if (clientModel != null) { if (auth.clients().canView(clientModel)) { - rep.add(ModelToRepresentation.toRepresentation(clientModel)); + ClientRepresentation representation = ModelToRepresentation.toRepresentation(clientModel); + representation.setAccess(auth.clients().getAccess(clientModel)); + rep.add(representation); } else if (!viewableOnly && auth.clients().canList()){ ClientRepresentation client = new ClientRepresentation(); client.setId(clientModel.getId()); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java index a5b7a6c77b..3de46b0e29 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java @@ -77,7 +77,7 @@ public class GroupResource { @Context private UriInfo uriInfo; - /** + /** * * * @return @@ -88,7 +88,11 @@ public class GroupResource { public GroupRepresentation getGroup() { this.auth.groups().requireView(group); - return ModelToRepresentation.toGroupHierarchy(group, true); + GroupRepresentation rep = ModelToRepresentation.toGroupHierarchy(group, true); + + rep.setAccess(auth.groups().getAccess(group)); + + return rep; } /** diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index 44e4892143..ca9d53640f 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -249,6 +249,7 @@ public class UserResource { if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) { rep.setEnabled(false); } + rep.setAccess(auth.users().getAccess(user)); return rep; } @@ -761,6 +762,7 @@ public class UserResource { if (group == null) { throw new NotFoundException("Group not found"); } + auth.groups().requireManageMembers(group); try { if (user.isMemberOf(group)){ @@ -783,6 +785,7 @@ public class UserResource { if (group == null) { throw new NotFoundException("Group not found"); } + //auth.groups().requireManageMembers(group); if (!user.isMemberOf(group)){ user.joinGroup(group); adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index fc4bfee770..6ed677f92b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -259,7 +259,9 @@ public class UsersResource { boolean canViewGlobal = auth.users().canView(); for (UserModel user : userModels) { if (!canViewGlobal && !auth.users().canView(user)) continue; - results.add(ModelToRepresentation.toRepresentation(session, realm, user)); + UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, realm, user); + userRep.setAccess(auth.users().getAccess(user)); + results.add(userRep); } return results; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java index 51e26c05da..25da468837 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java @@ -19,6 +19,8 @@ package org.keycloak.services.resources.admin.permissions; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientTemplateModel; +import java.util.Map; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -73,4 +75,6 @@ public interface ClientPermissionEvaluator { boolean canMapCompositeRoles(ClientModel client); boolean canMapClientScopeRoles(ClientModel client); + + Map getAccess(ClientModel client); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java index 06fa5d822e..39f7b4d6c6 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java @@ -492,4 +492,13 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return root.evaluatePermission(resource, scope, server); } + @Override + public Map getAccess(ClientModel client) { + Map map = new HashMap<>(); + map.put("view", canView(client)); + map.put("manage", canManage(client)); + return map; + } + + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java index 91ca5c5e9d..450b0746d8 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java @@ -18,6 +18,8 @@ package org.keycloak.services.resources.admin.permissions; import org.keycloak.models.GroupModel; +import java.util.Map; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -50,4 +52,6 @@ public interface GroupPermissionEvaluator { boolean canManageMembers(GroupModel group); void requireManageMembers(GroupModel group); + + Map getAccess(GroupModel group); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java index a3277ba316..5fcfbbbb16 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionManagement.java @@ -33,8 +33,8 @@ public interface GroupPermissionManagement { Policy viewMembersPermission(GroupModel group); Policy manageMembersPermission(GroupModel group); - Policy viewPermissionGroup(GroupModel group); - Policy managePermissionGroup(GroupModel group); + Policy viewPermission(GroupModel group); + Policy managePermission(GroupModel group); Resource resource(GroupModel group); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java index acf80c13d1..222380229d 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java @@ -23,7 +23,6 @@ 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.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -125,7 +124,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag @Override public boolean canList() { - return root.hasOneAdminRole(AdminRoles.VIEW_USERS, AdminRoles.MANAGE_USERS); + return root.hasOneAdminRole(AdminRoles.VIEW_USERS, AdminRoles.MANAGE_USERS, AdminRoles.QUERY_GROUPS); } @Override @@ -164,11 +163,11 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag private void deletePermissions(GroupModel group) { ResourceServer server = root.realmResourceServer(); if (server == null) return; - Policy managePermission = managePermissionGroup(group); + Policy managePermission = managePermission(group); if (managePermission != null) { authz.getStoreFactory().getPolicyStore().delete(managePermission.getId()); } - Policy viewPermission = viewPermissionGroup(group); + Policy viewPermission = viewPermission(group); if (viewPermission != null) { authz.getStoreFactory().getPolicyStore().delete(viewPermission.getId()); } @@ -201,7 +200,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag } @Override - public Policy viewPermissionGroup(GroupModel group) { + public Policy viewPermission(GroupModel group) { ResourceServer server = root.realmResourceServer(); if (server == null) return null; String viewPermissionName = getViewPermissionGroup(group); @@ -209,7 +208,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag } @Override - public Policy managePermissionGroup(GroupModel group) { + public Policy managePermission(GroupModel group) { ResourceServer server = root.realmResourceServer(); if (server == null) return null; String managePermissionName = getManagePermissionGroup(group); @@ -228,8 +227,8 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag @Override public Map getPermissions(GroupModel group) { Map scopes = new HashMap<>(); - scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermissionGroup(group).getId()); - scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermissionGroup(group).getId()); + scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(group).getId()); + scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(group).getId()); scopes.put(MANAGE_MEMBERS_SCOPE, manageMembersPermission(group).getId()); scopes.put(VIEW_MEMBERS_SCOPE, viewMembersPermission(group).getId()); return scopes; @@ -250,7 +249,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); if (resource == null) return canManage(); - Policy policy = managePermissionGroup(group); + Policy policy = managePermission(group); if (policy == null) { return canManage(); } @@ -283,7 +282,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); if (resource == null) return canView(); - Policy policy = viewPermissionGroup(group); + Policy policy = viewPermission(group); if (policy == null) { return canView(); } @@ -405,6 +404,15 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag } } + @Override + public Map getAccess(GroupModel group) { + Map map = new HashMap<>(); + map.put("view", canView(group)); + map.put("manage", canManage(group)); + return map; + } + + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java index e276ae29df..05ce719862 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java @@ -18,6 +18,8 @@ package org.keycloak.services.resources.admin.permissions; import org.keycloak.models.UserModel; +import java.util.Map; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -47,4 +49,6 @@ public interface UserPermissionEvaluator { boolean canImpersonate(UserModel user); void requireImpersonate(UserModel user); + + Map getAccess(UserModel user); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java index b095592766..f8dd53b952 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -281,7 +281,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme @Override public boolean canQuery() { - return canView(); + return canView() || root.hasOneAdminRole(AdminRoles.QUERY_USERS); } @Override @@ -392,6 +392,14 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme } } + @Override + public Map getAccess(UserModel user) { + Map map = new HashMap<>(); + map.put("view", canView(user)); + map.put("manage", canManage(user)); + return map; + } + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 72f3df93af..d32ad794e7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -56,6 +56,8 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; //@Ignore public class FineGrainAdminUnitTest extends AbstractKeycloakTest { + public static final String CLIENT_NAME = "application"; + @Override public void addTestRealms(List testRealms) { RealmRepresentation testRealmRep = new RealmRepresentation(); @@ -70,7 +72,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { AdminPermissionManagement permissions = AdminPermissions.management(session, realm); RoleModel realmRole = realm.addRole("realm-role"); RoleModel realmRole2 = realm.addRole("realm-role2"); - ClientModel client1 = realm.addClient("role-namespace"); + ClientModel client1 = realm.addClient(CLIENT_NAME); RoleModel client1Role = client1.addRole("client-role"); GroupModel group = realm.createGroup("top"); @@ -80,7 +82,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { compositeRole.addCompositeRole(mapperRole); compositeRole.addCompositeRole(managerRole); - // realm-role and role-namespace.client-role will have a role policy associated with their map-role permission + // realm-role and application.client-role will have a role policy associated with their map-role permission { permissions.roles().setPermissionsEnabled(client1Role, true); Policy mapRolePermission = permissions.roles().mapRolePermission(client1Role); @@ -122,7 +124,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { public static void setupUsers(KeycloakSession session) { RealmModel realm = session.realms().getRealmByName(TEST); - ClientModel client = realm.getClientByClientId("role-namespace"); + ClientModel client = realm.getClientByClientId(CLIENT_NAME); RoleModel realmRole = realm.getRole("realm-role"); RoleModel realmRole2 = realm.getRole("realm-role2"); RoleModel clientRole = client.getRole("client-role"); @@ -131,6 +133,9 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleModel compositeRole = realm.getRole("composite-role"); ClientModel realmManagementClient = realm.getClientByClientId("realm-management"); RoleModel adminRole = realmManagementClient.getRole(AdminRoles.REALM_ADMIN); + RoleModel queryGroupsRole = realmManagementClient.getRole(AdminRoles.QUERY_GROUPS); + RoleModel queryUsersRole = realmManagementClient.getRole(AdminRoles.QUERY_USERS); + RoleModel queryClientsRole = realmManagementClient.getRole(AdminRoles.QUERY_CLIENTS); UserModel nomapAdmin = session.users().addUser(realm, "nomap-admin"); nomapAdmin.setEnabled(true); @@ -168,6 +173,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { groupMember.joinGroup(group); groupMember.setEnabled(true); UserModel groupManager = session.users().addUser(realm, "groupManager"); + groupManager.grantRole(queryGroupsRole); + groupManager.grantRole(queryUsersRole); groupManager.setEnabled(true); groupManager.grantRole(mapperRole); session.userCredentialManager().updateCredential(realm, groupManager, UserCredentialModel.password("password")); @@ -175,6 +182,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { UserModel groupManagerNoMapper = session.users().addUser(realm, "noMapperGroupManager"); groupManagerNoMapper.setEnabled(true); session.userCredentialManager().updateCredential(realm, groupManagerNoMapper, UserCredentialModel.password("password")); + groupManagerNoMapper.grantRole(queryGroupsRole); + groupManagerNoMapper.grantRole(queryUsersRole); UserPolicyRepresentation groupManagerRep = new UserPolicyRepresentation(); groupManagerRep.setName("groupManagers"); @@ -184,10 +193,12 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { Policy groupManagerPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(groupManagerRep, server); Policy groupManagerPermission = permissions.groups().manageMembersPermission(group); groupManagerPermission.addAssociatedPolicy(groupManagerPolicy); + permissions.groups().viewPermission(group).addAssociatedPolicy(groupManagerPolicy); UserModel clientMapper = session.users().addUser(realm, "clientMapper"); clientMapper.setEnabled(true); clientMapper.grantRole(managerRole); + clientMapper.grantRole(queryUsersRole); session.userCredentialManager().updateCredential(realm, clientMapper, UserCredentialModel.password("password")); Policy clientMapperPolicy = permissions.clients().mapRolesPermission(client); UserPolicyRepresentation userRep = new UserPolicyRepresentation(); @@ -196,6 +207,19 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { Policy userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client)); clientMapperPolicy.addAssociatedPolicy(userPolicy); + UserModel clientManager = session.users().addUser(realm, "clientManager"); + clientManager.setEnabled(true); + clientManager.grantRole(queryClientsRole); + session.userCredentialManager().updateCredential(realm, clientManager, UserCredentialModel.password("password")); + + Policy clientManagerPolicy = permissions.clients().managePermission(client); + userRep = new UserPolicyRepresentation(); + userRep.setName("clientManager"); + userRep.addUser("clientManager"); + userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client)); + clientManagerPolicy.addAssociatedPolicy(userPolicy); + + @@ -206,7 +230,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RealmModel realm = session.realms().getRealmByName(TEST); RoleModel realmRole = realm.getRole("realm-role"); RoleModel realmRole2 = realm.getRole("realm-role2"); - ClientModel client = realm.getClientByClientId("role-namespace"); + ClientModel client = realm.getClientByClientId(CLIENT_NAME); RoleModel clientRole = client.getRole("client-role"); // test authorized @@ -309,7 +333,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleRepresentation realmRole2 = adminClient.realm(TEST).roles().get("realm-role2").toRepresentation(); List realmRole2Set = new LinkedList<>(); realmRole2Set.add(realmRole2); - ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId("role-namespace").get(0); + ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId(CLIENT_NAME).get(0); RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation(); List clientRoleSet = new LinkedList<>(); clientRoleSet.add(clientRole); @@ -463,7 +487,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleRepresentation realmRole2 = adminClient.realm(TEST).roles().get("realm-role2").toRepresentation(); List realmRole2Set = new LinkedList<>(); realmRole2Set.add(realmRole); - ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId("role-namespace").get(0); + ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId(CLIENT_NAME).get(0); RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation(); List clientRoleSet = new LinkedList<>(); clientRoleSet.add(clientRole); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 2864559796..c4e870fdac 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -1398,9 +1398,6 @@ module.config([ '$routeProvider', function($routeProvider) { realm : function(RealmLoader) { return RealmLoader(); }, - clients : function(ClientListLoader) { - return ClientListLoader(); - }, serverInfo : function(ServerInfoLoader) { return ServerInfoLoader(); } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 945fc5a699..515eb99720 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -728,9 +728,9 @@ module.controller('ClientImportCtrl', function($scope, $location, $upload, realm }); -module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications, filterFilter) { +module.controller('ClientListCtrl', function($scope, realm, Client, serverInfo, $route, Dialog, Notifications, filterFilter) { $scope.realm = realm; - $scope.clients = clients; + $scope.clients = Client.query({realm: realm.realm, viewableOnly: true}); $scope.currentPage = 1; $scope.currentPageInput = 1; $scope.pageSize = 20; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index e3d3070cbb..ab4e72a878 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -1,81 +1,95 @@ +function getAccess(Auth, Current, role) { + if (!Current.realm)return false; + var realmAccess = Auth.user && Auth.user['realm_access']; + if (realmAccess) { + realmAccess = realmAccess[Current.realm.realm]; + if (realmAccess) { + return realmAccess.indexOf(role) >= 0; + } + } + return false; +} + +function getAccessObject(Auth, Current) { + return { + get createRealm() { + return Auth.user && Auth.user.createRealm; + }, + + get queryUsers() { + return getAccess(Auth, Current, 'query-users') || this.viewUsers; + }, + + get queryGroups() { + return getAccess(Auth, Current, 'query-groups') || this.viewUsers; + }, + + get queryClients() { + return getAccess(Auth, Current, 'query-clients') || this.viewClients; + }, + + get viewRealm() { + return getAccess(Auth, Current, 'view-realm') || getAccess(Auth, Current, 'manage-realm') || this.manageRealm; + }, + + get viewClients() { + return getAccess(Auth, Current, 'view-clients') || getAccess(Auth, Current, 'manage-clients') || this.manageClients; + }, + + get viewUsers() { + return getAccess(Auth, Current, 'view-users') || getAccess(Auth, Current, 'manage-users') || this.manageClients; + }, + + get viewEvents() { + return getAccess(Auth, Current, 'view-events') || getAccess(Auth, Current, 'manage-events') || this.manageClients; + }, + + get viewIdentityProviders() { + return getAccess(Auth, Current, 'view-identity-providers') || getAccess(Auth, Current, 'manage-identity-providers') || this.manageIdentityProviders; + }, + + get viewAuthorization() { + return getAccess(Auth, Current, 'view-authorization') || this.manageAuthorization; + }, + + get manageRealm() { + return getAccess(Auth, Current, 'manage-realm'); + }, + + get manageClients() { + return getAccess(Auth, Current, 'manage-clients'); + }, + + get manageUsers() { + return getAccess(Auth, Current, 'manage-users'); + }, + + get manageEvents() { + return getAccess(Auth, Current, 'manage-events'); + }, + + get manageIdentityProviders() { + return getAccess(Auth, Current, 'manage-identity-providers'); + }, + + get manageAuthorization() { + return getAccess(Auth, Current, 'manage-authorization'); + }, + + get impersonation() { + return getAccess(Auth, Current, 'impersonation'); + } + }; +} + + module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location, Notifications, ServerInfo) { $scope.authUrl = authUrl; $scope.resourceUrl = resourceUrl; $scope.auth = Auth; $scope.serverInfo = ServerInfo.get(); - function getAccess(role) { - if (!Current.realm) { - return false; - } - - var realmAccess = Auth.user && Auth.user['realm_access']; - if (realmAccess) { - realmAccess = realmAccess[Current.realm.realm]; - if (realmAccess) { - return realmAccess.indexOf(role) >= 0; - } - } - return false; - } - - $scope.access = { - get createRealm() { - return Auth.user && Auth.user.createRealm; - }, - - get viewRealm() { - return getAccess('view-realm') || getAccess('manage-realm') || this.manageRealm; - }, - - get viewClients() { - return getAccess('view-clients') || getAccess('manage-clients') || this.manageClients; - }, - - get viewUsers() { - return getAccess('view-users') || getAccess('manage-users') || this.manageClients; - }, - - get viewEvents() { - return getAccess('view-events') || getAccess('manage-events') || this.manageClients; - }, - - get viewIdentityProviders() { - return getAccess('view-identity-providers') || getAccess('manage-identity-providers') || this.manageIdentityProviders; - }, - - get viewAuthorization() { - return getAccess('view-authorization') || this.manageAuthorization; - }, - - get manageRealm() { - return getAccess('manage-realm'); - }, - - get manageClients() { - return getAccess('manage-clients'); - }, - - get manageUsers() { - return getAccess('manage-users'); - }, - - get manageEvents() { - return getAccess('manage-events'); - }, - - get manageIdentityProviders() { - return getAccess('manage-identity-providers'); - }, - - get manageAuthorization() { - return getAccess('manage-authorization'); - }, - - get impersonation() { - return getAccess('impersonation'); - } - }; + $scope.access = getAccessObject(Auth, Current); $scope.$watch(function() { return $location.path(); @@ -85,20 +99,36 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location }); }); -module.controller('HomeCtrl', function(Realm, Auth, $location) { +module.controller('HomeCtrl', function(Realm, Auth, Current, $location) { + Realm.query(null, function(realms) { var realm; if (realms.length == 1) { - realm = realms[0].realm; + realm = realms[0]; } else if (realms.length == 2) { if (realms[0].realm == Auth.user.realm) { - realm = realms[1].realm; + realm = realms[1]; } else if (realms[1].realm == Auth.user.realm) { - realm = realms[0].realm; + realm = realms[0]; } } if (realm) { - $location.url('/realms/' + realm); + Current.realms = realms; + Current.realm = realm; + var access = getAccessObject(Auth, Current); + if (access.viewRealm || access.manageRealm) { + $location.url('/realms/' + realm.realm ); + } else if (access.queryClients) { + $location.url('/realms/' + realm.realm + "/clients"); + } else if (access.viewIdentityProviders) { + $location.url('/realms/' + realm.realm + "/identity-provider-settings"); + } else if (access.queryUsers) { + $location.url('/realms/' + realm.realm + "/users"); + } else if (access.queryGroups) { + $location.url('/realms/' + realm.realm + "/groups"); + } else if (access.viewEvents) { + $location.url('/realms/' + realm.realm + "/events"); + } } else { $location.url('/realms'); } diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html index 50241b2139..bb6d7b6d58 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html @@ -6,7 +6,7 @@ -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html index 988c1fa384..5b423d06f9 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html @@ -7,7 +7,7 @@ - +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html index 61770c91cf..897a0ed36d 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/group-permissions.html @@ -6,7 +6,7 @@ - +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html index 440994ff8b..98820eb75c 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html @@ -10,10 +10,10 @@

    {{:: 'add-node' | translate}}

    {{node.host|capitalize}} - +

    - +
    @@ -27,7 +27,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html index 4a6312b4d7..e729582dae 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html @@ -7,7 +7,7 @@ - + {{:: 'basic-configuration' | translate}}
    @@ -31,7 +31,7 @@
    -
    +
    @@ -43,7 +43,7 @@ - - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html index 04c2339238..653777924d 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html @@ -45,7 +45,7 @@
    +
    {{:: 'register-node-manually' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html index 1d59078d1a..a2d59e1fd0 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html @@ -1,11 +1,11 @@
    - +
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html index 382f612f29..8fbd0792e0 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html @@ -9,7 +9,7 @@

    {{:: 'generate-private-key' | translate}}

    - + @@ -48,7 +48,7 @@ {{:: 'store-password.tooltip' | translate}}
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html index 9968a316f8..f96c4f0c3f 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html @@ -9,7 +9,7 @@

    {{:: 'import-client-certificate' | translate}}

    - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html index 29f652455a..8df035ce57 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html @@ -1,4 +1,4 @@ -
    +
    @@ -63,7 +63,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html index 744ea80dac..d4fb071683 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html @@ -1,5 +1,5 @@
    - +
    @@ -7,7 +7,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html index b1b1062dfb..387177491d 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html @@ -7,7 +7,7 @@ - +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index 3eb084f44d..5f24b21d82 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -7,7 +7,7 @@ - +
    @@ -391,7 +391,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html index e2bc22ef6d..e8d6259b40 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html @@ -7,7 +7,7 @@ - + @@ -137,7 +137,7 @@ kc-select-action="click" readonly>{{keyInfo.certificate}}
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html index 51aaa2009b..03ebf5cc39 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html @@ -42,7 +42,7 @@
    {{:: 'edit' | translate}} {{:: 'export' | translate}}{{:: 'delete' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-results' | translate}}
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html index ba9b9bf5cc..5648c19bf8 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html @@ -7,7 +7,7 @@ - +
    @@ -35,7 +35,7 @@
    -
    + @@ -55,7 +55,7 @@ {{mapperTypes[mapper.protocolMapper].category}} {{mapperTypes[mapper.protocolMapper].name}} {{:: 'edit' | translate}} - {{:: 'delete' | translate}} + {{:: 'delete' | translate}} {{:: 'no-mappers-available' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html index 38560e825f..761b3374eb 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html @@ -7,7 +7,7 @@ - +
    @@ -18,7 +18,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html index 9c3e215a1a..d21d7ef593 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html @@ -10,7 +10,7 @@ - +
    @@ -47,13 +47,13 @@
    -
    +
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html index da4d49d482..458b412a5d 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html @@ -10,7 +10,7 @@ - - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html index 21651656ad..753f564bd1 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html @@ -9,7 +9,7 @@

    {{:: 'export-saml-key' | translate}} {{client.clientId|capitalize}}

    - + @@ -55,7 +55,7 @@ {{:: 'store-password.tooltip' | translate}}
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html index dec13a8c4c..b479058d66 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html @@ -9,7 +9,7 @@

    {{:: 'import-saml-key' | translate}} {{client.clientId|capitalize}}

    - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html index 52e54686b6..c5374bb654 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html @@ -7,7 +7,7 @@ - +
    {{:: 'signing-key' | translate}} {{:: 'saml-signing-key' | translate}}
    @@ -27,7 +27,7 @@
    -
    +
    @@ -53,7 +53,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html index fc10e6bf66..482ef5b77f 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html @@ -9,7 +9,7 @@

    {{client.clientId}} {{:: 'scope-mappings' | translate}}

    - +
    @@ -41,7 +41,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html index 27f967d408..24b2594841 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html @@ -10,7 +10,7 @@

    {{client.clientId}} {{:: 'service-accounts' | translate}}

    - +
    @@ -102,7 +102,7 @@
    -
    + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html index 26d30e1174..10e3d8fcfe 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html @@ -6,7 +6,7 @@ -
    +
    + @@ -29,7 +29,7 @@ {{role.description}} {{:: 'edit' | translate}}{{:: 'delete' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-client-roles-available' | translate}}
    @@ -29,7 +29,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html index f1998f8c3f..d7259fda8e 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html @@ -7,7 +7,7 @@ - +
    @@ -62,7 +62,7 @@
    - +
    {{:: 'user-enabled.tooltip' | translate}}
    @@ -93,7 +93,7 @@
    - +
    {{:: 'email-verified.tooltip' | translate}}
    @@ -138,7 +138,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html index f1dd03b839..569e1fc8d4 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html @@ -55,7 +55,7 @@ {{user.firstName}} {{:: 'edit' | translate}} {{:: 'impersonate' | translate}} - {{:: 'delete' | translate}} + {{:: 'delete' | translate}} {{:: 'users.instruction' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html index 1fe11e933c..02c195940c 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html @@ -8,9 +8,9 @@ - + @@ -20,7 +20,7 @@ - + @@ -34,7 +34,7 @@ - +
    - {{:: 'started' | translate}} {{:: 'last-access' | translate}} {{:: 'clients' | translate}}{{:: 'action' | translate}}{{:: 'action' | translate}}
    {{:: 'logout' | translate}}{{:: 'logout' | translate}}
    diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html index 25d22d7a44..53b0a3d5e7 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html @@ -16,7 +16,7 @@
    -
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html index 9a1c5b814a..62aa7d43dd 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html @@ -7,7 +7,7 @@ -
    +
    @@ -54,7 +54,7 @@ {{:: 'client-roles' | translate}} -
    +
    {{:: 'select-client-to-view-roles' | translate}}
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html index b57b1d2973..013dae72a2 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html @@ -7,7 +7,7 @@ -
    +
    @@ -54,7 +54,7 @@ {{:: 'client-roles' | translate}} -
    +
    {{:: 'select-client-to-view-roles' | translate}}
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html index d7259fda8e..88e66e7438 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html @@ -138,7 +138,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html index a9c0e6daa0..f7bf04e458 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html @@ -7,7 +7,7 @@ -
    +
    @@ -21,7 +21,7 @@ {{:: 'group-membership.tooltip' | translate}} -
    +
    @@ -53,7 +53,7 @@ {{:: 'membership.available-groups.tooltip' | translate}} -
    +
    From 57cb46148f4458e28aa3e932399490d6c9e39ec5 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 19 Jun 2017 11:21:59 -0400 Subject: [PATCH 12/18] tests --- .../services/resources/admin/AdminRoot.java | 24 +++---------------- .../admin/permissions/MgmtPermissions.java | 16 +++++++++++++ .../RealmsPermissionEvaluator.java | 2 ++ .../keycloak/testsuite/util/TestCleanup.java | 1 + .../testsuite/AbstractKeycloakTest.java | 2 ++ 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java index 5db1ea4475..650ac75d49 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java @@ -38,6 +38,8 @@ import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.admin.info.ServerInfoAdminResource; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; +import org.keycloak.services.resources.admin.permissions.RealmsPermissionEvaluator; import org.keycloak.theme.Theme; import org.keycloak.theme.ThemeProvider; @@ -229,7 +231,7 @@ public class AdminRoot { handlePreflightRequest(); AdminAuth auth = authenticateRealmAdminRequest(headers); - if (!isAdmin(auth)) { + if (!AdminPermissions.realms(session, auth).isAdmin()) { throw new ForbiddenException(); } @@ -244,26 +246,6 @@ public class AdminRoot { return adminResource; } - protected boolean isAdmin(AdminAuth auth) { - - RealmManager realmManager = new RealmManager(session); - if (auth.getRealm().equals(realmManager.getKeycloakAdminstrationRealm())) { - if (auth.hasOneOfRealmRole(AdminRoles.ADMIN, AdminRoles.CREATE_REALM)) { - return true; - } - for (RealmModel realm : session.realms().getRealms()) { - ClientModel client = realm.getMasterAdminClient(); - if (auth.hasOneOfAppRole(client, AdminRoles.ALL_REALM_ROLES)) { - return true; - } - } - return false; - } else { - ClientModel client = auth.getRealm().getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm())); - return auth.hasOneOfAppRole(client, AdminRoles.ALL_REALM_ROLES); - } - } - protected void handlePreflightRequest() { if (request.getHttpMethod().equalsIgnoreCase("OPTIONS")) { logger.debug("Cors admin pre-flight"); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java index 8c9e584514..94fa957b1d 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java @@ -317,6 +317,22 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage return hasAnyAdminRole(realm); } + @Override + public boolean isAdmin() { + RealmManager realmManager = new RealmManager(session); + if (adminsRealm.equals(realmManager.getKeycloakAdminstrationRealm())) { + if (identity.hasRealmRole(AdminRoles.ADMIN) || identity.hasRealmRole(AdminRoles.CREATE_REALM)) { + return true; + } + for (RealmModel realm : session.realms().getRealms()) { + if (isAdmin(realm)) return true; + } + return false; + } else { + return isAdmin(adminsRealm); + } + } + @Override public boolean canCreateRealm() { RealmManager realmManager = new RealmManager(session); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmsPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmsPermissionEvaluator.java index b58202f189..5286d103d1 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmsPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmsPermissionEvaluator.java @@ -27,6 +27,8 @@ public interface RealmsPermissionEvaluator { boolean isAdmin(RealmModel realm); + boolean isAdmin(); + boolean canCreateRealm(); void requireCreateRealm(); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java index 17ff44a9c6..e20485c37a 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java @@ -116,6 +116,7 @@ public class TestCleanup { public void executeCleanup() { + if (adminClient == null) throw new RuntimeException("ADMIN CLIENT NULL"); RealmResource realm = adminClient.realm(realmName); if (userIds != null) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index d6d2ad8e15..262d0b27b5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -167,6 +167,7 @@ public abstract class AbstractKeycloakTest { removeRealm(testRealm.getRealm()); } } else { + log.info("calling all TestCleanup"); // Logout all users after the test List realms = testContext.getTestRealmReps(); for (RealmRepresentation realm : realms) { @@ -178,6 +179,7 @@ public abstract class AbstractKeycloakTest { try { if (cleanup != null) cleanup.executeCleanup(); } catch (Exception e) { + log.error("failed cleanup!", e); throw new RuntimeException(e); } } From 39dea4b07813acf6f13a14aa4f2db230ae6d899d Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 22 Jun 2017 16:51:46 -0400 Subject: [PATCH 13/18] restricting admin role mapping --- .../java/org/keycloak/models/AdminRoles.java | 11 + .../admin/permissions/RolePermissions.java | 135 +++- .../permissions/UserPermissionEvaluator.java | 2 + .../admin/permissions/UserPermissions.java | 7 +- .../admin/IllegalAdminUpgradeTest.java | 648 ++++++++++++++++++ 5 files changed, 800 insertions(+), 3 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IllegalAdminUpgradeTest.java diff --git a/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java b/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java index cf605d16c5..c2304e8572 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java +++ b/server-spi-private/src/main/java/org/keycloak/models/AdminRoles.java @@ -17,6 +17,7 @@ package org.keycloak.models; +import java.util.HashSet; import java.util.Set; /** @@ -55,4 +56,14 @@ public class AdminRoles { public static String[] ALL_REALM_ROLES = {CREATE_CLIENT, VIEW_REALM, VIEW_USERS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, VIEW_AUTHORIZATION, MANAGE_REALM, MANAGE_USERS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS, MANAGE_AUTHORIZATION, QUERY_USERS, QUERY_CLIENTS, QUERY_REALMS, QUERY_GROUPS}; + public static Set ALL_ROLES = new HashSet<>(); + static { + for (String name : ALL_REALM_ROLES) { + ALL_ROLES.add(name); + } + ALL_ROLES.add(ImpersonationConstants.IMPERSONATION_ROLE); + ALL_ROLES.add(ADMIN); + ALL_ROLES.add(CREATE_REALM); + ALL_ROLES.add(CREATE_CLIENT); + } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java index e01f4eb65f..d220a3908a 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java @@ -17,6 +17,7 @@ package org.keycloak.services.resources.admin.permissions; import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; @@ -25,6 +26,7 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; +import org.keycloak.models.ImpersonationConstants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; @@ -129,6 +131,131 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme return root.resourceServer(client); } + private boolean checkAdminRoles(RoleModel role) { + if (AdminRoles.ALL_ROLES.contains(role.getName())) { + if (root.admin().hasRole(role)) return true; + + ClientModel adminClient = root.getRealmManagementClient(); + if (adminClient.equals(role.getContainer())) { + // if this is realm admin role, then check to see if admin has similar permissions + // we do this so that the authz service is invoked + if (role.getName().equals(AdminRoles.MANAGE_CLIENTS)) { + if (!root.clients().canManage()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.VIEW_CLIENTS)) { + if (!root.clients().canView()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.QUERY_CLIENTS)) { + return true; + } else if (role.getName().equals(AdminRoles.QUERY_USERS)) { + return true; + } else if (role.getName().equals(AdminRoles.QUERY_GROUPS)) { + return true; + } else if (role.getName().equals(AdminRoles.MANAGE_AUTHORIZATION)) { + if (!root.realm().canManageAuthorization()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.VIEW_AUTHORIZATION)) { + if (!root.realm().canViewAuthorization()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.MANAGE_EVENTS)) { + if (!root.realm().canManageEvents()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.VIEW_EVENTS)) { + if (!root.realm().canViewEvents()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.MANAGE_USERS)) { + if (!root.users().canManage()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.VIEW_USERS)) { + if (!root.users().canView()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.MANAGE_IDENTITY_PROVIDERS)) { + if (!root.realm().canManageIdentityProviders()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.VIEW_IDENTITY_PROVIDERS)) { + if (!root.realm().canViewIdentityProviders()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.MANAGE_REALM)) { + if (!root.realm().canManageRealm()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(AdminRoles.VIEW_REALM)) { + if (!root.realm().canViewRealm()) { + return adminConflictMessage(role); + } else { + return true; + } + } else if (role.getName().equals(ImpersonationConstants.IMPERSONATION_ROLE)) { + if (!root.users().canImpersonate()) { + return adminConflictMessage(role); + } else { + return true; + } + } else { + return adminConflictMessage(role); + } + + } else { + // now we need to check to see if this is a master admin role + if (role.getContainer() instanceof RealmModel) { + RealmModel realm = (RealmModel)role.getContainer(); + // If realm role is master admin role then abort + if (realm.getName().equals(Config.getAdminRealm())) { + return adminConflictMessage(role); + } + } else { + ClientModel container = (ClientModel)role.getContainer(); + // abort if this is an role in master realm and role is an admin role of any realm + if (container.getRealm().getName().equals(Config.getAdminRealm()) + && container.getClientId().endsWith("-realm")) { + return adminConflictMessage(role); + } + } + return true; + } + + } + return true; + + } + + private boolean adminConflictMessage(RoleModel role) { + logger.debug("Trying to assign admin privileges of role: " + role.getName() + " but admin doesn't have same privilege"); + return false; + } + /** * Is admin allowed to map this role? * @@ -137,7 +264,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme */ @Override public boolean canMapRole(RoleModel role) { - if (root.users().canManageDefault()) return true; + if (root.users().canManageDefault()) return checkAdminRoles(role); if (!root.isAdminSameRealm()) { return false; } @@ -159,7 +286,11 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme Resource roleResource = resource(role); Scope mapRoleScope = mapRoleScope(resourceServer); - return root.evaluatePermission(roleResource, mapRoleScope, resourceServer); + if (root.evaluatePermission(roleResource, mapRoleScope, resourceServer)) { + return checkAdminRoles(role); + } else { + return true; + } } @Override diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java index 74163d9145..40b1f8f44f 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java @@ -49,6 +49,8 @@ public interface UserPermissionEvaluator { boolean canImpersonate(UserModel user); + boolean canImpersonate(); + void requireImpersonate(UserModel user); Map getAccess(UserModel user); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java index 13f8aca4a6..46a9d0161c 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -451,6 +451,12 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme @Override public boolean canImpersonate(UserModel user) { + return canImpersonate(); + + } + + @Override + public boolean canImpersonate() { if (root.hasOneAdminRole(ImpersonationConstants.IMPERSONATION_ROLE)) return true; if (!root.isAdminSameRealm()) { @@ -476,7 +482,6 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme Scope scope = root.realmScope(IMPERSONATE_SCOPE); return root.evaluatePermission(resource, scope, server); - } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IllegalAdminUpgradeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IllegalAdminUpgradeTest.java new file mode 100644 index 0000000000..7c6ced5e29 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IllegalAdminUpgradeTest.java @@ -0,0 +1,648 @@ +/* + * 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.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.models.GroupModel; +import org.keycloak.models.ImpersonationConstants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; +import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; +import org.keycloak.services.resources.admin.permissions.AdminPermissions; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.util.AdminClientUtil; + +import javax.ws.rs.ClientErrorException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class IllegalAdminUpgradeTest extends AbstractKeycloakTest { + + public static final String CLIENT_NAME = "application"; + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation testRealmRep = new RealmRepresentation(); + testRealmRep.setId(TEST); + testRealmRep.setRealm(TEST); + testRealmRep.setEnabled(true); + testRealms.add(testRealmRep); + } + + protected boolean isImportAfterEachMethod() { + return true; + } + + + public static void setupUsers(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName(TEST); + RealmModel master = session.realms().getRealmByName("master"); + ClientModel realmAdminClient = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + ClientModel realmMasterAdminClient = realm.getMasterAdminClient(); + RoleModel realmManageUsers = realmAdminClient.getRole(AdminRoles.MANAGE_USERS); + RoleModel masterManageUsers = realmMasterAdminClient.getRole(AdminRoles.MANAGE_USERS); + RoleModel masterMasterManageUSers = master.getMasterAdminClient().getRole(AdminRoles.MANAGE_USERS); + + UserModel realmUser = session.users().addUser(realm, "userAdmin"); + realmUser.grantRole(realmManageUsers); + realmUser.setEnabled(true); + session.userCredentialManager().updateCredential(realm, realmUser, UserCredentialModel.password("password")); + + UserModel masterUser = session.users().addUser(master, "userAdmin"); + masterUser.grantRole(masterManageUsers); + masterUser.setEnabled(true); + session.userCredentialManager().updateCredential(master, masterUser, UserCredentialModel.password("password")); + + UserModel masterAdmin = session.users().addUser(master, "masterAdmin"); + masterAdmin.grantRole(masterMasterManageUSers); + masterAdmin.setEnabled(true); + session.userCredentialManager().updateCredential(master, masterAdmin, UserCredentialModel.password("password")); + + UserModel user = session.users().addUser(master, "user"); + user.grantRole(masterManageUsers); + user.setEnabled(true); + session.userCredentialManager().updateCredential(master, user, UserCredentialModel.password("password")); + + user = session.users().addUser(realm, "user"); + user.grantRole(realmManageUsers); + user.setEnabled(true); + session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password")); + } + + @Test + public void testRestEvaluation() throws Exception { + testingClient.server().run(IllegalAdminUpgradeTest::setupUsers); + + UserRepresentation realmUserAdmin = adminClient.realm(TEST).users().search("userAdmin").get(0); + UserRepresentation masterUserAdmin = adminClient.realm("master").users().search("userAdmin").get(0); + UserRepresentation realmUser = adminClient.realm(TEST).users().search("user").get(0); + UserRepresentation masterUser = adminClient.realm("master").users().search("user").get(0); + + ClientRepresentation realmAdminClient = adminClient.realm(TEST).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0); + RoleRepresentation realmManageAuthorization = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_AUTHORIZATION).toRepresentation(); + RoleRepresentation realmViewAuthorization = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_AUTHORIZATION).toRepresentation(); + RoleRepresentation realmManageClients = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_CLIENTS).toRepresentation(); + RoleRepresentation realmViewClients = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_CLIENTS).toRepresentation(); + RoleRepresentation realmManageEvents = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_EVENTS).toRepresentation(); + RoleRepresentation realmViewEvents = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_EVENTS).toRepresentation(); + RoleRepresentation realmManageIdentityProviders = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_IDENTITY_PROVIDERS).toRepresentation(); + RoleRepresentation realmViewIdentityProviders = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_IDENTITY_PROVIDERS).toRepresentation(); + RoleRepresentation realmManageRealm = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_REALM).toRepresentation(); + RoleRepresentation realmViewRealm = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_REALM).toRepresentation(); + RoleRepresentation realmImpersonate = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(ImpersonationConstants.IMPERSONATION_ROLE).toRepresentation(); + RoleRepresentation realmManageUsers = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.MANAGE_USERS).toRepresentation(); + RoleRepresentation realmViewUsers = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.VIEW_USERS).toRepresentation(); + RoleRepresentation realmQueryUsers = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.QUERY_USERS).toRepresentation(); + RoleRepresentation realmQueryClients = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.QUERY_CLIENTS).toRepresentation(); + RoleRepresentation realmQueryGroups = adminClient.realm(TEST).clients().get(realmAdminClient.getId()).roles().get(AdminRoles.QUERY_GROUPS).toRepresentation(); + + ClientRepresentation masterClient = adminClient.realm("master").clients().findByClientId(TEST + "-realm").get(0); + RoleRepresentation masterManageAuthorization = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_AUTHORIZATION).toRepresentation(); + RoleRepresentation masterViewAuthorization = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_AUTHORIZATION).toRepresentation(); + RoleRepresentation masterManageClients = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_CLIENTS).toRepresentation(); + RoleRepresentation masterViewClients = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_CLIENTS).toRepresentation(); + RoleRepresentation masterManageEvents = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_EVENTS).toRepresentation(); + RoleRepresentation masterViewEvents = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_EVENTS).toRepresentation(); + RoleRepresentation masterManageIdentityProviders = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_IDENTITY_PROVIDERS).toRepresentation(); + RoleRepresentation masterViewIdentityProviders = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_IDENTITY_PROVIDERS).toRepresentation(); + RoleRepresentation masterManageRealm = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_REALM).toRepresentation(); + RoleRepresentation masterViewRealm = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_REALM).toRepresentation(); + RoleRepresentation masterImpersonate = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(ImpersonationConstants.IMPERSONATION_ROLE).toRepresentation(); + RoleRepresentation masterManageUsers = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.MANAGE_USERS).toRepresentation(); + RoleRepresentation masterViewUsers = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.VIEW_USERS).toRepresentation(); + RoleRepresentation masterQueryUsers = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.QUERY_USERS).toRepresentation(); + RoleRepresentation masterQueryClients = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.QUERY_CLIENTS).toRepresentation(); + RoleRepresentation masterQueryGroups = adminClient.realm("master").clients().get(masterClient.getId()).roles().get(AdminRoles.QUERY_GROUPS).toRepresentation(); + + List roles = new LinkedList<>(); + + { + ClientRepresentation client = realmAdminClient; + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "userAdmin", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + roles.clear(); + roles.add(realmManageAuthorization); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmViewAuthorization); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmManageClients); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmViewClients); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmManageEvents); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmViewEvents); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmManageIdentityProviders); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmViewIdentityProviders); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmManageRealm); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmViewRealm); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmImpersonate); + try { + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(realmManageUsers); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmViewUsers); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmQueryUsers); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmQueryGroups); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmQueryClients); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + realmClient.close(); + } + // test master manageUsers only admin can do with master realm admin roles + { + ClientRepresentation client = masterClient; + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + "master", "masterAdmin", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + roles.clear(); + roles.add(masterManageAuthorization); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterViewAuthorization); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterManageClients); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterViewClients); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterManageEvents); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterViewEvents); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterManageIdentityProviders); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterViewIdentityProviders); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterManageRealm); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterViewRealm); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterImpersonate); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterManageUsers); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterViewUsers); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterQueryUsers); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterQueryGroups); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + roles.clear(); + roles.add(masterQueryClients); + try { + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + realmClient.close(); + } + // test master admin can add all admin roles in realm + { + ClientRepresentation client = realmAdminClient; + Keycloak realmClient = AdminClientUtil.createAdminClient(); + roles.clear(); + roles.add(realmManageAuthorization); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmViewAuthorization); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmManageClients); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmViewClients); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmManageEvents); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmViewEvents); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmManageIdentityProviders); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmViewIdentityProviders); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmManageRealm); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmViewRealm); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmImpersonate); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmManageUsers); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + + roles.clear(); + roles.add(realmViewUsers); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + + roles.clear(); + roles.add(realmQueryUsers); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + + roles.clear(); + roles.add(realmQueryGroups); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(realmQueryClients); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm(TEST).users().get(realmUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + realmClient.close(); + } + // test that "admin" in master realm can assign all roles of master realm realm client admin roles + { + ClientRepresentation client = masterClient; + Keycloak realmClient = AdminClientUtil.createAdminClient(); + roles.clear(); + roles.add(masterManageAuthorization); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterViewAuthorization); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterManageClients); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterViewClients); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterManageEvents); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterViewEvents); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterManageIdentityProviders); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterViewIdentityProviders); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterManageRealm); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterViewRealm); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterImpersonate); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterManageUsers); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + + roles.clear(); + roles.add(masterViewUsers); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + + roles.clear(); + roles.add(masterQueryUsers); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + + roles.clear(); + roles.add(masterQueryGroups); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + roles.clear(); + roles.add(masterQueryClients); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).add(roles); + realmClient.realm("master").users().get(masterUser.getId()).roles().clientLevel(client.getId()).remove(roles); + + realmClient.close(); + } + } + + + + +} From e7f781df5a84ef9e990e97288e4c4ccd3a6a3952 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 23 Jun 2017 09:57:25 -0400 Subject: [PATCH 14/18] fix --- .../resources/admin/permissions/RolePermissions.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java index d220a3908a..ddc27eeab5 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java @@ -289,7 +289,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme if (root.evaluatePermission(roleResource, mapRoleScope, resourceServer)) { return checkAdminRoles(role); } else { - return true; + return false; } } @@ -348,7 +348,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme @Override public boolean canMapComposite(RoleModel role) { - if (canManageDefault(role)) return true; + if (canManageDefault(role)) return checkAdminRoles(role); if (!root.isAdminSameRealm()) { return false; @@ -370,7 +370,11 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme Resource roleResource = resource(role); Scope scope = mapCompositeScope(resourceServer); - return root.evaluatePermission(roleResource, scope, resourceServer); + if (root.evaluatePermission(roleResource, scope, resourceServer)) { + return checkAdminRoles(role); + } else { + return false; + } } @Override From f1807aead4d9b6e0d0f93bbbfad7db70f8f9c288 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sun, 25 Jun 2017 11:28:37 -0400 Subject: [PATCH 15/18] impersonate --- .../admin/permissions/MgmtPermissions.java | 4 ++ .../permissions/UserPermissionManagement.java | 4 +- .../admin/permissions/UserPermissions.java | 67 ++++++++++++++++--- .../admin/FineGrainAdminUnitTest.java | 38 +++++++++++ 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java index ffcc0f077c..2df495322b 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java @@ -294,6 +294,10 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage if (identity == null) { throw new RuntimeException("Identity of admin is not set for permission query"); } + return evaluatePermission(resource, scope, resourceServer, identity); + } + + public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, Identity identity) { RealmModel oldRealm = session.getContext().getRealm(); try { session.getContext().setRealm(realm); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java index d4184edb5f..d4653782ef 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionManagement.java @@ -43,5 +43,7 @@ public interface UserPermissionManagement { Policy mapRolesPermission(); - Policy impersonatePermission(); + Policy adminImpersonatingPermission(); + + Policy userImpersonatedPermission(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java index 46a9d0161c..80bf07f74c 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -18,12 +18,13 @@ package org.keycloak.services.resources.admin.permissions; import org.jboss.logging.Logger; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.common.UserModelIdentity; +import org.keycloak.authorization.identity.Identity; 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.GroupModel; import org.keycloak.models.ImpersonationConstants; import org.keycloak.models.KeycloakSession; @@ -47,9 +48,11 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme private static final Logger logger = Logger.getLogger(UserPermissions.class); public static final String MAP_ROLES_SCOPE="map-roles"; public static final String IMPERSONATE_SCOPE="impersonate"; + public static final String USER_IMPERSONATED_SCOPE="user-impersonated"; public static final String MANAGE_GROUP_MEMBERSHIP_SCOPE="manage-group-membership"; public static final String MAP_ROLES_PERMISSION_USERS = "map-roles.permission.users"; - public static final String IMPERSONATE_PERMISSION_USERS = "impersonate.permission.users"; + public static final String ADMIN_IMPERSONATING_PERMISSION = "admin-impersonating.permission.users"; + public static final String USER_IMPERSONATED_PERMISSION = "user-impersonated.permission.users"; public static final String MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS = "manage-group-membership.permission.users"; public static final String MANAGE_PERMISSION_USERS = "manage.permission.users"; public static final String VIEW_PERMISSION_USERS = "view.permission.users"; @@ -75,6 +78,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme Scope viewScope = root.realmViewScope(); Scope mapRolesScope = root.initializeRealmScope(MAP_ROLES_SCOPE); Scope impersonateScope = root.initializeRealmScope(IMPERSONATE_SCOPE); + Scope userImpersonatedScope = root.initializeRealmScope(USER_IMPERSONATED_SCOPE); Scope manageGroupMembershipScope = root.initializeRealmScope(MANAGE_GROUP_MEMBERSHIP_SCOPE); Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); @@ -86,6 +90,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme scopeset.add(mapRolesScope); scopeset.add(impersonateScope); scopeset.add(manageGroupMembershipScope); + scopeset.add(userImpersonatedScope); usersResource.updateScopes(scopeset); } Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); @@ -104,9 +109,13 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme if (membershipPermission == null) { Helper.addEmptyScopePermission(authz, server, MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, usersResource, manageGroupMembershipScope); } - Policy impersonatePermission = authz.getStoreFactory().getPolicyStore().findByName(IMPERSONATE_PERMISSION_USERS, server.getId()); + Policy impersonatePermission = authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId()); if (impersonatePermission == null) { - Helper.addEmptyScopePermission(authz, server, IMPERSONATE_PERMISSION_USERS, usersResource, impersonateScope); + Helper.addEmptyScopePermission(authz, server, ADMIN_IMPERSONATING_PERMISSION, usersResource, impersonateScope); + } + impersonatePermission = authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId()); + if (impersonatePermission == null) { + Helper.addEmptyScopePermission(authz, server, USER_IMPERSONATED_PERMISSION, usersResource, userImpersonatedScope); } } @@ -117,7 +126,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission().getId()); scopes.put(MAP_ROLES_SCOPE, mapRolesPermission().getId()); scopes.put(MANAGE_GROUP_MEMBERSHIP_SCOPE, manageGroupMembershipPermission().getId()); - scopes.put(IMPERSONATE_SCOPE, impersonatePermission().getId()); + scopes.put(IMPERSONATE_SCOPE, adminImpersonatingPermission().getId()); return scopes; } @@ -166,7 +175,12 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme authz.getStoreFactory().getPolicyStore().delete(policy.getId()); } - policy = impersonatePermission(); + policy = adminImpersonatingPermission(); + if (policy == null) { + authz.getStoreFactory().getPolicyStore().delete(policy.getId()); + + } + policy = userImpersonatedPermission(); if (policy == null) { authz.getStoreFactory().getPolicyStore().delete(policy.getId()); @@ -215,9 +229,15 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme @Override - public Policy impersonatePermission() { + public Policy adminImpersonatingPermission() { ResourceServer server = root.realmResourceServer(); - return authz.getStoreFactory().getPolicyStore().findByName(IMPERSONATE_PERMISSION_USERS, server.getId()); + return authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId()); + } + + @Override + public Policy userImpersonatedPermission() { + ResourceServer server = root.realmResourceServer(); + return authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId()); } @@ -451,7 +471,34 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme @Override public boolean canImpersonate(UserModel user) { - return canImpersonate(); + if (!canImpersonate()) { + return false; + } + + Identity userIdentity = new UserModelIdentity(root.realm, user); + if (!root.isAdminSameRealm()) { + return true; + } + + ResourceServer server = root.realmResourceServer(); + if (server == null) return true; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); + if (resource == null) return true; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId()); + if (policy == null) { + return true; + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return true; + } + + Scope scope = root.realmScope(USER_IMPERSONATED_SCOPE); + return root.evaluatePermission(resource, scope, server, userIdentity); } @@ -469,7 +516,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); if (resource == null) return false; - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(IMPERSONATE_PERMISSION_USERS, server.getId()); + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId()); if (policy == null) { return false; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index b8edcf8316..452e81fc8a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -25,6 +25,7 @@ import org.keycloak.authorization.model.Resource; import org.keycloak.models.GroupModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; @@ -160,6 +161,19 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { { permissions.clients().setPermissionsEnabled(client1, true); } + // setup Users impersonate policy + { + ClientModel realmManagementClient = realm.getClientByClientId("realm-management"); + RoleModel adminRole = realmManagementClient.getRole(AdminRoles.REALM_ADMIN); + permissions.users().setPermissionsEnabled(true); + ResourceServer server = permissions.realmResourceServer(); + Policy adminPolicy = permissions.roles().rolePolicy(server, adminRole); + adminPolicy.setLogic(Logic.NEGATIVE); + Policy permission = permissions.users().userImpersonatedPermission(); + permission.addAssociatedPolicy(adminPolicy); + permission.setDecisionStrategy(DecisionStrategy.UNANIMOUS); + } + } @@ -183,6 +197,11 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { session.userCredentialManager().updateCredential(realm, nomapAdmin, UserCredentialModel.password("password")); nomapAdmin.grantRole(adminRole); + UserModel anotherAdmin = session.users().addUser(realm, "anotherAdmin"); + anotherAdmin.setEnabled(true); + session.userCredentialManager().updateCredential(realm, anotherAdmin, UserCredentialModel.password("password")); + anotherAdmin.grantRole(adminRole); + UserModel authorizedUser = session.users().addUser(realm, "authorized"); authorizedUser.setEnabled(true); session.userCredentialManager().updateCredential(realm, authorizedUser, UserCredentialModel.password("password")); @@ -372,6 +391,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { testingClient.server().run(FineGrainAdminUnitTest::setupUsers); UserRepresentation user1 = adminClient.realm(TEST).users().search("user1").get(0); + UserRepresentation anotherAdmin = adminClient.realm(TEST).users().search("anotherAdmin").get(0); UserRepresentation groupMember = adminClient.realm(TEST).users().search("groupMember").get(0); RoleRepresentation realmRole = adminClient.realm(TEST).roles().get("realm-role").toRepresentation(); List realmRoleSet = new LinkedList<>(); @@ -384,6 +404,24 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { List clientRoleSet = new LinkedList<>(); clientRoleSet.add(clientRole); + // test illegal impersonation + { + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "nomap-admin", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + realmClient.realm(TEST).users().get(user1.getId()).impersonate(); + realmClient.close(); // just in case of cookie settings + realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "nomap-admin", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + try { + realmClient.realm(TEST).users().get(anotherAdmin.getId()).impersonate(); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + + } + { Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), From 28b3ef9aa909e1e6583dee87aeb8487c0fb38af4 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 26 Jun 2017 11:40:32 -0400 Subject: [PATCH 16/18] admin console work --- .../java/org/keycloak/RSAVerifierTest.java | 5 +- .../AuthenticationManagementResource.java | 4 +- .../ClientAttributeCertificateResource.java | 8 +- .../resources/admin/ClientResource.java | 38 +++++++-- .../ClientPermissionEvaluator.java | 4 + .../ClientPermissionManagement.java | 3 + .../admin/permissions/ClientPermissions.java | 78 +++++++++++++++++-- .../admin/permissions/GroupPermissions.java | 6 +- .../admin/permissions/RolePermissions.java | 2 +- .../admin/permissions/UserPermissions.java | 1 + .../admin/FineGrainAdminUnitTest.java | 70 +++++++++++++++-- .../messages/admin-messages_en.properties | 11 ++- .../resources/js/authz/authz-controller.js | 10 ++- .../authz/mgmt/client-permissions.html | 4 +- .../authz/mgmt/client-role-permissions.html | 4 +- .../partials/client-clustering-node.html | 6 +- .../resources/partials/client-clustering.html | 6 +- .../partials/client-credentials-generic.html | 4 +- .../client-credentials-jwt-key-export.html | 4 +- .../client-credentials-jwt-key-import.html | 2 +- .../partials/client-credentials-jwt.html | 4 +- .../partials/client-credentials-secret.html | 4 +- .../partials/client-credentials.html | 2 +- .../resources/partials/client-detail.html | 6 +- .../admin/resources/partials/client-keys.html | 4 +- .../client-registration-access-token.html | 4 +- .../resources/partials/client-revocation.html | 4 +- .../partials/client-role-detail.html | 6 +- .../resources/partials/client-role-list.html | 4 +- .../partials/client-saml-key-export.html | 4 +- .../partials/client-saml-key-import.html | 2 +- .../resources/partials/client-saml-keys.html | 6 +- .../partials/client-scope-mappings.html | 4 +- .../client-service-account-roles.html | 4 +- .../resources/templates/kc-tabs-client.html | 2 +- .../resources/templates/kc-tabs-group.html | 4 +- .../resources/templates/kc-tabs-role.html | 2 +- .../resources/templates/kc-tabs-users.html | 2 +- 38 files changed, 251 insertions(+), 87 deletions(-) diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java index 58e1f38d73..e606e35a94 100755 --- a/core/src/test/java/org/keycloak/RSAVerifierTest.java +++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java @@ -234,13 +234,16 @@ public class RSAVerifierTest { public void testTokenAuth() throws Exception { token = new AccessToken(); token.subject("CN=Client") - .issuer("domain") + .issuer("http://localhost:8080/auth/realms/demo") .addAccess("service").addRole("admin").verifyCaller(true); + token.setEmail("bill@jboss.org"); String encoded = new JWSBuilder() .jsonContent(token) .rsa256(idpPair.getPrivate()); + System.out.println("token size: " + encoded.length()); + AccessToken v = null; try { v = verifySkeletonKeyToken(encoded); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 88650c178a..61f6254710 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -137,7 +137,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List> getClientAuthenticatorProviders() { - auth.realm().requireViewRealm(); + auth.requireAnyAdminRole(); List factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class); return buildProviderMetadata(factories); @@ -959,7 +959,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public Map> getPerClientConfigDescription() { - auth.realm().requireViewRealm(); + auth.requireAnyAdminRole(); List factories = session.getKeycloakSessionFactory().getProviderFactories(ClientAuthenticator.class); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java index e3dda51b98..a798414247 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java @@ -115,7 +115,7 @@ public class ClientAttributeCertificateResource { @Path("generate") @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation generate() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); CertificateRepresentation info = KeycloakModelUtils.generateKeyPairCertificate(client.getClientId()); @@ -139,7 +139,7 @@ public class ClientAttributeCertificateResource { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); try { CertificateRepresentation info = getCertFromRequest(input); @@ -165,7 +165,7 @@ public class ClientAttributeCertificateResource { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); try { CertificateRepresentation info = getCertFromRequest(input); @@ -306,7 +306,7 @@ public class ClientAttributeCertificateResource { @Produces(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_JSON) public byte[] generateAndGetKeystore(final KeyStoreConfig config) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) { throw new NotAcceptableException("Only support jks or pkcs12 format."); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 77440be34c..e7d611eaeb 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -27,6 +27,7 @@ import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; @@ -41,6 +42,7 @@ import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.protocol.ClientInstallationProvider; import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.UserRepresentation; @@ -134,7 +136,7 @@ public class ClientResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public Response update(final ClientRepresentation rep) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); ValidationMessages validationMessages = new ValidationMessages(); if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { @@ -227,7 +229,7 @@ public class ClientResource { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public CredentialRepresentation regenerateSecret() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); logger.debug("regenerateSecret"); UserCredentialModel cred = KeycloakModelUtils.generateSecret(client); @@ -326,7 +328,7 @@ public class ClientResource { @POST @Produces(MediaType.APPLICATION_JSON) public GlobalRequestResult pushRevocation() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).resource(ResourceType.CLIENT).success(); return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client); @@ -456,7 +458,7 @@ public class ClientResource { @POST @Consumes(MediaType.APPLICATION_JSON) public void registerNode(Map formParams) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); String node = formParams.get("node"); if (node == null) { @@ -476,7 +478,7 @@ public class ClientResource { @DELETE @NoCache public void unregisterNode(final @PathParam("node") String node) { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); if (logger.isDebugEnabled()) logger.debug("Unregister node: " + node); @@ -500,7 +502,7 @@ public class ClientResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public GlobalRequestResult testNodesAvailable() { - auth.clients().requireManage(client); + auth.clients().requireConfigure(client); logger.debug("Test availability of cluster nodes"); GlobalRequestResult result = new ResourceAdminManager(session).testNodesAvailability(uriInfo.getRequestUri(), realm, client); @@ -583,6 +585,30 @@ public class ClientResource { new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId()); } + if (rep.isFullScopeAllowed() != null && rep.isFullScopeAllowed().booleanValue() != client.isFullScopeAllowed()) { + auth.clients().requireManage(client); + } + + if (rep.getClientTemplate() != null) { + ClientTemplateModel currTemplate = client.getClientTemplate(); + if (currTemplate == null) { + if (!rep.getClientTemplate().equals(ClientTemplateRepresentation.NONE)) { + auth.clients().requireManage(client); + } + } else if (!rep.getClientTemplate().equals(currTemplate.getName())){ + auth.clients().requireManage(client); + } + if ((rep.isUseTemplateConfig() != null && rep.isUseTemplateConfig().booleanValue() != client.useTemplateConfig()) + || (rep.isUseTemplateScope() != null && rep.isUseTemplateScope().booleanValue() != client.useTemplateScope()) + || (rep.isUseTemplateMappers() != null && rep.isUseTemplateMappers().booleanValue() != client.useTemplateMappers()) + + ) { + auth.clients().requireManage(client); + } + } + + + RepresentationToModel.updateClient(rep, client); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java index 25da468837..3b64a27e12 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java @@ -56,6 +56,10 @@ public interface ClientPermissionEvaluator { boolean canManage(ClientModel client); + boolean canConfigure(ClientModel client); + + void requireConfigure(ClientModel client); + void requireManage(ClientModel client); boolean canView(ClientModel client); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java index d967443264..8a6b76dd8e 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java @@ -31,6 +31,7 @@ public interface ClientPermissionManagement { public static final String MAP_ROLES_SCOPE = "map-roles"; public static final String MAP_ROLES_CLIENT_SCOPE = "map-roles-client-scope"; public static final String MAP_ROLES_COMPOSITE_SCOPE = "map-roles-composite"; + public static final String CONFIGURE_SCOPE = "configure"; boolean isPermissionsEnabled(ClientModel client); @@ -48,6 +49,8 @@ public interface ClientPermissionManagement { Policy managePermission(ClientModel client); + Policy configurePermission(ClientModel client); + Policy viewPermission(ClientModel client); ResourceServer resourceServer(ClientModel client); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java index 95e440eb6e..2b1e2340c4 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java @@ -63,6 +63,9 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private String getManagePermissionName(ClientModel client) { return "manage.permission.client." + client.getId(); } + private String getConfigurePermissionName(ClientModel client) { + return "configure.permission.client." + client.getId(); + } private String getViewPermissionName(ClientModel client) { return "view.permission.client." + client.getId(); } @@ -98,6 +101,10 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa if (mapRoleCompositeScope == null) { mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_COMPOSITE_SCOPE, server); } + Scope configureScope = authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId()); + if (configureScope == null) { + configureScope = authz.getStoreFactory().getScopeStore().create(CONFIGURE_SCOPE, server); + } String resourceName = getResourceName(client); Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId()); @@ -105,6 +112,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa resource = authz.getStoreFactory().getResourceStore().create(resourceName, server, server.getClientId()); resource.setType("Client"); Set scopeset = new HashSet<>(); + scopeset.add(configureScope); scopeset.add(manageScope); scopeset.add(viewScope); scopeset.add(mapRoleScope); @@ -117,6 +125,11 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa if (managePermission == null) { Helper.addEmptyScopePermission(authz, server, managePermissionName, resource, manageScope); } + String configurePermissionName = getConfigurePermissionName(client); + Policy configurePermission = authz.getStoreFactory().getPolicyStore().findByName(configurePermissionName, server.getId()); + if (configurePermission == null) { + Helper.addEmptyScopePermission(authz, server, configurePermissionName, resource, configureScope); + } String viewPermissionName = getViewPermissionName(client); Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId()); if (viewPermission == null) { @@ -139,8 +152,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa } } - private void deletePolicy(String name, ClientModel client, ResourceServer server) { - Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getViewPermissionName(client), server.getId()); + private void deletePolicy(String name, ResourceServer server) { + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(name, server.getId()); if (policy != null) { authz.getStoreFactory().getPolicyStore().delete(policy.getId()); } @@ -150,11 +163,12 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa private void deletePermissions(ClientModel client) { ResourceServer server = resourceServer(client); if (server == null) return; - deletePolicy(getManagePermissionName(client), client, server); - deletePolicy(getViewPermissionName(client), client, server); - deletePolicy(getMapRolesPermissionName(client), client, server); - deletePolicy(getMapRolesClientScopePermissionName(client), client, server); - deletePolicy(getMapRolesCompositePermissionName(client), client, server); + deletePolicy(getManagePermissionName(client), server); + deletePolicy(getViewPermissionName(client), server); + deletePolicy(getMapRolesPermissionName(client), server); + deletePolicy(getMapRolesClientScopePermissionName(client), server); + deletePolicy(getMapRolesCompositePermissionName(client), server); + deletePolicy(getConfigurePermissionName(client), server); Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());; if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId()); } @@ -182,6 +196,10 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.MANAGE_SCOPE, server.getId()); } + private Scope configureScope(ResourceServer server) { + return authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId()); + } + private Scope viewScope(ResourceServer server) { return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.VIEW_SCOPE, server.getId()); } @@ -259,6 +277,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId()); scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(client).getId()); scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(client).getId()); + scopes.put(CONFIGURE_SCOPE, configurePermission(client).getId()); return scopes; } @@ -291,6 +310,41 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return root.evaluatePermission(resource, scope, server); } + @Override + public boolean canConfigure(ClientModel client) { + if (canManage(client)) return true; + if (!root.isAdminSameRealm()) { + return false; + } + + ResourceServer server = resourceServer(client); + if (server == null) return false; + + Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId()); + if (resource == null) return false; + + Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getConfigurePermissionName(client), server.getId()); + if (policy == null) { + return false; + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + // if no policies attached to permission then just do default behavior + if (associatedPolicies == null || associatedPolicies.isEmpty()) { + return false; + } + + Scope scope = configureScope(server); + return root.evaluatePermission(resource, scope, server); + } + @Override + public void requireConfigure(ClientModel client) { + if (!canConfigure(client)) { + throw new ForbiddenException(); + } + } + + @Override public void requireManage(ClientModel client) { if (!canManage(client)) { @@ -300,7 +354,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa @Override public boolean canView(ClientModel client) { - return hasView(client) || canManage(client); + return hasView(client) || canConfigure(client); } private boolean hasView(ClientModel client) { @@ -437,6 +491,13 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa return authz.getStoreFactory().getPolicyStore().findByName(getManagePermissionName(client), server.getId()); } + @Override + public Policy configurePermission(ClientModel client) { + ResourceServer server = resourceServer(client); + if (server == null) return null; + return authz.getStoreFactory().getPolicyStore().findByName(getConfigurePermissionName(client), server.getId()); + } + @Override public Policy viewPermission(ClientModel client) { ResourceServer server = resourceServer(client); @@ -499,6 +560,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa Map map = new HashMap<>(); map.put("view", canView(client)); map.put("manage", canManage(client)); + map.put("configure", canConfigure(client)); return map; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java index d586c362ef..a7e9f374be 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java @@ -41,9 +41,9 @@ import java.util.Set; class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManagement { private static final Logger logger = Logger.getLogger(GroupPermissions.class); public static final String MAP_ROLE_SCOPE = "map-role"; - public static final String MANAGE_MEMBERSHIP_SCOPE = "manage.membership"; - public static final String MANAGE_MEMBERS_SCOPE = "manage.members"; - public static final String VIEW_MEMBERS_SCOPE = "view.members"; + public static final String MANAGE_MEMBERSHIP_SCOPE = "manage-membership"; + public static final String MANAGE_MEMBERS_SCOPE = "manage-members"; + public static final String VIEW_MEMBERS_SCOPE = "view-members"; protected final KeycloakSession session; protected final RealmModel realm; protected final AuthorizationProvider authz; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java index ddc27eeab5..091d7a58f7 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java @@ -319,7 +319,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme if (container instanceof RealmModel) { return root.realm().canManageRealm(); } else { - return root.clients().canManage((ClientModel)container); + return root.clients().canConfigure((ClientModel)container); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java index 80bf07f74c..149e52678b 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -127,6 +127,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme scopes.put(MAP_ROLES_SCOPE, mapRolesPermission().getId()); scopes.put(MANAGE_GROUP_MEMBERSHIP_SCOPE, manageGroupMembershipPermission().getId()); scopes.put(IMPERSONATE_SCOPE, adminImpersonatingPermission().getId()); + scopes.put(USER_IMPERSONATED_SCOPE, userImpersonatedPermission().getId()); return scopes; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 452e81fc8a..fc39267b54 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -22,9 +22,11 @@ import org.keycloak.admin.client.Keycloak; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.model.Resource; +import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.GroupModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; @@ -85,6 +87,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleModel salesAppsAdminRole = realm.addRole("sales-apps-admin"); salesAppsAdminRole.addCompositeRole(clientAdmin); salesAppsAdminRole.addCompositeRole(client2Admin); + ClientModel realmManagementClient = realm.getClientByClientId("realm-management"); + RoleModel queryClient = realmManagementClient.getRole(AdminRoles.QUERY_CLIENTS); UserModel admin = session.users().addUser(realm, "salesManager"); @@ -99,6 +103,10 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { admin = session.users().addUser(realm, "sales-pipeline-admin"); admin.setEnabled(true); session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password")); + admin = session.users().addUser(realm, "client-admin"); + admin.setEnabled(true); + admin.grantRole(queryClient); + session.userCredentialManager().updateCredential(realm, admin, UserCredentialModel.password("password")); UserModel user = session.users().addUser(realm, "salesman"); user.setEnabled(true); @@ -115,6 +123,8 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { RoleModel realmRole = realm.addRole("realm-role"); RoleModel realmRole2 = realm.addRole("realm-role2"); ClientModel client1 = realm.addClient(CLIENT_NAME); + ClientTemplateModel template = realm.addClientTemplate("template"); + client1.setFullScopeAllowed(false); RoleModel client1Role = client1.addRole("client-role"); GroupModel group = realm.createGroup("top"); @@ -280,6 +290,19 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { clientManagerPolicy.addAssociatedPolicy(userPolicy); + UserModel clientConfigurer = session.users().addUser(realm, "clientConfigurer"); + clientConfigurer.setEnabled(true); + clientConfigurer.grantRole(queryClientsRole); + session.userCredentialManager().updateCredential(realm, clientConfigurer, UserCredentialModel.password("password")); + + Policy clientConfigurePolicy = permissions.clients().configurePermission(client); + userRep = new UserPolicyRepresentation(); + userRep.setName("clientConfigure"); + userRep.addUser("clientConfigurer"); + userPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(userRep, permissions.clients().resourceServer(client)); + clientConfigurePolicy.addAssociatedPolicy(userPolicy); + + @@ -364,20 +387,13 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { protected boolean isImportAfterEachMethod() { return true; } - //@Test + @Test public void testDemo() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupDemo); Thread.sleep(1000000000); } - //@Test - public void testUI() throws Exception { - testingClient.server().run(FineGrainAdminUnitTest::setupPolices); - testingClient.server().run(FineGrainAdminUnitTest::setupUsers); - Thread.sleep(1000000000); - } - @Test public void testEvaluationLocal() throws Exception { testingClient.server().run(FineGrainAdminUnitTest::setupPolices); @@ -400,10 +416,48 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { List realmRole2Set = new LinkedList<>(); realmRole2Set.add(realmRole2); ClientRepresentation client = adminClient.realm(TEST).clients().findByClientId(CLIENT_NAME).get(0); + ClientTemplateRepresentation template = adminClient.realm(TEST).clientTemplates().findAll().get(0); RoleRepresentation clientRole = adminClient.realm(TEST).clients().get(client.getId()).roles().get("client-role").toRepresentation(); List clientRoleSet = new LinkedList<>(); clientRoleSet.add(clientRole); + // test configure client + { + Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, "clientConfigurer", "password", Constants.ADMIN_CLI_CLIENT_ID, null); + client.setAdminUrl("http://nowhere"); + realmClient.realm(TEST).clients().get(client.getId()).update(client); + client.setFullScopeAllowed(true); + try { + realmClient.realm(TEST).clients().get(client.getId()).update(client); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + client.setFullScopeAllowed(false); + realmClient.realm(TEST).clients().get(client.getId()).update(client); + + client.setClientTemplate(template.getName()); + try { + realmClient.realm(TEST).clients().get(client.getId()).update(client); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + client.setClientTemplate(null); + realmClient.realm(TEST).clients().get(client.getId()).update(client); + + try { + realmClient.realm(TEST).clients().get(client.getId()).getScopeMappings().realmLevel().add(realmRoleSet); + Assert.fail("should fail with forbidden exception"); + } catch (ClientErrorException e) { + Assert.assertEquals(e.getResponse().getStatus(), 403); + + } + } + // test illegal impersonation { Keycloak realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), 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 94d214e766..28a985c516 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 @@ -1326,9 +1326,9 @@ manage-permissions-client.tooltip=Fine grain permssions for admins that want to manage-permissions-group.tooltip=Fine grain permssions for admins that want to manage this group or the members of this group. manage-authz-group-scope-description=Policies that decide if an admin can manage this group view-authz-group-scope-description=Policies that decide if an admin can view this group -view.members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group -manage.members-authz-group-scope-description=Policies that decide if an admin can view the members of this group +view-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group manage-authz-client-scope-description=Policies that decide if an admin can manage this client +configure-authz-client-scope-description=Reduced management permissions for admin. Cannot set scope, template, or protocol mappers. view-authz-client-scope-description=Policies that decide if an admin can view this client map-roles-authz-client-scope-description=Policies that decide if an admin can map roles defined by this client map-roles-client-scope-authz-client-scope-description=Policies that decide if an admin can apply roles defined by this client to the client scope of another client @@ -1336,6 +1336,11 @@ map-roles-composite-authz-client-scope-description=Policies that decide if an ad map-role-authz-role-scope-description=Policies that decide if an admin can map role this role to a user or group map-role-client-scope-authz-role-scope-description=Policies that decide if an admin can apply this role to the client scope of a client map-role-composite-authz-role-scope-description=Policies that decide if an admin can apply this role as a composite to another role - +manage-group-membership-authz-users-scope-description=Policies that decide if an admin can manage group membership for all users in the realm. This is used in conjunction with specific group policy +impersonate-authz-users-scope-description=Policies that decide if admin can impersonate other users +map-roles-authz-users-scope-description=Policies that decide if admin can map roles for all users +user-impersonated-authz-users-scope-description=Policies that decide which users can be impersonated. These policies are applied to the user being impersonated. +manage-membership-authz-group-scope-description=Policies that decide if admin can add or remove users from this group +manage-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group 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 86444b86bb..034d595496 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 @@ -2507,7 +2507,7 @@ module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $l }); -module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $location, realm, client, role, RoleManagementPermissions, Client, Notifications) { +module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $location, realm, client, role, Client, RoleManagementPermissions, Client, Notifications) { console.log('RealmRolePermissionsCtrl'); $scope.client = client; $scope.role = role; @@ -2515,6 +2515,9 @@ module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $ RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { $scope.permissions = data; }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); $scope.setEnabled = function() { console.log('perssions enabled: ' + $scope.permissions.enabled); var param = { enabled: $scope.permissions.enabled}; @@ -2542,12 +2545,15 @@ module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $locat }); -module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $location, realm, client, ClientManagementPermissions, Notifications) { +module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $location, realm, client, Client, ClientManagementPermissions, Notifications) { $scope.client = client; $scope.realm = realm; ClientManagementPermissions.get({realm: realm.realm, client: client.id}, function(data) { $scope.permissions = data; }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); $scope.setEnabled = function() { var param = { enabled: $scope.permissions.enabled}; $scope.permissions = ClientManagementPermissions.update({realm: realm.realm, client: client.id}, param); diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html index bb6d7b6d58..abc21a4d90 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-permissions.html @@ -27,9 +27,9 @@ - {{scopeName}} + {{scopeName}} - {{:: 'edit' | translate}} + {{:: 'edit' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html index 5b423d06f9..c5f37ea320 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/mgmt/client-role-permissions.html @@ -28,9 +28,9 @@ - {{scopeName}} + {{scopeName}} - {{:: 'edit' | translate}} + {{:: 'edit' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html index 98820eb75c..7df3a42ae0 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering-node.html @@ -10,10 +10,10 @@

    {{:: 'add-node' | translate}}

    {{node.host|capitalize}} - +

    - +
    @@ -27,7 +27,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html index e729582dae..72ff437896 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html @@ -7,7 +7,7 @@ - + {{:: 'basic-configuration' | translate}}
    @@ -31,7 +31,7 @@
    -
    +
    @@ -43,7 +43,7 @@ -
    +
    {{:: 'register-node-manually' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html index a2d59e1fd0..39e9ebc3a5 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html @@ -1,11 +1,11 @@
    - +
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html index 8fbd0792e0..82e50b78f3 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-export.html @@ -9,7 +9,7 @@

    {{:: 'generate-private-key' | translate}}

    - + @@ -48,7 +48,7 @@ {{:: 'store-password.tooltip' | translate}}
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html index f96c4f0c3f..ec88cd6f65 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt-key-import.html @@ -9,7 +9,7 @@

    {{:: 'import-client-certificate' | translate}}

    - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html index 8df035ce57..b2a5bf44b5 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html @@ -1,4 +1,4 @@ -
    +
    @@ -63,7 +63,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html index d4fb071683..7cf5bf1359 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html @@ -1,5 +1,5 @@
    - +
    @@ -7,7 +7,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html index 387177491d..e6865a3e79 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html @@ -7,7 +7,7 @@ - +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index 5f24b21d82..cd6e2717be 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -7,7 +7,7 @@ - +
    @@ -57,7 +57,7 @@
    {{:: 'client-protocol.tooltip' | translate}}
    -
    +
    @@ -391,7 +391,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html index e8d6259b40..c74f54f4fd 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-keys.html @@ -7,7 +7,7 @@ - + @@ -137,7 +137,7 @@ kc-select-action="click" readonly>{{keyInfo.certificate}}
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html index 55a3546dbd..327b70d9ad 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html @@ -1,5 +1,5 @@
    - +
    @@ -7,7 +7,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html index 761b3374eb..95db767ae5 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html @@ -7,7 +7,7 @@ - +
    @@ -18,7 +18,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html index d21d7ef593..67c96fc930 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html @@ -10,7 +10,7 @@ - +
    @@ -47,13 +47,13 @@
    -
    +
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html index 458b412a5d..99060743cb 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html @@ -10,7 +10,7 @@ - - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html index 753f564bd1..ec51e9674f 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-export.html @@ -9,7 +9,7 @@

    {{:: 'export-saml-key' | translate}} {{client.clientId|capitalize}}

    - + @@ -55,7 +55,7 @@ {{:: 'store-password.tooltip' | translate}}
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html index b479058d66..a580a61b18 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-key-import.html @@ -9,7 +9,7 @@

    {{:: 'import-saml-key' | translate}} {{client.clientId|capitalize}}

    - + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html index c5374bb654..776349d5be 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-saml-keys.html @@ -7,7 +7,7 @@ - +
    {{:: 'signing-key' | translate}} {{:: 'saml-signing-key' | translate}}
    @@ -27,7 +27,7 @@
    -
    +
    @@ -53,7 +53,7 @@
    -
    +
    diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html index 482ef5b77f..6cb01f4a47 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html @@ -25,14 +25,14 @@ {{:: 'full-scope-allowed.tooltip' | translate}}
    - +
    Client template has full scope allowed, which means this client will have the full scope of all roles.
    - +
    inherited diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html index 24b2594841..d57873f2a5 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-service-account-roles.html @@ -10,7 +10,7 @@

    {{client.clientId}} {{:: 'service-accounts' | translate}}

    - +
    @@ -102,7 +102,7 @@
    -
    + diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html index cf46c2e912..e5b7c21c70 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html @@ -43,7 +43,7 @@ {{:: 'service-account-roles' | translate}} {{:: 'service-account-roles.tooltip' | translate}} -
  • +
  • {{:: 'authz-permissions' | translate}} {{:: 'manage-permissions-client.tooltip' | translate}}
  • diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html index 3aa12bc2f0..e73d998f5b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html @@ -1,7 +1,7 @@

    {{group.name|capitalize}} - +

    + @@ -29,7 +29,7 @@ {{role.description}} {{:: 'edit' | translate}}{{:: 'delete' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-client-roles-available' | translate}}