keycloak-aplcache

impersonation

7/10/2015 9:38:43 PM

Changes

Details

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
new file mode 100755
index 0000000..0a138be
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl
@@ -0,0 +1,45 @@
+<#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">
+            <form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
+                <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
+                <#if realmList??>
+                <div class="${properties.kcFormGroupClass!}">
+                    <div class="${properties.kcLabelWrapperClass!}">
+                        <label for="realm" class="${properties.kcLabelClass!}">${msg("realmChoice")}</label>
+                    </div>
+
+                    <div class="${properties.kcInputWrapperClass!}">
+                        <select  class="${properties.kcInputClass!}" id="selectRealm" name="realm">
+                            <#list realmList as r>
+                            <option value="${r}">${r}</option>
+                            </#list>
+                        </select>
+                    </div>
+                </div>
+                </#if>
+                <div class="${properties.kcFormGroupClass!}">
+                    <div class="${properties.kcLabelWrapperClass!}">
+                        <label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
+                    </div>
+
+                    <div class="${properties.kcInputWrapperClass!}">
+                        <input id="username" class="${properties.kcInputClass!}" name="username" value="" type="text" autofocus />
+                    </div>
+                </div>
+                <div class="${properties.kcFormGroupClass!}">
+                    <div class="${properties.kcLabelWrapperClass!}">
+                        <label for="username" class="${properties.kcLabelClass!}"></label>
+                    </div>
+                    <div class="${properties.kcInputWrapperClass!}">
+                            <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="impersonate" id="kc-impersonate" type="submit" value="${msg("doImpersonate")}"/>
+                            <input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
+                        </div>
+                </div>
+            </form>
+    </#if>
+</@layout.registrationLayout>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
index 4fe6ab3..5fab8e3 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -9,6 +9,7 @@ doDecline=Decline
 doContinue=Continue
 doForgotPassword=Passwort vergessen?
 doClickHere=hier klicken
+doImpersonate=Impersonate
 kerberosNotConfigured=Kerberos Not Configured
 kerberosNotConfiguredTitle=Kerberos Not Configured
 bypassKerberos=Your browser is not set up for Kerberos login.  Please click continue to login in through other means
@@ -24,6 +25,10 @@ loginOauthTitle=
 loginOauthTitleHtml=Tempor\u00E4rer zugriff auf <strong>{0}</strong> angefordert von <strong>{1}</strong>.
 loginTotpTitle=Mobile Authentifizierung Einrichten
 loginProfileTitle=Benutzerkonto Informationen aktualisieren
+impersonateTitle={0} Impersonate User
+impersonateTitleHtml=<strong>{0}</strong> Impersonate User</strong>
+unknownUser=Unknown user
+realmChoice=Realm
 oauthGrantTitle=OAuth gew\u00E4hren
 oauthGrantTitleHtml=Tempor\u00E4rer zugriff auf <strong>{0}</strong> angefordert von
 errorTitle=Es tut uns leid...
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 3fdb91c..8fc0d2e 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -9,6 +9,7 @@ doAccept=Accept
 doDecline=Decline
 doForgotPassword=Forgot Password?
 doClickHere=Click here
+doImpersonate=Impersonate
 kerberosNotConfigured=Kerberos Not Configured
 kerberosNotConfiguredTitle=Kerberos Not Configured
 bypassKerberosDetail=Either you are not logged in via Kerberos or your browser is not set up for Kerberos login.  Please click continue to login in through other means
@@ -17,6 +18,10 @@ registerWithTitle=Register with {0}
 registerWithTitleHtml=Register with <strong>{0}</strong>
 loginTitle=Log in to {0}
 loginTitleHtml=Log in to <strong>{0}</strong>
+impersonateTitle={0} Impersonate User
+impersonateTitleHtml=<strong>{0}</strong> Impersonate User</strong>
+realmChoice=Realm
+unknownUser=Unknown user
 loginTotpTitle=Mobile Authenticator Setup
 loginProfileTitle=Update Account Information
 oauthGrantTitle=OAuth Grant
