From 6e55604dc3d3c4d04c46993cd966e76fb0d235ad Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 10 Jul 2015 20:38:43 -0400 Subject: [PATCH] impersonation --- .../theme/base/login/impersonate.ftl | 45 ++++ .../login/messages/messages_de.properties | 5 + .../login/messages/messages_en.properties | 6 +- .../login/messages/messages_it.properties | 5 + .../login/messages/messages_pt_BR.properties | 5 + .../migration/migrators/MigrateTo1_4_0.java | 3 +- .../java/org/keycloak/models/Constants.java | 1 + .../models/ImpersonationServiceConstants.java | 59 +++++ .../org/keycloak/models/KeycloakContext.java | 2 + .../keycloak/protocol/saml/SamlService.java | 4 +- .../authenticators/CookieAuthenticator.java | 2 +- .../oidc/endpoints/LogoutEndpoint.java | 2 +- .../services/DefaultKeycloakContext.java | 7 + .../main/java/org/keycloak/services/Urls.java | 2 +- .../services/managers/AppAuthManager.java | 8 +- .../managers/AuthenticationManager.java | 14 +- .../services/managers/RealmManager.java | 17 ++ .../AbstractSecuredLocalService.java | 246 ++++++++++++++++++ .../services/resources/AccountService.java | 185 ++----------- .../resources/ImpersonationService.java | 177 +++++++++++++ .../resources/KeycloakApplication.java | 3 +- .../services/resources/RealmsResource.java | 17 +- 22 files changed, 625 insertions(+), 190 deletions(-) create mode 100755 forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl create mode 100755 model/api/src/main/java/org/keycloak/models/ImpersonationServiceConstants.java create mode 100755 services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java create mode 100755 services/src/main/java/org/keycloak/services/resources/ImpersonationService.java 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 0000000000..0a138beb18 --- /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"> +
+ + <#if realmList??> +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+
+ + +
+
+
+ + 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 4fe6ab3fcf..5fab8e33f0 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 {0} angefordert von {1}. loginTotpTitle=Mobile Authentifizierung Einrichten loginProfileTitle=Benutzerkonto Informationen aktualisieren +impersonateTitle={0} Impersonate User +impersonateTitleHtml={0} Impersonate User +unknownUser=Unknown user +realmChoice=Realm oauthGrantTitle=OAuth gew\u00E4hren oauthGrantTitleHtml=Tempor\u00E4rer zugriff auf {0} 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 3fdb91c48f..8fc0d2ee40 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 {0} loginTitle=Log in to {0} loginTitleHtml=Log in to {0} +impersonateTitle={0} Impersonate User +impersonateTitleHtml={0} Impersonate User +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 b239ecc31a..df610f92aa 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 {0} loginTotpTitle=Configura Autenticazione Mobile loginProfileTitle=Aggiorna Profilo +impersonateTitle={0} Impersonate User +impersonateTitleHtml={0} Impersonate User +unknownUser=Unknown user +realmChoice=Realm oauthGrantTitle=OAuth Grant oauthGrantTitleHtml=Accesso temporaneo per {0} 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 85b28fd553..9329282e47 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 {0} loginTitle=Entrar em {0} loginTitleHtml=Entrar em {0} +impersonateTitle={0} Impersonate User +impersonateTitleHtml={0} Impersonate User +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 0b7e8f825e..734c472a46 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 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 eef214f04a..a3104e14d9 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 0000000000..3f9e6ae10a --- /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 Bill Burke + * @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 4d33403586..ec280cfcb2 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 bc1e1bda9a..16594fbda6 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 7e68a0221c..bbeb73ed4a 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 25ccda58ee..e86895f5a3 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 99849a90db..f592f0338b 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; @@ -20,6 +21,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/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java index 293437581c..5254993e0c 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); } 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 9e0e4ab161..457388ba50 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 9695a4efd9..0bb41c17a2 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 99b893c0ba..b99c1c8174 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 0000000000..6c7b5a09a0 --- /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 Bill Burke + * @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 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 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 02762af48d..05f8c812d9 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 Stian Thorgersen */ -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 getValidPaths() { + return AccountService.VALID_PATHS; + } + private Response forwardToPage(String path, AccountPages page) { if (auth != null) { try { @@ -367,33 +346,6 @@ public class AccountService { return forwardToPage("applications", AccountPages.APPLICATIONS); } - /** - * Check to see if form post has sessionId hidden field and match it against the session id. - * - * @param formData - */ - protected void csrfCheck(final MultivaluedMap 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. * @@ -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 0000000000..2c1469191d --- /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 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/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 0e32fe8982..0601882213 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 656c4dccd7..a5fe0830be 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);