From 608701aea7e76d727408c7d8208c61aa40813c6e Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sat, 11 Jul 2015 10:10:05 -0400 Subject: [PATCH 1/4] impersonation redo in admin console --- .../admin/resources/js/controllers/realm.js | 3 + .../admin/resources/js/controllers/users.js | 24 ++- .../theme/base/admin/resources/js/services.js | 7 + .../admin/resources/partials/user-detail.html | 9 +- .../admin/resources/partials/user-list.html | 6 +- .../theme/base/login/impersonate.ftl | 45 ----- .../migration/migrators/MigrateTo1_4_0.java | 4 +- ...tants.java => ImpersonationConstants.java} | 32 ++-- .../services/managers/RealmManager.java | 4 +- .../resources/ImpersonationService.java | 177 ------------------ .../services/resources/RealmsResource.java | 16 -- .../AuthenticationManagementResource.java | 13 +- .../services/resources/admin/RealmAuth.java | 9 +- .../resources/admin/UsersResource.java | 33 ++++ .../testsuite/account/AccountTest.java | 2 +- .../keycloak/testsuite/admin/ClientTest.java | 4 +- .../keycloak/testsuite/model/ImportTest.java | 4 +- 17 files changed, 114 insertions(+), 278 deletions(-) delete mode 100755 forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl rename model/api/src/main/java/org/keycloak/models/{ImpersonationServiceConstants.java => ImpersonationConstants.java} (62%) delete mode 100755 services/src/main/java/org/keycloak/services/resources/ImpersonationService.java diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index e9341b329b..335bef3415 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -79,6 +79,9 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $ get manageEvents() { return getAccess('manage-events'); + }, + get impersonation() { + return getAccess('impersonation'); } } diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 93e321bf15..9d807a911e 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -154,7 +154,7 @@ module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents }); -module.controller('UserListCtrl', function($scope, realm, User) { +module.controller('UserListCtrl', function($scope, realm, User, UserImpersonation) { $scope.realm = realm; $scope.page = 0; @@ -164,6 +164,16 @@ module.controller('UserListCtrl', function($scope, realm, User) { first : 0 } + $scope.impersonate = function(userId) { + UserImpersonation.save({realm : realm.realm, user: userId}, function (data) { + if (data.sameRealm) { + window.location = data.redirect; + } else { + window.open(data.redirect, "_blank"); + } + }); + }; + $scope.firstPage = function() { $scope.query.first = 0; $scope.searchQuery(); @@ -195,7 +205,7 @@ module.controller('UserListCtrl', function($scope, realm, User) { -module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, RequiredActions, $location, Dialog, Notifications) { +module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, UserImpersonation, RequiredActions, $location, Dialog, Notifications) { $scope.realm = realm; $scope.create = !user.id; $scope.editUsername = $scope.create || $scope.realm.editUsernameAllowed; @@ -208,7 +218,17 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede } convertAttributeValuesToString(user); + $scope.user = angular.copy(user); + $scope.impersonate = function() { + UserImpersonation.save({realm : realm.realm, user: $scope.user.id}, function (data) { + if (data.sameRealm) { + window.location = data.redirect; + } else { + window.open(data.redirect, "_blank"); + } + }); + }; if(user.federationLink) { console.log("federationLink is not null"); UserFederationInstances.get({realm : realm.realm, instance: user.federationLink}, function(link) { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index 31071017a9..bd1fe9868a 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -319,6 +319,13 @@ module.factory('UserConsents', function($resource) { }); }); +module.factory('UserImpersonation', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/impersonation', { + realm : '@realm', + user : '@user' + }); +}); + module.factory('UserCredentials', function($resource) { var credentials = {}; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html index 6779d4254b..19f79af538 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html @@ -116,13 +116,14 @@ -
- - +
+ + +
- + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html index 9a042e39a3..dc2c4ac7e7 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html @@ -5,7 +5,7 @@ Table of realm users - +
@@ -17,7 +17,7 @@
- @@ -29,6 +29,7 @@ Last Name First Name Email + @@ -49,6 +50,7 @@ {{user.lastName}} {{user.firstName}} {{user.email}} + Please enter a search, or click on view all users diff --git a/forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl b/forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl deleted file mode 100755 index 0a138beb18..0000000000 --- a/forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl +++ /dev/null @@ -1,45 +0,0 @@ -<#import "template.ftl" as layout> -<@layout.registrationLayout displayInfo=social.displayInfo; section> - <#if section = "title"> - ${msg("imperonateTitle",(realm.name!''))} - <#elseif section = "header"> - ${msg("impersonateTitleHtml",(realm.name!''))} - <#elseif section = "form"> -
- - <#if realmList??> -
-
- -
- -
- -
-
- -
-
- -
- -
- -
-
-
-
- -
-
- - -
-
-
- - diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java index 734c472a46..4f49a176f0 100755 --- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java +++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java @@ -1,7 +1,7 @@ package org.keycloak.migration.migrators; import org.keycloak.migration.ModelVersion; -import org.keycloak.models.ImpersonationServiceConstants; +import org.keycloak.models.ImpersonationConstants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; @@ -23,7 +23,7 @@ public class MigrateTo1_4_0 { DefaultAuthenticationFlows.addFlows(realm); DefaultRequiredActions.addActions(realm); } - ImpersonationServiceConstants.setupImpersonationService(session, realm, session.getContext().getContextPath()); + ImpersonationConstants.setupImpersonationService(session, realm); } diff --git a/model/api/src/main/java/org/keycloak/models/ImpersonationServiceConstants.java b/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java similarity index 62% rename from model/api/src/main/java/org/keycloak/models/ImpersonationServiceConstants.java rename to model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java index 3f9e6ae10a..d051f0180b 100755 --- a/model/api/src/main/java/org/keycloak/models/ImpersonationServiceConstants.java +++ b/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java @@ -7,8 +7,8 @@ import org.keycloak.models.utils.KeycloakModelUtils; * @author Bill Burke * @version $Revision: 1 $ */ -public class ImpersonationServiceConstants { - public static String IMPERSONATION_ALLOWED = "impersonation"; +public class ImpersonationConstants { + public static String IMPERSONATION_ROLE = "impersonation"; public static void setupMasterRealmRole(RealmProvider model, RealmModel realm) { RealmModel adminRealm; @@ -22,8 +22,9 @@ public class ImpersonationServiceConstants { adminRole = adminRealm.getRole(AdminRoles.ADMIN); } ClientModel realmAdminApp = adminRealm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm)); - RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ALLOWED); - impersonationRole.setDescription("${role_" + IMPERSONATION_ALLOWED + "}"); + if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return; + RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE); + impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}"); adminRole.addCompositeRole(impersonationRole); } @@ -31,28 +32,17 @@ public class ImpersonationServiceConstants { if (realm.getName().equals(Config.getAdminRealm())) { return; } // don't need to do this for master realm String realmAdminApplicationClientId = Constants.REALM_MANAGEMENT_CLIENT_ID; ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId); - RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ALLOWED); - impersonationRole.setDescription("${role_" + IMPERSONATION_ALLOWED + "}"); + if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return; + RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE); + impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}"); RoleModel adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN); adminRole.addCompositeRole(impersonationRole); } - public static void setupImpersonationService(KeycloakSession session, RealmModel realm, String contextPath) { - ClientModel client = realm.getClientNameMap().get(Constants.IMPERSONATION_SERVICE_CLIENT_ID); - if (client == null) { - client = KeycloakModelUtils.createClient(realm, Constants.IMPERSONATION_SERVICE_CLIENT_ID); - client.setName("${client_" + Constants.IMPERSONATION_SERVICE_CLIENT_ID + "}"); - client.setEnabled(true); - client.setFullScopeAllowed(false); - String base = contextPath + "/realms/" + realm.getName() + "/impersonate"; - String redirectUri = base + "/*"; - client.addRedirectUri(redirectUri); - client.setBaseUrl(base); - - setupMasterRealmRole(session.realms(), realm); - setupRealmRole(realm); - } + public static void setupImpersonationService(KeycloakSession session, RealmModel realm) { + setupMasterRealmRole(session.realms(), realm); + setupRealmRole(realm); } diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index b99c1c8174..1aff2edd77 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -9,7 +9,7 @@ import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; import org.keycloak.models.BrowserSecurityHeaders; import org.keycloak.models.Constants; -import org.keycloak.models.ImpersonationServiceConstants; +import org.keycloak.models.ImpersonationConstants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; @@ -236,7 +236,7 @@ public class RealmManager { } public void setupImpersonationService(RealmModel realm) { - ImpersonationServiceConstants.setupImpersonationService(session, realm, contextPath); + ImpersonationConstants.setupImpersonationService(session, realm); } public void setupBrokerService(RealmModel realm) { diff --git a/services/src/main/java/org/keycloak/services/resources/ImpersonationService.java b/services/src/main/java/org/keycloak/services/resources/ImpersonationService.java deleted file mode 100755 index 2c1469191d..0000000000 --- a/services/src/main/java/org/keycloak/services/resources/ImpersonationService.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.keycloak.services.resources; - -import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.Config; -import org.keycloak.events.EventBuilder; -import org.keycloak.login.LoginFormsProvider; -import org.keycloak.models.ClientModel; -import org.keycloak.models.Constants; -import org.keycloak.models.ImpersonationServiceConstants; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.services.ErrorPage; -import org.keycloak.services.ForbiddenException; -import org.keycloak.services.Urls; -import org.keycloak.services.managers.AuthenticationManager; -import org.keycloak.services.messages.Messages; - -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import java.net.URI; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class ImpersonationService extends AbstractSecuredLocalService { - - public static final String UNKNOWN_USER_MESSAGE = "unknownUser"; - private EventBuilder event; - - public ImpersonationService(RealmModel realm, ClientModel client, EventBuilder event) { - super(realm, client); - this.event = event; - } - - private static Set VALID_PATHS = new HashSet(); - - static { - } - - @Override - protected Set getValidPaths() { - return VALID_PATHS; - } - - @Override - protected URI getBaseRedirectUri() { - return Urls.realmBase(uriInfo.getBaseUri()).path(RealmsResource.class, "getImpersonationService").build(realm.getName()); - } - - @GET - public Response impersonatePage() { - Response challenge = authenticateBrowser(); - if (challenge != null) return challenge; - LoginFormsProvider page = page(); - return renderPage(page); - } - - protected LoginFormsProvider page() { - UserModel user = auth.getUser(); - LoginFormsProvider page = session.getProvider(LoginFormsProvider.class) - .setActionUri(getBaseRedirectUri()) - .setAttribute("stateChecker", stateChecker); - if (realm.getName().equals(Config.getAdminRealm())) { - List realms = new LinkedList<>(); - for (RealmModel possibleRealm : session.realms().getRealms()) { - ClientModel realmAdminApp = realm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(possibleRealm)); - RoleModel role = realmAdminApp.getRole(ImpersonationServiceConstants.IMPERSONATION_ALLOWED); - if (user.hasRole(role)) { - realms.add(possibleRealm.getName()); - } - } - if (realms.isEmpty()) { - throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS)); - } - if (realms.size() > 1 || !realms.get(0).equals(realm.getName())) { - page.setAttribute("realmList", realms); - } - } else { - authorizeCurrentRealm(); - } return page; - } - - protected Response renderPage(LoginFormsProvider page) { - return page - .createForm("impersonate.ftl", new HashMap()); - } - - protected void authorizeMaster(String realmName) { - RealmModel possibleRealm = session.realms().getRealmByName(realmName); - if (possibleRealm == null) { - throw new NotFoundException("Could not find realm"); - } - ClientModel realmAdminApp = realm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(possibleRealm)); - RoleModel role = realmAdminApp.getRole(ImpersonationServiceConstants.IMPERSONATION_ALLOWED); - if (!auth.getUser().hasRole(role)) { - throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS)); - } - } - - private void authorizeCurrentRealm() { - UserModel user = auth.getUser(); - String realmAdminApplicationClientId = Constants.REALM_MANAGEMENT_CLIENT_ID; - ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId); - RoleModel role = realmAdminApp.getRole(ImpersonationServiceConstants.IMPERSONATION_ALLOWED); - if (!user.hasRole(role)) { - throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS)); - } - } - - @POST - public Response impersonate() { - Response challenge = authenticateBrowser(); - if (challenge != null) return challenge; - MultivaluedMap formData = request.getDecodedFormParameters(); - String realmName = formData.getFirst("realm"); - RealmModel chosenRealm = null; - if (realmName == null) { - chosenRealm = realm; - } else{ - chosenRealm = session.realms().getRealmByName(realmName); - if (chosenRealm == null) { - throw new NotFoundException("Could not find realm"); - } - } - - if (realm.getName().equals(Config.getAdminRealm())) { - authorizeMaster(chosenRealm.getName()); - } else { - if (realmName == null) authorizeCurrentRealm(); - else { - throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS)); - } - } - - csrfCheck(formData); - - if (formData.containsKey("cancel")) { - return renderPage(page()); - } - String username = formData.getFirst(AuthenticationManager.FORM_USERNAME); - if (username == null) { - return renderPage( - page().setError(UNKNOWN_USER_MESSAGE) - ); - } - UserModel user = session.users().getUserByUsername(username, chosenRealm); - if (user == null) { - user = session.users().getUserByEmail(username, chosenRealm); - } - if (user == null) { - return renderPage( - page().setError(UNKNOWN_USER_MESSAGE) - ); - } - // if same realm logout before impersonation - if (chosenRealm.getId().equals(realm.getId())) { - AuthenticationManager.backchannelLogout(session, realm, auth.getSession(), uriInfo, clientConnection, headers, true); - } - UserSessionModel userSession = session.sessions().createUserSession(chosenRealm, user, username, clientConnection.getRemoteAddr(), "impersonate", false, null, null); - AuthenticationManager.createLoginCookie(chosenRealm, userSession.getUser(), userSession, uriInfo, clientConnection); - URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(chosenRealm.getName()); - return Response.status(302).location(redirect).build(); - - - } -} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index a5fe0830be..ee91b43a03 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -148,22 +148,6 @@ public class RealmsResource { return accountService; } - @Path("{realm}/impersonate") - public ImpersonationService getImpersonationService(final @PathParam("realm") String name) { - RealmModel realm = init(name); - - ClientModel client = realm.getClientNameMap().get(Constants.IMPERSONATION_SERVICE_CLIENT_ID); - if (client == null || !client.isEnabled()) { - logger.debug("impersonate service not enabled"); - throw new NotFoundException("impersonate service not enabled"); - } - - EventBuilder event = new EventBuilder(realm, session, clientConnection); - ImpersonationService impersonateService = new ImpersonationService(realm, client, event); - ResteasyProviderFactory.getInstance().injectProperties(impersonateService); - return impersonateService; - } - @Path("{realm}") public PublicRealmResource getRealmResource(final @PathParam("realm") String name) { RealmModel realm = init(name); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 39ef7c3914..b0d4c7259c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -56,7 +56,7 @@ public class AuthenticationManagementResource { this.realm = realm; this.session = session; this.auth = auth; - this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER); + this.auth.init(RealmAuth.Resource.REALM); this.adminEvent = adminEvent; } @@ -140,6 +140,7 @@ public class AuthenticationManagementResource { @NoCache @Produces(MediaType.APPLICATION_JSON) public List getFlows() { + this.auth.requireView(); List flows = new LinkedList<>(); for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) { if (flow.isTopLevel()) { @@ -265,6 +266,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) { + this.auth.requireView(); AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); if (config == null) { throw new NotFoundException("Could not find authenticator config"); @@ -328,6 +330,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getRequiredActions() { + this.auth.requireView(); List list = new LinkedList<>(); for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) { RequiredActionProviderRepresentation rep = toRepresentation(model); @@ -351,6 +354,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias) { + this.auth.requireView(); RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); if (model == null) { throw new NotFoundException("Failed to find required action: " + alias); @@ -363,6 +367,7 @@ public class AuthenticationManagementResource { @PUT @Consumes(MediaType.APPLICATION_JSON) public void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep) { + this.auth.requireManage(); RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); if (model == null) { throw new NotFoundException("Failed to find required action: " + alias); @@ -381,6 +386,7 @@ public class AuthenticationManagementResource { @Path("required-actions/{alias}") @DELETE public void updateRequiredAction(@PathParam("alias") String alias) { + this.auth.requireManage(); RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); if (model == null) { throw new NotFoundException("Failed to find required action: " + alias); @@ -434,6 +440,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public AuthenticatorConfigDescription getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) { + this.auth.requireView(); ConfigurableAuthenticatorFactory factory = getConfigurableAuthenticatorFactory(providerId); if (factory == null) { throw new NotFoundException("Could not find authenticator provider"); @@ -460,6 +467,7 @@ public class AuthenticationManagementResource { @POST @NoCache public Response createAuthenticatorConfig(AuthenticatorConfigModel config) { + this.auth.requireManage(); config = realm.addAuthenticatorConfig(config); return Response.created(uriInfo.getAbsolutePathBuilder().path(config.getId()).build()).build(); } @@ -469,6 +477,7 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("id") String id) { + this.auth.requireView(); AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); if (config == null) { throw new NotFoundException("Could not find authenticator config"); @@ -480,6 +489,7 @@ public class AuthenticationManagementResource { @DELETE @NoCache public void removeAuthenticatorConfig(@PathParam("id") String id) { + this.auth.requireManage(); AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id); if (config == null) { throw new NotFoundException("Could not find authenticator config"); @@ -502,6 +512,7 @@ public class AuthenticationManagementResource { @Consumes(MediaType.APPLICATION_JSON) @NoCache public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigModel config) { + this.auth.requireManage(); AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id); if (exists == null) { throw new NotFoundException("Could not find authenticator config"); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java index f93e8c7b43..fc778d0ddf 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java @@ -2,6 +2,7 @@ package org.keycloak.services.resources.admin; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; +import org.keycloak.models.ImpersonationConstants; import org.keycloak.services.ForbiddenException; @@ -13,7 +14,7 @@ public class RealmAuth { private Resource resource; public enum Resource { - CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER + CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER, IMPERSONATION } private AdminAuth auth; @@ -29,6 +30,10 @@ public class RealmAuth { return this; } + public AdminAuth getAuth() { + return auth; + } + public void requireAny() { if (!auth.hasOneOfAppRole(realmAdminApp, AdminRoles.ALL_REALM_ROLES)) { throw new ForbiddenException(); @@ -84,6 +89,8 @@ public class RealmAuth { return AdminRoles.MANAGE_EVENTS; case IDENTITY_PROVIDER: return AdminRoles.MANAGE_IDENTITY_PROVIDERS; + case IMPERSONATION: + return ImpersonationConstants.IMPERSONATION_ROLE; default: throw new IllegalStateException(); } 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 b6f1fb179e..a56534d6f3 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 @@ -61,6 +61,7 @@ import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import javax.ws.rs.WebApplicationException; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -72,6 +73,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.keycloak.models.UsernameLoginFailureModel; import org.keycloak.services.managers.BruteForceProtector; +import org.keycloak.services.resources.AccountService; /** * Base resource for managing users @@ -276,6 +278,37 @@ public class UsersResource { return rep; } + @Path("{id}/impersonation") + @POST + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public Map impersonate(final @PathParam("id") String id) { + auth.init(RealmAuth.Resource.IMPERSONATION); + auth.requireManage(); + UserModel user = session.users().getUserById(id, realm); + if (user == null) { + throw new NotFoundException("User not found"); + } + RealmModel authenticatedRealm = auth.getAuth().getRealm(); + // if same realm logout before impersonation + boolean sameRealm = false; + if (authenticatedRealm.getId().equals(realm.getId())) { + sameRealm = true; + UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.getAuth().getToken().getSessionState()); + AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection); + AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection); + AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true); + } + UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null); + AuthenticationManager.createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection); + URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName()); + Map result = new HashMap<>(); + result.put("sameRealm", sameRealm); + result.put("redirect", redirect.toString()); + return result; + } + + /** * List set of sessions associated with this user. * diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java index 1ea081f5ce..95434c063b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -167,7 +167,7 @@ public class AccountTest { }); } - //@Test + @Test public void ideTesting() throws Exception { Thread.sleep(100000000); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java index 01a8ed9546..c30ee360b2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java @@ -42,7 +42,7 @@ public class ClientTest extends AbstractClientTest { @Test public void getClients() { - assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "impersonation"); + assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker"); } private String createClient() { @@ -59,7 +59,7 @@ public class ClientTest extends AbstractClientTest { String id = createClient(); assertNotNull(realm.clients().get(id)); - assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app", "impersonation"); + assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app"); } @Test diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java index 0141166a3f..86ca62e5eb 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java @@ -86,7 +86,7 @@ public class ImportTest extends AbstractModelTest { Assert.assertEquals(0, session.users().getFederatedIdentities(user, realm).size()); List resources = realm.getClients(); - Assert.assertEquals(8, resources.size()); + Assert.assertEquals(7, resources.size()); // Test applications imported ClientModel application = realm.getClientByClientId("Application"); @@ -97,7 +97,7 @@ public class ImportTest extends AbstractModelTest { Assert.assertNotNull(otherApp); Assert.assertNull(nonExisting); Map clients = realm.getClientNameMap(); - Assert.assertEquals(8, clients.size()); + Assert.assertEquals(7, clients.size()); Assert.assertTrue(clients.values().contains(application)); Assert.assertTrue(clients.values().contains(otherApp)); Assert.assertTrue(clients.values().contains(accountApp)); From 1ec8e63e9e09b285a5d9b83c918c9be610eaa87d Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sat, 11 Jul 2015 10:12:55 -0400 Subject: [PATCH 2/4] impersonation redo in admin console --- .../theme/base/admin/resources/partials/user-sessions.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html index 762dda9243..b7253a6509 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html @@ -1,4 +1,4 @@ -
+
-

{{user.username|capitalize}}

+

{{user.username|capitalize}}

Add User

diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html index 05496ffe6f..685549db41 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html @@ -10,7 +10,7 @@ - + - + @@ -36,7 +36,7 @@ - +
Logout All Sessions @@ -22,7 +22,7 @@
Started Last Access ClientsActionAction
logout logout
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index b0d4c7259c..e6c186bf45 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -330,7 +330,6 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public List getRequiredActions() { - this.auth.requireView(); List list = new LinkedList<>(); for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) { RequiredActionProviderRepresentation rep = toRepresentation(model); @@ -354,7 +353,6 @@ public class AuthenticationManagementResource { @Produces(MediaType.APPLICATION_JSON) @NoCache public RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias) { - this.auth.requireView(); RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); if (model == null) { throw new NotFoundException("Failed to find required action: " + alias);