@@ -76,7 +81,6 @@ emailInstruction=Enter your username or email address and we will send you instr
 copyCodeInstruction=Please copy this code and paste it into your application:
 
 personalInfo=Personal Info:
-
 role_admin=Admin
 role_realm-admin=Realm Admin
 role_create-realm=Create realm
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
index b239ecc..df610f9 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
@@ -9,6 +9,7 @@ doDecline=Decline
 doContinue=Continue
 doForgotPassword=Password Dimenticata?
 doClickHere=Clicca qui
+doImpersonate=Impersonate
 bypassKerberos=Your browser is not set up for Kerberos login.  Please click continue to login in through other means
 kerberosNotSetUp=Kerberos is not set up.  You cannot login.
 kerberosNotConfigured=Kerberos Not Configured
@@ -22,6 +23,10 @@ loginTitle=Accedi a {0}
 loginTitleHtml=Accedi a <strong>{0}</strong>
 loginTotpTitle=Configura Autenticazione Mobile
 loginProfileTitle=Aggiorna Profilo
+impersonateTitle={0} Impersonate User
+impersonateTitleHtml=<strong>{0}</strong> Impersonate User</strong>
+unknownUser=Unknown user
+realmChoice=Realm
 oauthGrantTitle=OAuth Grant
 oauthGrantTitleHtml=Accesso temporaneo per <strong>{0}</strong> richiesto da
 errorTitle=Siamo spiacenti...
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index 85b28fd..9329282 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -9,6 +9,7 @@ doNo=N\u00E3o
 doContinue=Continue
 doForgotPassword=Esqueceu sua senha?
 doClickHere=Clique aqui
+doImpersonate=Impersonate
 bypassKerberos=Your browser is not set up for Kerberos login.  Please click continue to login in through other means
 kerberosNotSetUp=Kerberos is not set up.  You cannot login.
 kerberosNotConfigured=Kerberos Not Configured
@@ -20,6 +21,10 @@ registerWithTitle=Registre-se com {0}
 registerWithTitleHtml=Registre-se com <strong>{0}</strong>
 loginTitle=Entrar em {0}
 loginTitleHtml=Entrar em <strong>{0}</strong>
+impersonateTitle={0} Impersonate User
+impersonateTitleHtml=<strong>{0}</strong> Impersonate User</strong>
+unknownUser=Unknown user
+realmChoice=Realm
 loginTotpTitle=Configura\u00E7\u00E3o do autenticador mobile
 loginProfileTitle=Atualiza\u00E7\u00E3o das Informa\u00E7\u00F5es da Conta
 oauthGrantTitle=Concess\u00E3o OAuth
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 0b7e8f8..734c472 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,6 +1,7 @@
 package org.keycloak.migration.migrators;
 
 import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.ImpersonationServiceConstants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.DefaultAuthenticationFlows;
