keycloak-aplcache

impersonation redo in admin console

7/11/2015 11:10:05 AM

Changes

forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl 45(+0 -45)

services/src/main/java/org/keycloak/services/resources/ImpersonationService.java 177(+0 -177)

Details

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 e9341b3..335bef3 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 93e321b..9d807a9 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 3107101..bd1fe98 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 6779d42..19f79af 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 @@
                 <button kc-cancel data-ng-click="cancel()">Cancel</button>
             </div>
 
-            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
-                <button kc-save  data-ng-show="changed">Save</button>
-                <button kc-reset data-ng-show="changed">Cancel</button>
+            <div class="col-md-10 col-md-offset-2" data-ng-show="!create">
+                <button kc-save  data-ng-show="access.manageUsers && changed">Save</button>
+                <button kc-reset data-ng-show="access.manageUsers && changed">Cancel</button>
+                <button data-ng-show="access.impersonation" class="btn btn-default" data-ng-click="impersonate()">Impersonate</button>
             </div>
         </div>
 
     </form>
 </div>
 
-<kc-menu></kc-menu>
+<kc-menu></kc-menu>
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 9a042e3..dc2c4ac 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 @@
         <caption data-ng-show="users" class="hidden">Table of realm users</caption>
         <thead>
         <tr>
-            <th colspan="4">
+            <th colspan="{{access.impersonation == true ? '5' : '4'}}">
                 <div class="form-inline">
                     <div class="form-group">
                         <div class="input-group">
@@ -17,7 +17,7 @@
                     </div>
                     <button class="btn btn-primary" ng-click="query.search = null; firstPage()">View all users</button>
 
-                    <div class="pull-right">
+                    <div class="pull-right" data-ng-show="access.manageUsers">
                         <a class="btn btn-primary" href="#/create/user/{{realm.realm}}">Add User</a>
                     </div>
                 </div>
@@ -29,6 +29,7 @@
             <th>Last Name</th>
             <th>First Name</th>
             <th>Email</th>
+            <th data-ng-show="access.impersonation"></th>
         </tr>
         </tr>
         </thead>
@@ -49,6 +50,7 @@
             <td>{{user.lastName}}</td>
             <td>{{user.firstName}}</td>
             <td>{{user.email}}</td>
+            <td data-ng-show="access.impersonation"><button class="btn btn-default" data-ng-click="impersonate(user.id)">Impersonate</button></td>
         </tr>
         <tr data-ng-show="!users || users.length == 0">
             <td class="text-muted" data-ng-show="!users">Please enter a search, or click on view all users</td>
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 734c472..4f49a17 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/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index b99c1c8..1aff2ed 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/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 39ef7c3..b0d4c72 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<AuthenticationFlowModel> getFlows() {
+        this.auth.requireView();
         List<AuthenticationFlowModel> 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<RequiredActionProviderRepresentation> getRequiredActions() {
+        this.auth.requireView();
         List<RequiredActionProviderRepresentation> 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 f93e8c7..fc778d0 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 b6f1fb1..a56534d 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<String, Object> 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<String, Object> 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/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index a5fe083..ee91b43 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/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 1ea081f..95434c0 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 01a8ed9..c30ee36 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 0141166..86ca62e 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<ClientModel> 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<String, ClientModel> 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));