commit
aeab25b7fe
24 changed files with 629 additions and 194 deletions
45
forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl
Executable file
45
forms/common-themes/src/main/resources/theme/base/login/impersonate.ftl
Executable file
|
@ -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>
|
|
@ -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...
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -10,6 +10,8 @@ import javax.ws.rs.core.UriInfo;
|
|||
*/
|
||||
public interface KeycloakContext {
|
||||
|
||||
String getContextPath();
|
||||
|
||||
UriInfo getUri();
|
||||
|
||||
HttpHeaders getRequestHeaders();
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
@ -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<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.
|
||||
*
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
177
services/src/main/java/org/keycloak/services/resources/ImpersonationService.java
Executable file
177
services/src/main/java/org/keycloak/services/resources/ImpersonationService.java
Executable file
|
@ -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();
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ClientTest extends AbstractClientTest {
|
|||
|
||||
@Test
|
||||
public void getClients() {
|
||||
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker");
|
||||
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "impersonation");
|
||||
}
|
||||
|
||||
private String createClient() {
|
||||
|
@ -59,7 +59,7 @@ public class ClientTest extends AbstractClientTest {
|
|||
String id = createClient();
|
||||
|
||||
assertNotNull(realm.clients().get(id));
|
||||
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app");
|
||||
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app", "impersonation");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -86,7 +86,7 @@ public class ImportTest extends AbstractModelTest {
|
|||
Assert.assertEquals(0, session.users().getFederatedIdentities(user, realm).size());
|
||||
|
||||
List<ClientModel> resources = realm.getClients();
|
||||
Assert.assertEquals(7, resources.size());
|
||||
Assert.assertEquals(8, resources.size());
|
||||
|
||||
// Test applications imported
|
||||
ClientModel application = realm.getClientByClientId("Application");
|
||||
|
@ -97,7 +97,7 @@ public class ImportTest extends AbstractModelTest {
|
|||
Assert.assertNotNull(otherApp);
|
||||
Assert.assertNull(nonExisting);
|
||||
Map<String, ClientModel> clients = realm.getClientNameMap();
|
||||
Assert.assertEquals(7, clients.size());
|
||||
Assert.assertEquals(8, clients.size());
|
||||
Assert.assertTrue(clients.values().contains(application));
|
||||
Assert.assertTrue(clients.values().contains(otherApp));
|
||||
Assert.assertTrue(clients.values().contains(accountApp));
|
||||
|
|
Loading…
Reference in a new issue