@@ -15,7 +16,6 @@ import java.util.List;
 public class MigrateTo1_4_0 {
     public static final ModelVersion VERSION = new ModelVersion("1.4.0");
 
-
     public void migrate(KeycloakSession session) {
         List<RealmModel> realms = session.realms().getRealms();
         for (RealmModel realm : realms) {
@@ -23,6 +23,7 @@ public class MigrateTo1_4_0 {
                 DefaultAuthenticationFlows.addFlows(realm);
                 DefaultRequiredActions.addActions(realm);
             }
+            ImpersonationServiceConstants.setupImpersonationService(session, realm, session.getContext().getContextPath());
 
         }
 
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index eef214f..a3104e1 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -8,6 +8,7 @@ public interface Constants {
     String ADMIN_CONSOLE_CLIENT_ID = "security-admin-console";
 
     String ACCOUNT_MANAGEMENT_CLIENT_ID = "account";
+    String IMPERSONATION_SERVICE_CLIENT_ID = "impersonation";
     String BROKER_SERVICE_CLIENT_ID = "broker";
     String REALM_MANAGEMENT_CLIENT_ID = "realm-management";
 
diff --git a/model/api/src/main/java/org/keycloak/models/ImpersonationServiceConstants.java b/model/api/src/main/java/org/keycloak/models/ImpersonationServiceConstants.java
new file mode 100755
index 0000000..3f9e6ae
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/ImpersonationServiceConstants.java
@@ -0,0 +1,59 @@
+package org.keycloak.models;
+
+import org.keycloak.Config;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ImpersonationServiceConstants {
+    public static String IMPERSONATION_ALLOWED = "impersonation";
+
+    public static void setupMasterRealmRole(RealmProvider model, RealmModel realm) {
+        RealmModel adminRealm;
+        RoleModel adminRole;
+
+        if (realm.getName().equals(Config.getAdminRealm())) {
+            adminRealm = realm;
+            adminRole = realm.getRole(AdminRoles.ADMIN);
+        } else {
+            adminRealm = model.getRealmByName(Config.getAdminRealm());
+            adminRole = adminRealm.getRole(AdminRoles.ADMIN);
+        }
+        ClientModel realmAdminApp = adminRealm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm));
+        RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ALLOWED);
+        impersonationRole.setDescription("${role_" + IMPERSONATION_ALLOWED + "}");
+        adminRole.addCompositeRole(impersonationRole);
+    }
+
+    public static void setupRealmRole(RealmModel realm) {
+        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 + "}");
+        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);
+        }
+    }
+
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
index 4d33403..ec280cf 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
@@ -10,6 +10,8 @@ import javax.ws.rs.core.UriInfo;
  */
 public interface KeycloakContext {
 
+    String getContextPath();
+
     UriInfo getUri();
 
     HttpHeaders getRequestHeaders();
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index bc1e1bd..16594fb 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -131,7 +131,7 @@ public class SamlService {
                 return ErrorPage.error(session, Messages.INVALID_REQUEST);
             }
 
-            AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
+            AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
             if (authResult == null) {
                 logger.warn("Unknown saml response.");
                 event.event(EventType.LOGOUT);
@@ -354,7 +354,7 @@ public class SamlService {
             }
 
             // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
-            AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
+            AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
             if (authResult != null) {
                 String logoutBinding = getBindingType();
                 if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING)))
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
index 7e68a02..bbeb73e 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
@@ -21,7 +21,7 @@ public class CookieAuthenticator implements Authenticator {
     @Override
     public void authenticate(AuthenticatorContext context) {
         AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(context.getSession(),
-                context.getRealm(), context.getUriInfo(), context.getConnection(), context.getHttpRequest().getHttpHeaders(), true);
+                context.getRealm(), true);
         if (authResult == null) {
             context.attempted();
         } else {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 25ccda5..e86895f 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -117,7 +117,7 @@ public class LogoutEndpoint {
         }
 
         // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
-        AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
+        AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
         if (authResult != null) {
             userSession = userSession != null ? userSession : authResult.getSession();
             if (redirect != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirect);
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
index 99849a9..f592f03 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
@@ -5,6 +5,7 @@ import org.keycloak.ClientConnection;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakContext;
 import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.KeycloakApplication;
 
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.UriInfo;
@@ -21,6 +22,12 @@ public class DefaultKeycloakContext implements KeycloakContext {
     private ClientConnection connection;
 
     @Override
+    public String getContextPath() {
+        KeycloakApplication app = ResteasyProviderFactory.getContextData(KeycloakApplication.class);
+        return app.getContextPath();
+    }
+
+    @Override
     public UriInfo getUri() {
         return ResteasyProviderFactory.getContextData(UriInfo.class);
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
index 9e0e4ab..457388b 100755
--- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
@@ -18,12 +18,12 @@ public class AppAuthManager extends AuthenticationManager {
     protected static Logger logger = Logger.getLogger(AppAuthManager.class);
 
     @Override
-    public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
-        AuthResult authResult = super.authenticateIdentityCookie(session, realm, uriInfo, connection, headers);
+    public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm) {
+        AuthResult authResult = super.authenticateIdentityCookie(session, realm);
         if (authResult == null) return null;
         // refresh the cookies!
-        createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, connection);
-        if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, authResult.getUser().getUsername(), uriInfo, connection);
+        createLoginCookie(realm, authResult.getUser(), authResult.getSession(), session.getContext().getUri(), session.getContext().getConnection());
+        if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, authResult.getUser().getUsername(), session.getContext().getUri(), session.getContext().getConnection());
         return authResult;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 9695a4e..0bb41c1 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -359,21 +359,21 @@ public class AuthenticationManager {
         CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly);
     }
 
-    public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
-        return authenticateIdentityCookie(session, realm, uriInfo, connection, headers, true);
+    public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm) {
+        return authenticateIdentityCookie(session, realm, true);
     }
 
-    public static AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers, boolean checkActive) {
-        Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
+    public static AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, boolean checkActive) {
+        Cookie cookie = session.getContext().getRequestHeaders().getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
         if (cookie == null || "".equals(cookie.getValue())) {
             logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
             return null;
         }
 
         String tokenString = cookie.getValue();
-        AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString, headers);
+        AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, tokenString, session.getContext().getRequestHeaders());
         if (authResult == null) {
-            expireIdentityCookie(realm, uriInfo, connection);
+            expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
             return null;
         }
         authResult.getSession().setLastSessionRefresh(Time.currentTime());
