From f1807aead4d9b6e0d0f93bbbfad7db70f8f9c288 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sun, 25 Jun 2017 11:28:37 -0400 Subject: [PATCH] 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(),