diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java new file mode 100644 index 00000000000..6da014a3ca5 --- /dev/null +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java @@ -0,0 +1,76 @@ +package org.keycloak.freemarker; + +import org.jboss.logging.Logger; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.UriInfo; +import java.util.*; + +/** + * Created by michigerber on 23.02.15. + */ +public class LocaleHelper { + private final static String LOCALE_COOKIE = "KEYCLOAK_LOCALE"; + private final static Logger LOGGER = Logger.getLogger(LocaleHelper.class); + private static final String LOCALE_PARAM = "ui_locale"; + + public static Locale getLocale(RealmModel realm, UserModel user, UriInfo uriInfo, HttpHeaders httpHeaders) { + + //1. Locale cookie + if(httpHeaders.getCookies().containsKey(LOCALE_COOKIE)){ + String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue(); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + + //2. User profile + if(user.getAttributes().containsKey(UserModel.LOCALE)){ + String localeString = user.getAttribute(UserModel.LOCALE); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + + //3. ui_locales query parameter + if(uriInfo.getQueryParameters().containsKey(LOCALE_PARAM)){ + String localeString = uriInfo.getQueryParameters().getFirst(LOCALE_PARAM); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + + //4. Accept-Language http header + if(httpHeaders.getLanguage() != null){ + String localeString =httpHeaders.getLanguage().toLanguageTag(); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + + //5. Default realm locale + return Locale.forLanguageTag(realm.getDefaultLocale()); + } + + private static Locale findLocale(String localeString, Set supportedLocales) { + List locales = new ArrayList(); + for(String l : supportedLocales) { + locales.add(Locale.forLanguageTag(l)); + } + return Locale.lookup(Locale.LanguageRange.parse(localeString),locales); + } +} diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java index 3dbb0a808dc..3ad511c1315 100755 --- a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java +++ b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java @@ -1,6 +1,5 @@ package org.keycloak.theme; -import org.jboss.logging.Logger; import org.keycloak.freemarker.Theme; import java.io.File; @@ -9,14 +8,13 @@ import java.io.InputStream; import java.net.URL; import java.util.Locale; import java.util.Properties; +import java.util.ResourceBundle; /** * @author Stian Thorgersen */ public class ClassLoaderTheme implements Theme { - private static final Logger LOGGER = Logger.getLogger(ClassLoaderTheme.class); - private String name; private String parentName; @@ -110,20 +108,14 @@ public class ClassLoaderTheme implements Theme { public Properties getMessages(Locale locale) throws IOException { Properties m = new Properties(); - URL url = null; - if(locale != null) { - URL messageFile = classLoader.getResource(this.messageRoot + "messages_" + locale.toString() + ".properties"); - if(messageFile != null){ - url = messageFile; - }else{ - LOGGER.warnf("Can not find message file %s", messageFile); - } - } - - if(url == null){ - url = classLoader.getResource(this.messageRoot + "messages.properties"); + String message = null; + if(locale != null){ + message = this.messageRoot + "messages_" + locale.toString() + ".properties"; + }else{ + message = this.messageRoot + "messages.properties"; } + URL url = classLoader.getResource(message); if (url != null) { m.load(url.openStream()); } diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java index 766bba28176..dbba9d9dc98 100644 --- a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java +++ b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java @@ -1,6 +1,5 @@ package org.keycloak.theme; -import org.jboss.logging.Logger; import org.keycloak.freemarker.Theme; import java.io.File; @@ -16,7 +15,6 @@ import java.util.Properties; */ public class FolderTheme implements Theme { - private static final Logger LOGGER = Logger.getLogger(FolderTheme.class); private String parentName; private String importName; private File themeDir; @@ -87,20 +85,14 @@ public class FolderTheme implements Theme { public Properties getMessages(Locale locale) throws IOException { Properties m = new Properties(); - File file = null; - if(locale != null) { - File messageFile = new File(themeDir, "messages" + File.separator + "messages_" + locale.toString() + ".properties"); - if (messageFile.isFile()) { - file = messageFile; - } else { - LOGGER.warnf("Can not find message file %s", messageFile); - } - } - - if(file == null){ - file = new File(themeDir, "messages" + File.separator + "messages.properties"); + String message = null; + if(locale != null){ + message = "messages_" + locale.toString() + ".properties"; + }else{ + message = "messages.properties"; } + File file = new File(themeDir, "messages" + File.separator + message); if (file.isFile()) { m.load(new FileInputStream(file)); } diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java index 45f3adb1b8e..f09efa7cf51 100755 --- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java +++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java @@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -21,6 +22,8 @@ public interface LoginFormsProvider extends Provider { public LoginFormsProvider setUriInfo(UriInfo uriInfo); + public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders); + public Response createResponse(UserModel.RequiredAction action); public Response createLogin(); diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java index 587dd0bdc2c..8c9ead899e4 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java @@ -5,11 +5,7 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.keycloak.OAuth2Constants; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; -import org.keycloak.freemarker.BrowserSecurityHeaderSetup; -import org.keycloak.freemarker.FreeMarkerException; -import org.keycloak.freemarker.FreeMarkerUtil; -import org.keycloak.freemarker.Theme; -import org.keycloak.freemarker.ThemeProvider; +import org.keycloak.freemarker.*; import org.keycloak.login.LoginFormsPages; import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.freemarker.model.ClientBean; @@ -31,11 +27,7 @@ import org.keycloak.models.UserModel; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Urls; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.core.*; import java.io.IOException; import java.net.URI; import java.util.*; @@ -74,6 +66,8 @@ import java.util.concurrent.TimeUnit; private UriInfo uriInfo; + private HttpHeaders httpHeaders; + public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { this.session = session; this.freeMarker = freeMarker; @@ -89,6 +83,12 @@ import java.util.concurrent.TimeUnit; return this; } + @Override + public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + return this; + } + public Response createResponse(UserModel.RequiredAction action) { String actionMessage; LoginFormsPages page; @@ -170,7 +170,7 @@ import java.util.concurrent.TimeUnit; Properties messages; try { - messages = theme.getMessages(Locale.GERMAN); + messages = theme.getMessages(LocaleHelper.getLocale(realm, user, uriInfo, httpHeaders)); attributes.put("rb", messages); } catch (IOException e) { logger.warn("Failed to load messages", e); diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java index 1047ad252bd..ee8ce80d0c5 100755 --- a/model/api/src/main/java/org/keycloak/models/UserModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserModel.java @@ -13,6 +13,7 @@ public interface UserModel { public static final String LAST_NAME = "lastName"; public static final String FIRST_NAME = "firstName"; public static final String EMAIL = "email"; + public static final String LOCALE = "locale"; String getId(); diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index ffdb39d83b0..a7afb42ed98 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -82,6 +82,7 @@ public class SamlProtocol implements LoginProtocol { protected UriInfo uriInfo; + protected HttpHeaders headers; @Override @@ -102,6 +103,12 @@ public class SamlProtocol implements LoginProtocol { return this; } + @Override + public SamlProtocol setHttpHeaders(HttpHeaders headers){ + this.headers = headers; + return this; + } + @Override public Response cancelLogin(ClientSessionModel clientSession) { return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get()); @@ -129,7 +136,7 @@ public class SamlProtocol implements LoginProtocol { return builder.redirectBinding().response(); } } catch (Exception e) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response", headers); } } @@ -279,7 +286,7 @@ public class SamlProtocol implements LoginProtocol { publicKey = SamlProtocolUtils.getEncryptionValidationKey(client); } catch (Exception e) { logger.error("failed", e); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response", headers); } builder.encrypt(publicKey); } @@ -291,7 +298,7 @@ public class SamlProtocol implements LoginProtocol { } } catch (Exception e) { logger.error("failed", e); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response", headers); } } 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 d8fd673788c..1795446da34 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 @@ -101,18 +101,18 @@ public class SamlService { if (!checkSsl()) { event.event(EventType.LOGIN); event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required", headers); } if (!realm.isEnabled()) { event.event(EventType.LOGIN_ERROR); event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled", headers); } if (samlRequest == null && samlResponse == null) { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request", headers); } return null; @@ -124,7 +124,7 @@ public class SamlService { logger.warn("Unknown saml response."); event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request", headers); } // assume this is a logout response UserSessionModel userSession = authResult.getSession(); @@ -133,10 +133,10 @@ public class SamlService { logger.warn("UserSession is not tagged as logging out."); event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request", headers); } logger.debug("logout response"); - return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection); + return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers); } protected Response handleSamlRequest(String samlRequest, String relayState) { @@ -144,7 +144,7 @@ public class SamlService { if (documentHolder == null) { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request", headers); } SAML2Object samlObject = documentHolder.getSamlObject(); @@ -156,23 +156,23 @@ public class SamlService { if (client == null) { event.event(EventType.LOGIN); event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.", headers); } if (!client.isEnabled()) { event.event(EventType.LOGIN); event.error(Errors.CLIENT_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.", headers); } if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) { event.event(EventType.LOGIN); event.error(Errors.NOT_ALLOWED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login", headers); } if (client.isDirectGrantsOnly()) { event.event(EventType.LOGIN); event.error(Errors.NOT_ALLOWED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login", headers); } try { @@ -181,7 +181,7 @@ public class SamlService { SamlService.logger.error("request validation failed", e); event.event(EventType.LOGIN); event.error(Errors.INVALID_SIGNATURE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester.", headers); } logger.debug("verified request"); if (samlObject instanceof AuthnRequestType) { @@ -199,7 +199,7 @@ public class SamlService { } else { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request", headers); } } @@ -229,7 +229,7 @@ public class SamlService { if (redirect == null) { event.error(Errors.INVALID_REDIRECT_URI); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.", headers); } @@ -251,14 +251,14 @@ public class SamlService { clientSession.setNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat); } else { event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unsupported NameIDFormat."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unsupported NameIDFormat.", headers); } } Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event); if (response != null) return response; - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode()); String rememberMeUsername = AuthenticationManager.getRememberMeUsername(realm, headers); @@ -327,7 +327,7 @@ public class SamlService { } } logger.debug("browser Logout"); - return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection); + return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers); } @@ -340,7 +340,7 @@ public class SamlService { if (redirectUri != null) { redirectUri = OIDCLoginProtocolService.verifyRedirectUri(uriInfo, redirectUri, realm, client); if (redirectUri == null) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.", headers); } } if (redirectUri != null) { @@ -352,7 +352,7 @@ public class SamlService { } private Response logout(UserSessionModel userSession) { - Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection); + Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers); if (response == null) event.user(userSession.getUser()).session(userSession).success(); return response; } diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java index 7bd4d033c8c..0290771d7ab 100755 --- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java @@ -7,6 +7,7 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.provider.Provider; import org.keycloak.services.managers.ClientSessionCode; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -21,6 +22,8 @@ public interface LoginProtocol extends Provider { LoginProtocol setUriInfo(UriInfo uriInfo); + LoginProtocol setHttpHeaders(HttpHeaders headers); + Response cancelLogin(ClientSessionModel clientSession); Response invalidSessionError(ClientSessionModel clientSession); Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java index 9258264b9b9..76a9c6eb611 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java @@ -33,6 +33,7 @@ import org.keycloak.protocol.LoginProtocol; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ResourceAdminManager; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; @@ -59,13 +60,17 @@ public class OIDCLoginProtocol implements LoginProtocol { protected UriInfo uriInfo; - public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo) { + protected HttpHeaders headers; + + public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers) { this.session = session; this.realm = realm; this.uriInfo = uriInfo; + this.headers = headers; } - public OIDCLoginProtocol() { + public OIDCLoginProtocol(){ + } @Override @@ -86,6 +91,12 @@ public class OIDCLoginProtocol implements LoginProtocol { return this; } + @Override + public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers){ + this.headers = headers; + return this; + } + @Override public Response cancelLogin(ClientSessionModel clientSession) { String redirect = clientSession.getRedirectUri(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java index ae0ba0a457e..80eb5510818 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java @@ -508,7 +508,7 @@ public class OIDCLoginProtocolService { } AccessToken accessToken; try { - accessToken = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event); + accessToken = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event, headers); } catch (OAuthErrorException e) { Map error = new HashMap(); error.put(OAuth2Constants.ERROR, e.getError()); @@ -648,7 +648,7 @@ public class OIDCLoginProtocolService { } if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection); + AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection, headers); Map res = new HashMap(); res.put(OAuth2Constants.ERROR, "invalid_grant"); res.put(OAuth2Constants.ERROR_DESCRIPTION, "Session not active"); @@ -785,37 +785,37 @@ public class OIDCLoginProtocolService { event.client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code"); if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required", headers); } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled", headers); } clientSession = null; ClientModel client = realm.findClient(clientId); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.", headers); } if (!client.isEnabled()) { event.error(Errors.CLIENT_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.", headers); } if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) { event.error(Errors.NOT_ALLOWED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login", headers); } if (client.isDirectGrantsOnly()) { event.error(Errors.NOT_ALLOWED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login", headers); } String redirectUriParam = redirect; redirect = verifyRedirectUri(uriInfo, redirect, realm, client); if (redirect == null) { event.error(Errors.INVALID_REDIRECT_URI); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.", headers); } clientSession = session.sessions().createClientSession(realm, client); clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL); @@ -874,7 +874,7 @@ public class OIDCLoginProtocolService { IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(idpHint); if (identityProviderModel == null) { - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setError("Could not find an identity provider with the identifier [" + idpHint + "].") .createErrorPage(); } @@ -886,11 +886,11 @@ public class OIDCLoginProtocolService { // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?) HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event); - HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(); + HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers); if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse(); if (prompt != null && prompt.equals("none")) { - OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo); + OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers); return oauth.cancelLogin(clientSession); } @@ -908,13 +908,13 @@ public class OIDCLoginProtocolService { return redirectToIdentityProvider(identityProviders.get(0).getId(), accessCode); } - return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + this.realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError("Realm [" + this.realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage(); } - return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + this.realm.getName() + "] does not support any credential type.").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError("Realm [" + this.realm.getName() + "] does not support any credential type.").createErrorPage(); } - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(accessCode); // Attach state from SPNEGO authentication @@ -960,7 +960,7 @@ public class OIDCLoginProtocolService { event.event(EventType.REGISTER); if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed", headers); } FrontPageInitializer pageInitializer = new FrontPageInitializer(); @@ -976,7 +976,7 @@ public class OIDCLoginProtocolService { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); - return Flows.forms(session, realm, clientSession.getClient(), uriInfo) + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode()) .createRegistration(); } @@ -1004,7 +1004,7 @@ public class OIDCLoginProtocolService { if (redirectUri != null) { String validatedRedirect = verifyRealmRedirectUri(uriInfo, redirectUri, realm); if (validatedRedirect == null) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.", headers); } return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build(); } else { @@ -1065,14 +1065,14 @@ public class OIDCLoginProtocolService { } private void logout(UserSessionModel userSession) { - authManager.logout(session, realm, userSession, uriInfo, clientConnection); + authManager.logout(session, realm, userSession, uriInfo, clientConnection, headers); event.user(userSession.getUser()).session(userSession).success(); } @Path("oauth/oob") @GET public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) { - LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo); + LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo, headers); if (code != null) { return forms.setClientSessionCode(code).createCode(); } else { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 4c56df7df70..45317259825 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -28,6 +28,7 @@ import org.keycloak.representations.RefreshToken; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.util.Time; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; import java.io.IOException; import java.util.HashSet; @@ -57,7 +58,7 @@ public class TokenManager { } } - public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException { + public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException { RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken); event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()); @@ -74,7 +75,7 @@ public class TokenManager { UserSessionModel userSession = session.sessions().getUserSession(realm, refreshToken.getSessionState()); int currentTime = Time.currentTime(); if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, connection); + AuthenticationManager.logout(session, realm, userSession, uriInfo, connection, headers); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active"); } 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 e3bb56971ac..9e0e4ab161b 100755 --- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java @@ -42,7 +42,7 @@ public class AppAuthManager extends AuthenticationManager { public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { String tokenString = extractAuthorizationHeaderToken(headers); if (tokenString == null) return null; - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString); + AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString, headers); 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 d02ad2b9eb7..b71de560464 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -78,7 +78,7 @@ public class AuthenticationManager { return userSession != null && userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime; } - public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { if (userSession == null) return; UserModel user = userSession.getUser(); userSession.setState(UserSessionModel.State.LOGGING_OUT); @@ -94,6 +94,7 @@ public class AuthenticationManager { if (authMethod == null) continue; // must be a keycloak service like account LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); protocol.backchannelLogout(userSession, clientSession); clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT); @@ -104,7 +105,7 @@ public class AuthenticationManager { } - public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { if (userSession == null) return null; UserModel user = userSession.getUser(); @@ -127,6 +128,7 @@ public class AuthenticationManager { if (authMethod == null) continue; // must be a keycloak service like account LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); try { logger.debugv("backchannel logout to: {0}", client.getClientId()); @@ -139,12 +141,13 @@ public class AuthenticationManager { } if (redirectClients.size() == 0) { - return finishBrowserLogout(session, realm, userSession, uriInfo, connection); + return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers); } for (ClientSessionModel nextRedirectClient : redirectClients) { String authMethod = nextRedirectClient.getAuthMethod(); LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); // setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT); @@ -160,16 +163,17 @@ public class AuthenticationManager { } } - return finishBrowserLogout(session, realm, userSession, uriInfo, connection); + return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers); } - protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { expireIdentityCookie(realm, uriInfo, connection); expireRememberMeCookie(realm, uriInfo, connection); userSession.setState(UserSessionModel.State.LOGGED_OUT); String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL); LoginProtocol protocol = session.getProvider(LoginProtocol.class, method); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); Response response = protocol.finishLogout(userSession); session.sessions().removeUserSession(realm, userSession); @@ -284,7 +288,7 @@ public class AuthenticationManager { } String tokenString = cookie.getValue(); - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString); + AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString, headers); if (authResult == null) { expireIdentityCookie(realm, uriInfo, connection); return null; @@ -334,6 +338,7 @@ public class AuthenticationManager { if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(request.getHttpHeaders()) .setUriInfo(uriInfo); return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession)); @@ -363,7 +368,7 @@ public class AuthenticationManager { UserModel.RequiredAction action = user.getRequiredActions().iterator().next(); accessCode.setRequiredAction(action); - LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user); + LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()).setClientSessionCode(accessCode.getCode()).setUser(user); if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) { event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success(); } @@ -385,7 +390,7 @@ public class AuthenticationManager { } } - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()) .setClientSessionCode(accessCode.getCode()) .setAccessRequest(realmRoles, resourceRoles) .setClient(client) @@ -413,7 +418,7 @@ public class AuthenticationManager { } } - protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) { + protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) { try { AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive); if (checkActive) { @@ -433,7 +438,7 @@ public class AuthenticationManager { UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState()); if (!isSessionValid(realm, userSession)) { - if (userSession != null) logout(session, realm, userSession, uriInfo, connection); + if (userSession != null) logout(session, realm, userSession, uriInfo, connection, headers); logger.debug("User session not active"); return null; } diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java index 9791de321d3..d25c0cb6a16 100644 --- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java @@ -56,7 +56,7 @@ public class HttpAuthenticationManager { } - public HttpAuthOutput spnegoAuthenticate() { + public HttpAuthOutput spnegoAuthenticate(HttpHeaders headers) { boolean kerberosSupported = false; for (RequiredCredentialModel c : realm.getRequiredCredentials()) { if (c.getType().equals(CredentialRepresentation.KERBEROS)) { @@ -94,7 +94,7 @@ public class HttpAuthenticationManager { CredentialValidationOutput output = session.users().validCredentials(realm, spnegoCredential); if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) { - return sendResponse(output.getAuthenticatedUser(), "spnego"); + return sendResponse(output.getAuthenticatedUser(), "spnego", headers); } else { String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN); return challengeNegotiation(spnegoResponseToken); @@ -104,7 +104,7 @@ public class HttpAuthenticationManager { // Send response after successful authentication - private HttpAuthOutput sendResponse(UserModel user, String authMethod) { + private HttpAuthOutput sendResponse(UserModel user, String authMethod, HttpHeaders headers) { if (logger.isTraceEnabled()) { logger.trace("User " + user.getUsername() + " authenticated with " + authMethod); } @@ -112,7 +112,7 @@ public class HttpAuthenticationManager { Response response; if (!user.isEnabled()) { event.error(Errors.USER_DISABLED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, Messages.ACCOUNT_DISABLED); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, Messages.ACCOUNT_DISABLED, headers); } else { UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false); TokenManager.attachClientSession(userSession, clientSession); 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 337f01d462b..b8283994d59 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -241,7 +241,7 @@ public class AccountService { try { require(AccountRoles.MANAGE_ACCOUNT); } catch (ForbiddenException e) { - return Flows.forms(session, realm, null, uriInfo).setError("No access").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError("No access").createErrorPage(); } setReferrerOnPage(); diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 1bbb5811d52..07230430ae6 100644 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -57,13 +57,8 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; +import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -97,6 +92,10 @@ public class IdentityBrokerService { @Context private HttpRequest request; + + @Context + private HttpHeaders headers; + private EventBuilder event; public IdentityBrokerService(RealmModel realmModel) { @@ -187,11 +186,10 @@ public class IdentityBrokerService { } if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) { - return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo) + return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo, headers) .setClientSessionCode(authManager.extractAuthorizationHeaderToken(this.request.getHttpHeaders())) .setAccessRequest("Your information from " + providerId + " identity provider.") .setClient(clientModel) - .setUriInfo(this.uriInfo) .setActionUri(this.uriInfo.getRequestUri()) .createOAuthGrant(), clientModel); } @@ -439,7 +437,7 @@ public class IdentityBrokerService { private Response redirectToErrorPage(String message, Throwable throwable) { fireErrorEvent(message, throwable); - return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, message); + return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, message, headers); } private Response badRequest(String message) { @@ -449,7 +447,7 @@ public class IdentityBrokerService { private Response redirectToLoginPage(String message, ClientSessionCode clientCode) { fireErrorEvent(message); - return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo) + return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo, headers) .setClientSessionCode(clientCode.getCode()) .setError(message) .createLogin(); diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index d5d4867ae2e..86335e33815 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -156,7 +156,7 @@ public class LoginActionsService { return false; } else if (!clientCode.isValid(requiredAction)) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.", headers); return false; } else { return true; @@ -166,18 +166,18 @@ public class LoginActionsService { public boolean check(String code) { if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required", headers); return false; } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.", headers); return false; } clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.", headers); return false; } return true; @@ -208,7 +208,7 @@ public class LoginActionsService { clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); } - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(clientSessionCode.getCode()); return forms.createLogin(); @@ -226,7 +226,7 @@ public class LoginActionsService { event.event(EventType.REGISTER); if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed", headers); } Checks checks = new Checks(); @@ -240,7 +240,7 @@ public class LoginActionsService { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); - return Flows.forms(session, realm, clientSession.getClient(), uriInfo) + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(clientSessionCode.getCode()) .createRegistration(); } @@ -260,23 +260,23 @@ public class LoginActionsService { event.event(EventType.LOGIN); if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required", headers); } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.", headers); } ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.", headers); } ClientSessionModel clientSession = clientCode.getClientSession(); if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) { clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE); event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); - return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.EXPIRED_CODE) + return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE) .setClientSessionCode(clientCode.getCode()) .createLogin(); } @@ -300,17 +300,18 @@ public class LoginActionsService { ClientModel client = clientSession.getClient(); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.", headers); } if (!client.isEnabled()) { event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.", headers); } if (formData.containsKey("cancel")) { event.error(Errors.REJECTED_BY_USER); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); return protocol.cancelLogin(clientSession); } @@ -337,14 +338,14 @@ public class LoginActionsService { return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); case ACCOUNT_TEMPORARILY_DISABLED: event.error(Errors.USER_TEMPORARILY_DISABLED); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); case ACCOUNT_DISABLED: event.error(Errors.USER_DISABLED); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setError(Messages.ACCOUNT_DISABLED) .setClientSessionCode(clientCode.getCode()) .setFormData(formData).createLogin(); @@ -354,19 +355,19 @@ public class LoginActionsService { String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey()); formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLoginTotp(); case INVALID_USER: event.error(Errors.USER_NOT_FOUND); - return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER) + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); default: event.error(Errors.INVALID_USER_CREDENTIALS); - return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER) + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); @@ -388,25 +389,25 @@ public class LoginActionsService { event.event(EventType.REGISTER); if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required", headers); } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.", headers); } if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed", headers); } ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.", headers); } if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.", headers); } String username = formData.getFirst("username"); @@ -421,17 +422,17 @@ public class LoginActionsService { if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled", headers); } ClientModel client = clientSession.getClient(); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.", headers); } if (!client.isEnabled()) { event.error(Errors.CLIENT_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.", headers); } @@ -448,7 +449,7 @@ public class LoginActionsService { if (error != null) { event.error(Errors.INVALID_REGISTRATION); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(error) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) @@ -458,7 +459,7 @@ public class LoginActionsService { // Validate that user with this username doesn't exist in realm or any federation provider if (session.users().getUserByUsername(username, realm) != null) { event.error(Errors.USERNAME_IN_USE); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(Messages.USERNAME_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) @@ -468,7 +469,7 @@ public class LoginActionsService { // Validate that user with this email doesn't exist in realm or any federation provider if (session.users().getUserByEmail(email, realm) != null) { event.error(Errors.EMAIL_IN_USE); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(Messages.EMAIL_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) @@ -500,7 +501,7 @@ public class LoginActionsService { // User already registered, but force him to update password if (!passwordUpdateSuccessful) { user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(passwordUpdateError) .setClientSessionCode(clientCode.getCode()) .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); @@ -527,7 +528,7 @@ public class LoginActionsService { if (!checkSsl()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required", headers); } String code = formData.getFirst("code"); @@ -535,7 +536,7 @@ public class LoginActionsService { ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code.", headers); } ClientSessionModel clientSession = accessCode.getClientSession(); event.detail(Details.CODE_ID, clientSession.getId()); @@ -557,14 +558,15 @@ public class LoginActionsService { } if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection); + AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection, headers); event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active", headers); } event.session(userSession); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); if (formData.containsKey("cancel")) { event.error(Errors.REJECTED_BY_USER); @@ -598,7 +600,7 @@ public class LoginActionsService { String error = Validation.validateUpdateProfileForm(formData); if (error != null) { - return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error) + return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(error) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PROFILE); } @@ -616,7 +618,7 @@ public class LoginActionsService { // check for duplicated email if (userByEmail != null && !userByEmail.getId().equals(user.getId())) { - return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(Messages.EMAIL_EXISTS) + return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PROFILE); } @@ -655,7 +657,7 @@ public class LoginActionsService { String totp = formData.getFirst("totp"); String totpSecret = formData.getFirst("totpSecret"); - LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user); + LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user); if (Validation.isEmpty(totp)) { return loginForms.setError(Messages.MISSING_TOTP) .setClientSessionCode(accessCode.getCode()) @@ -700,7 +702,7 @@ public class LoginActionsService { String passwordNew = formData.getFirst("password-new"); String passwordConfirm = formData.getFirst("password-confirm"); - LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user); + LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user); if (Validation.isEmpty(passwordNew)) { return loginForms.setError(Messages.MISSING_PASSWORD) .setClientSessionCode(accessCode.getCode()) @@ -757,7 +759,7 @@ public class LoginActionsService { UserSessionModel userSession = clientSession.getUserSession(); initEvent(clientSession); - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(accessCode.getCode()) .setUser(userSession.getUser()) .createResponse(RequiredAction.VERIFY_EMAIL); @@ -775,11 +777,11 @@ public class LoginActionsService { } ClientSessionCode accessCode = checks.clientCode; accessCode.setRequiredAction(RequiredAction.UPDATE_PASSWORD); - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PASSWORD); } else { - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(code) .createPasswordReset(); } @@ -792,16 +794,16 @@ public class LoginActionsService { final MultivaluedMap formData) { event.event(EventType.SEND_RESET_PASSWORD); if (!checkSsl()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required", headers); } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.", headers); } ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); if (accessCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.", headers); } ClientSessionModel clientSession = accessCode.getClientSession(); @@ -810,11 +812,11 @@ public class LoginActionsService { ClientModel client = clientSession.getClient(); if (client == null) { return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, - "Unknown login requester."); + "Unknown login requester.", headers); } if (!client.isEnabled()) { return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, - "Login requester not enabled."); + "Login requester not enabled.", headers); } event.client(client.getClientId()) @@ -857,13 +859,13 @@ public class LoginActionsService { } catch (EmailException e) { event.error(Errors.EMAIL_SEND_FAILED); logger.error("Failed to send password reset email", e); - return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError") + return Flows.forms(this.session, realm, client, uriInfo, headers).setError("emailSendError") .setClientSessionCode(accessCode.getCode()) .createErrorPage(); } } - return Flows.forms(session, realm, client, uriInfo).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset(); + return Flows.forms(session, realm, client, uriInfo, headers).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset(); } private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) { diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java index 8c9e4d4b659..4f29aa2c82a 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java @@ -26,6 +26,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -37,16 +38,16 @@ public class Flows { private Flows() { } - public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo) { - return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client); + public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo, HttpHeaders headers) { + return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client).setHttpHeaders(headers); } public static ErrorFlows errors() { return new ErrorFlows(); } - public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String message) { - return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage(); + public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String message, HttpHeaders headers) { + return Flows.forms(session, realm, null, uriInfo, headers).setError(message).createErrorPage(); }