From f760427c5c71e0f8beca54e36608a873c07f6e60 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 8 May 2017 13:48:51 -0400 Subject: [PATCH] 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