@@ -399,9 +399,9 @@ public class AuthenticationManager {
                 }
             }
         }
-        if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
         // refresh the cookies!
         createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
+        if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
         if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
         protocol.setRealm(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 99b893c..b99c1c8 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -9,6 +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.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
@@ -88,6 +89,7 @@ public class RealmManager {
         setupAccountManagement(realm);
         setupBrokerService(realm);
         setupAdminConsole(realm);
+        setupImpersonationService(realm);
         setupAuthenticationFlows(realm);
         setupRequiredActions(realm);
 
@@ -233,6 +235,10 @@ public class RealmManager {
         }
     }
 
+    public void setupImpersonationService(RealmModel realm) {
+        ImpersonationServiceConstants.setupImpersonationService(session, realm, contextPath);
+    }
+
     public void setupBrokerService(RealmModel realm) {
         ClientModel client = realm.getClientNameMap().get(Constants.BROKER_SERVICE_CLIENT_ID);
         if (client == null) {
@@ -261,6 +267,8 @@ public class RealmManager {
         setupMasterAdminManagement(realm);
         if (!hasRealmAdminManagementClient(rep)) setupRealmAdminManagement(realm);
         if (!hasAccountManagementClient(rep)) setupAccountManagement(realm);
+        if (!hasImpersonationServiceClient(rep)) setupImpersonationService(realm);
+
         if (!hasBrokerClient(rep)) setupBrokerService(realm);
         if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
 
@@ -297,6 +305,15 @@ public class RealmManager {
         }
         return false;
     }
+    private boolean hasImpersonationServiceClient(RealmRepresentation rep) {
+        if (rep.getClients() == null) return false;
+        for (ClientRepresentation clientRep : rep.getClients()) {
+            if (clientRep.getClientId().equals(Constants.IMPERSONATION_SERVICE_CLIENT_ID)) {
+                return true;
+            }
+        }
+        return false;
+    }
     private boolean hasBrokerClient(RealmRepresentation rep) {
         if (rep.getClients() == null) return false;
         for (ClientRepresentation clientRep : rep.getClients()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
new file mode 100755
index 0000000..6c7b5a0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
@@ -0,0 +1,246 @@
+package org.keycloak.services.resources;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.AbstractOAuthClient;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.Urls;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.Auth;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.util.CookieHelper;
+import org.keycloak.util.UriUtils;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Helper class for securing local services.  Provides login basics as well as CSRF check basics
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractSecuredLocalService {
+    private static final Logger logger = Logger.getLogger(AbstractSecuredLocalService.class);
+    protected final ClientModel client;
+    protected RealmModel realm;
+
+    @Context
+    protected UriInfo uriInfo;
+    @Context
+    protected HttpHeaders headers;
+    @Context
+    protected ClientConnection clientConnection;
+    protected String stateChecker;
+    @Context
+    protected KeycloakSession session;
+    @Context
+    protected HttpRequest request;
+    protected Auth auth;
+
+    public AbstractSecuredLocalService(RealmModel realm, ClientModel client) {
+        this.realm = realm;
+        this.client = client;
+    }
+
+    @Path("login-redirect")
+    @GET
+    public Response loginRedirect(@QueryParam("code") String code,
+                                  @QueryParam("state") String state,
+                                  @QueryParam("error") String error,
+                                  @QueryParam("path") String path,
+                                  @QueryParam("referrer") String referrer,
+                                  @Context HttpHeaders headers) {
+        try {
+            if (error != null) {
+                logger.debug("error from oauth");
+                throw new ForbiddenException("error");
+            }
+            if (path != null && !getValidPaths().contains(path)) {
+                throw new BadRequestException("Invalid path");
+            }
+            if (!realm.isEnabled()) {
+                logger.debug("realm not enabled");
+                throw new ForbiddenException();
+            }
+            if (!client.isEnabled()) {
+                logger.debug("account management app not enabled");
+                throw new ForbiddenException();
+            }
+            if (code == null) {
+                logger.debug("code not specified");
+                throw new BadRequestException("code not specified");
+            }
+            if (state == null) {
+                logger.debug("state not specified");
+                throw new BadRequestException("state not specified");
+            }
+
+            URI uri = getBaseRedirectUri();
+            URI redirectUri = path != null ? uri.resolve(path) : uri;
+            if (referrer != null) {
+                redirectUri = redirectUri.resolve("?referrer=" + referrer);
+            }
+
+            return Response.status(302).location(redirectUri).build();
+        } finally {
+        }
+    }
+
+    protected void updateCsrfChecks() {
+        Cookie cookie = headers.getCookies().get(AccountService.KEYCLOAK_STATE_CHECKER);
+        if (cookie != null) {
+            stateChecker = cookie.getValue();
+        } else {
+            stateChecker = UUID.randomUUID().toString();
+            String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
+            boolean secureOnly = realm.getSslRequired().isRequired(clientConnection);
+            CookieHelper.addCookie(AccountService.KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);
+        }
+    }
+
+    protected abstract Set<String> getValidPaths();
+
+    /**
+     * Check to see if form post has sessionId hidden field and match it against the session id.
+     *
+     * @param formData
+     */
+    protected void csrfCheck(final MultivaluedMap<String, String> formData) {
+        if (!auth.isCookieAuthenticated()) return;
+        String stateChecker = formData.getFirst("stateChecker");
+        if (!this.stateChecker.equals(stateChecker)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+    /**
+     * Check to see if form post has sessionId hidden field and match it against the session id.
+     *
+     */
+    protected void csrfCheck(String stateChecker) {
+        if (!auth.isCookieAuthenticated()) return;
+        if (auth.getSession() == null) return;
+        if (!this.stateChecker.equals(stateChecker)) {
+            throw new ForbiddenException();
+        }
+
+    }
+
+    protected abstract URI getBaseRedirectUri();
+
+    protected Response login(String path) {
+        OAuthRedirect oauth = new OAuthRedirect();
+        String authUrl = OIDCLoginProtocolService.authUrl(uriInfo).build(realm.getName()).toString();
+        oauth.setAuthUrl(authUrl);
+
+        oauth.setClientId(client.getClientId());
+
+        UriBuilder uriBuilder = UriBuilder.fromUri(getBaseRedirectUri()).path("login-redirect");
+
+        if (path != null) {
+            uriBuilder.queryParam("path", path);
+        }
+
+        String referrer = uriInfo.getQueryParameters().getFirst("referrer");
+        if (referrer != null) {
+            uriBuilder.queryParam("referrer", referrer);
+        }
+
+        String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
+        if (referrerUri != null) {
+            uriBuilder.queryParam("referrer_uri", referrerUri);
+        }
+
+        URI accountUri = uriBuilder.build(realm.getName());
+
+        oauth.setStateCookiePath(accountUri.getRawPath());
+        return oauth.redirect(uriInfo, accountUri.toString());
+    }
+
+    protected Response authenticateBrowser() {
+        AppAuthManager authManager = new AppAuthManager();
+        AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
+        if (authResult != null) {
+            auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
+        } else {
+            return login(null);
+        }
+        // don't allow cors requests
+        // This is to prevent CSRF attacks.
+        String requestOrigin = UriUtils.getOrigin(uriInfo.getBaseUri());
+        String origin = headers.getRequestHeaders().getFirst("Origin");
+        if (origin != null && !requestOrigin.equals(origin)) {
+            throw new ForbiddenException();
+        }
+
+        if (!request.getHttpMethod().equals("GET")) {
+            String referrer = headers.getRequestHeaders().getFirst("Referer");
+            if (referrer != null && !requestOrigin.equals(UriUtils.getOrigin(referrer))) {
+                throw new ForbiddenException();
+            }
+        }
+        updateCsrfChecks();
+        return null;
+    }
+
+    static class OAuthRedirect extends AbstractOAuthClient {
+
+        /**
+         * closes client
+         */
+        public void stop() {
+        }
+
+        public Response redirect(UriInfo uriInfo, String redirectUri) {
+            String state = getStateCode();
+
+            UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
+                    .queryParam(OAuth2Constants.CLIENT_ID, clientId)
+                    .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
+                    .queryParam(OAuth2Constants.STATE, state)
+                    .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE);
+            if (scope != null) {
+                uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
+            }
+
+            URI url = uriBuilder.build();
+
+            // todo httpOnly!
+            NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure);
+            logger.debug("NewCookie: " + cookie.toString());
+            logger.debug("Oauth Redirect to: " + url);
+            return Response.status(302)
+                    .location(url)
+                    .cookie(cookie).build();
+        }
+
+        private String getStateCookiePath(UriInfo uriInfo) {
+            if (stateCookiePath != null) return stateCookiePath;
+            return uriInfo.getBaseUri().getRawPath();
+        }
+
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 02762af..05f8c81 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -98,7 +98,7 @@ import java.util.UUID;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class AccountService {
+public class AccountService extends AbstractSecuredLocalService {
 
     private static final Logger logger = Logger.getLogger(AccountService.class);
 
@@ -128,34 +128,13 @@ public class AccountService {
 
     public static final String KEYCLOAK_STATE_CHECKER = "KEYCLOAK_STATE_CHECKER";
 
-    private RealmModel realm;
-
-    @Context
-    private HttpRequest request;
-
-    @Context
-    protected HttpHeaders headers;
-
-    @Context
-    private UriInfo uriInfo;
-
-    @Context
-    private ClientConnection clientConnection;
-
-    @Context
-    private KeycloakSession session;
-
     private final AppAuthManager authManager;
-    private final ClientModel client;
     private EventBuilder event;
     private AccountProvider account;
-    private Auth auth;
     private EventStoreProvider eventStore;
-    private String stateChecker;
 
     public AccountService(RealmModel realm, ClientModel client, EventBuilder event) {
-        this.realm = realm;
-        this.client = client;
+        super(realm, client);
         this.event = event;
         this.authManager = new AppAuthManager();
     }
@@ -169,18 +148,10 @@ public class AccountService {
         if (authResult != null) {
             auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false);
         } else {
-            authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers);
+            authResult = authManager.authenticateIdentityCookie(session, realm);
             if (authResult != null) {
                 auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
-                Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
-                if (cookie != null) {
-                    stateChecker = cookie.getValue();
-                } else {
-                    stateChecker = UUID.randomUUID().toString();
-                    String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
-                    boolean secureOnly = realm.getSslRequired().isRequired(clientConnection);
-                    CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);
-                }
+                updateCsrfChecks();
                 account.setStateChecker(stateChecker);
             }
         }
@@ -236,10 +207,18 @@ public class AccountService {
         return base;
     }
 
+    public static UriBuilder accountServiceApplicationPage(UriInfo uriInfo) {
+        return accountServiceBaseUrl(uriInfo).path(AccountService.class, "applicationsPage");
+    }
+
     public static UriBuilder accountServiceBaseUrl(UriBuilder base) {
         return base.path(RealmsResource.class).path(RealmsResource.class, "getAccountService");
     }
 
+    protected Set<String> getValidPaths() {
+        return AccountService.VALID_PATHS;
+    }
+
     private Response forwardToPage(String path, AccountPages page) {
         if (auth != null) {
             try {
@@ -368,33 +347,6 @@ public class AccountService {
     }
 
     /**
-     * Check to see if form post has sessionId hidden field and match it against the session id.
-     *
-     * @param formData
-     */
-    protected void csrfCheck(final MultivaluedMap<String, String> formData) {
-        if (!auth.isCookieAuthenticated()) return;
-        String stateChecker = formData.getFirst("stateChecker");
-        if (!this.stateChecker.equals(stateChecker)) {
-            throw new ForbiddenException();
-        }
-
-    }
-
-    /**
-     * Check to see if form post has sessionId hidden field and match it against the session id.
-     *
-     */
-    protected void csrfCheck(String stateChecker) {
-        if (!auth.isCookieAuthenticated()) return;
-        if (auth.getSession() == null) return;
-        if (!this.stateChecker.equals(stateChecker)) {
-            throw new ForbiddenException();
-        }
-
-    }
-
-    /**
      * Update account information.
      *
      * Form params:
@@ -799,77 +751,9 @@ public class AccountService {
         return RealmsResource.accountUrl(base).path(AccountService.class, "loginRedirect");
     }
 
-    @Path("login-redirect")
-    @GET
-    public Response loginRedirect(@QueryParam("code") String code,
-                                  @QueryParam("state") String state,
-                                  @QueryParam("error") String error,
-                                  @QueryParam("path") String path,
-                                  @QueryParam("referrer") String referrer,
-                                  @Context HttpHeaders headers) {
-        try {
-            if (error != null) {
-                logger.debug("error from oauth");
-                throw new ForbiddenException("error");
-            }
-            if (path != null && !VALID_PATHS.contains(path)) {
-                throw new BadRequestException("Invalid path");
-            }
-            if (!realm.isEnabled()) {
-                logger.debug("realm not enabled");
-                throw new ForbiddenException();
-            }
-            if (!client.isEnabled()) {
-                logger.debug("account management app not enabled");
-                throw new ForbiddenException();
-            }
-            if (code == null) {
-                logger.debug("code not specified");
-                throw new BadRequestException("code not specified");
-            }
-            if (state == null) {
-                logger.debug("state not specified");
-                throw new BadRequestException("state not specified");
-            }
-
-            URI accountUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
-            URI redirectUri = path != null ? accountUri.resolve(path) : accountUri;
-            if (referrer != null) {
-                redirectUri = redirectUri.resolve("?referrer=" + referrer);
-            }
-
-            return Response.status(302).location(redirectUri).build();
-        } finally {
-        }
-    }
-
-    private Response login(String path) {
-        OAuthRedirect oauth = new OAuthRedirect();
-        String authUrl = OIDCLoginProtocolService.authUrl(uriInfo).build(realm.getName()).toString();
-        oauth.setAuthUrl(authUrl);
-
-        oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
-
-        UriBuilder uriBuilder = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect");
-
-        if (path != null) {
-            uriBuilder.queryParam("path", path);
-        }
-
-        String referrer = uriInfo.getQueryParameters().getFirst("referrer");
-        if (referrer != null) {
-            uriBuilder.queryParam("referrer", referrer);
-        }
-
-        String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
-        if (referrerUri != null) {
-            uriBuilder.queryParam("referrer_uri", referrerUri);
-        }
-
-        URI accountUri = uriBuilder.build(realm.getName());
-
-        oauth.setStateCookiePath(accountUri.getRawPath());
-        return oauth.redirect(uriInfo, accountUri.toString());
+    @Override
+    protected URI getBaseRedirectUri() {
+        return Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
     }
 
     public static boolean isPasswordSet(UserModel user) {
@@ -954,43 +838,4 @@ public class AccountService {
             }
         }
     }
-
-    class OAuthRedirect extends AbstractOAuthClient {
-
-        /**
-         * closes client
-         */
-        public void stop() {
-        }
-
-        public Response redirect(UriInfo uriInfo, String redirectUri) {
-            String state = getStateCode();
-
-            UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
-                    .queryParam(OAuth2Constants.CLIENT_ID, clientId)
-                    .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
-                    .queryParam(OAuth2Constants.STATE, state)
-                    .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE);
-            if (scope != null) {
-                uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
-            }
-
-            URI url = uriBuilder.build();
-
-            // todo httpOnly!
-            NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure);
-            logger.debug("NewCookie: " + cookie.toString());
-            logger.debug("Oauth Redirect to: " + url);
-            return Response.status(302)
-                    .location(url)
-                    .cookie(cookie).build();
-        }
-
-        private String getStateCookiePath(UriInfo uriInfo) {
-            if (stateCookiePath != null) return stateCookiePath;
-            return uriInfo.getBaseUri().getRawPath();
-        }
-
-    }
-
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/ImpersonationService.java b/services/src/main/java/org/keycloak/services/resources/ImpersonationService.java
new file mode 100755
index 0000000..2c14691
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/ImpersonationService.java
@@ -0,0 +1,177 @@
+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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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<String> VALID_PATHS = new HashSet<String>();
+
+    static {
+    }
+
+    @Override
+    protected Set<String> 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<String> 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<String, Object>());
+    }
+
+    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<String, String> 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/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 0e32fe8..0601882 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -61,13 +61,14 @@ public class KeycloakApplication extends Application {
     public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
         loadConfig();
 
+        this.contextPath = context.getContextPath();
         this.sessionFactory = createSessionFactory();
 
         dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this);
-        this.contextPath = context.getContextPath();
         BruteForceProtector protector = new BruteForceProtector(sessionFactory);
         dispatcher.getDefaultContextObjects().put(BruteForceProtector.class, protector);
         ResteasyProviderFactory.pushContext(BruteForceProtector.class, protector); // for injection
+        ResteasyProviderFactory.pushContext(KeycloakApplication.class, this); // for injection
         protector.start();
         context.setAttribute(BruteForceProtector.class.getName(), protector);
         context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory);
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 656c4dc..a5fe083 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -4,7 +4,6 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.ClientConnection;
-import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
@@ -149,6 +148,22 @@ 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/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index 2934375..5254993 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -172,7 +172,7 @@ public class Urls {
         return realmBase(baseUri).path("{realm}").build(realmId).toString();
     }
 
-    private static UriBuilder realmBase(URI baseUri) {
+    public static UriBuilder realmBase(URI baseUri) {
         return UriBuilder.fromUri(baseUri).path(RealmsResource.class);
     }