diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java
index 022dadd867..b552d1bd89 100644
--- a/core/src/main/java/org/keycloak/OAuth2Constants.java
+++ b/core/src/main/java/org/keycloak/OAuth2Constants.java
@@ -41,6 +41,9 @@ public interface OAuth2Constants {
// http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
String OFFLINE_ACCESS = "offline_access";
+ String UI_LOCALES_PARAM = "ui_locales";
+
+
}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index f7825438ac..b285df628d 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -51,7 +51,6 @@ import org.keycloak.events.Event;
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
import org.keycloak.freemarker.FreeMarkerException;
import org.keycloak.freemarker.FreeMarkerUtil;
-import org.keycloak.freemarker.LocaleHelper;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
@@ -65,7 +64,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
-import org.keycloak.services.Urls;
/**
* @author Stian Thorgersen
@@ -130,7 +128,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
logger.warn("Failed to load properties", e);
}
- Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers);
+ Locale locale = session.getContext().resolveLocale(user);
Properties messagesBundle;
try {
messagesBundle = theme.getMessages(locale);
@@ -213,10 +211,6 @@ public class FreeMarkerAccountProvider implements AccountProvider {
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
-
- String keycloakLocaleCookiePath = Urls.localeCookiePath(baseUri, realm.getName());
-
- LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, keycloakLocaleCookiePath);
return builder.build();
} catch (FreeMarkerException e) {
logger.error("Failed to process template", e);
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
deleted file mode 100644
index 14f7f6c6aa..0000000000
--- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
- * as indicated by the @author tags. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.keycloak.freemarker;
-
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.NewCookie;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-import java.util.*;
-
-/**
- * @author Michael Gerber
- */
-public class LocaleHelper {
- public final static String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
- public static final String UI_LOCALES_PARAM = "ui_locales";
- public static final String KC_LOCALE_PARAM = "kc_locale";
-
- public static Locale getLocale(RealmModel realm, UserModel user) {
- return getLocale(realm, user, null, null);
- }
-
- public static Locale getLocale(RealmModel realm, UserModel user, UriInfo uriInfo, HttpHeaders httpHeaders) {
- if(!realm.isInternationalizationEnabled()){
- return Locale.ENGLISH;
- }
-
- //0. kc_locale query parameter
- if(uriInfo != null && uriInfo.getQueryParameters().containsKey(KC_LOCALE_PARAM)){
- String localeString = uriInfo.getQueryParameters().getFirst(KC_LOCALE_PARAM);
- Locale locale = findLocale(realm.getSupportedLocales(), localeString);
- if(locale != null){
- if(user != null){
- user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
- }
- return locale;
- }
- }
-
- //1. Locale cookie
- if(httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)){
- String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
- Locale locale = findLocale(realm.getSupportedLocales(), localeString);
- if(locale != null){
- if(user != null && user.getFirstAttribute(UserModel.LOCALE) == null){
- user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
- }
- return locale;
- }
- }
-
- //2. User profile
- if(user != null && user.getAttributes().containsKey(UserModel.LOCALE)){
- String localeString = user.getFirstAttribute(UserModel.LOCALE);
- Locale locale = findLocale(realm.getSupportedLocales(), localeString);
- if(locale != null){
-
- return locale;
- }
- }
-
- //3. ui_locales query parameter
- if(uriInfo != null && uriInfo.getQueryParameters().containsKey(UI_LOCALES_PARAM)){
- String localeString = uriInfo.getQueryParameters().getFirst(UI_LOCALES_PARAM);
- Locale locale = findLocale(realm.getSupportedLocales(), localeString.split(" "));
- if(locale != null){
- return locale;
- }
- }
-
- //4. Accept-Language http header
- if(httpHeaders !=null && httpHeaders.getAcceptableLanguages() != null && !httpHeaders.getAcceptableLanguages().isEmpty()){
- for(Locale l : httpHeaders.getAcceptableLanguages()){
- String localeString = l.toLanguageTag();
- Locale locale = findLocale(realm.getSupportedLocales(), localeString);
- if(locale != null){
- return locale;
- }
- }
- }
-
- //5. Default realm locale
- if(realm.getDefaultLocale() != null){
- return Locale.forLanguageTag(realm.getDefaultLocale());
- }
-
- return Locale.ENGLISH;
- }
-
- public static void updateLocaleCookie(Response.ResponseBuilder builder,
- Locale locale,
- RealmModel realm,
- UriInfo uriInfo,
- String path) {
- if (locale == null) {
- return;
- }
- boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost());
- builder.cookie(new NewCookie(LocaleHelper.LOCALE_COOKIE, locale.toLanguageTag(), path, null, null, 31536000, secure));
- }
-
- public static Locale findLocale(Set supportedLocales, String ... localeStrings) {
- for(String localeString : localeStrings){
- Locale result = null;
- Locale search = Locale.forLanguageTag(localeString);
- for(String languageTag : supportedLocales) {
- Locale locale = Locale.forLanguageTag(languageTag);
- if(locale.getLanguage().equals(search.getLanguage())){
- if(locale.getCountry().equals("") && result == null){
- result = locale;
- }
- if(locale.getCountry().equals(search.getCountry())){
- return locale;
- }
- }
- }
- if(result != null){
- return result;
- }
- }
- return null;
- }
-}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/LocaleBean.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/LocaleBean.java
index 91ae108147..aedf33e3c4 100644
--- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/LocaleBean.java
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/LocaleBean.java
@@ -1,6 +1,5 @@
package org.keycloak.freemarker.beans;
-import org.keycloak.freemarker.LocaleHelper;
import org.keycloak.models.RealmModel;
import javax.ws.rs.core.UriBuilder;
@@ -22,7 +21,7 @@ public class LocaleBean {
supported = new LinkedList<>();
for (String l : realm.getSupportedLocales()) {
String label = messages.getProperty("locale_" + l, l);
- String url = uriBuilder.replaceQueryParam(LocaleHelper.KC_LOCALE_PARAM, l).build().toString();
+ String url = uriBuilder.replaceQueryParam("kc_locale", l).build().toString();
supported.add(new Locale(label, url));
}
}
diff --git a/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java b/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java
deleted file mode 100644
index 47fa9e4cb8..0000000000
--- a/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.keycloak.freemarke;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.keycloak.freemarker.LocaleHelper;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-/**
- * @author Michael Gerber
- */
-public class LocaleHelperTest {
- @Test
- public void findLocaleTest(){
- Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de").toLanguageTag());
- Assert.assertEquals("en", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "en").toLanguageTag());
- Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de-CH").toLanguageTag());
- Assert.assertEquals("de-CH", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-CH").toLanguageTag());
- Assert.assertEquals("de-DE", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-DE").toLanguageTag());
- Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de").toLanguageTag());
- Assert.assertNull(LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de-CH","de-DE")), "de"));
- }
-
- @Test
- public void findLocalesTest(){
- Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de en".split(" ")).toLanguageTag());
- Assert.assertEquals("en", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "en de".split(" ")).toLanguageTag());
- Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de-CH en".split(" ")).toLanguageTag());
- Assert.assertEquals("de-CH", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-CH de".split(" ")).toLanguageTag());
- Assert.assertEquals("de-DE", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-DE de-CH de".split(" ")).toLanguageTag());
- Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "en fr de".split(" ")).toLanguageTag());
- Assert.assertNull(LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de-CH","de-DE")), "de en fr".split(" ")));
- }
-}
diff --git a/forms/email-freemarker/pom.xml b/forms/email-freemarker/pom.xml
index e1eff9db3d..0b9f540016 100755
--- a/forms/email-freemarker/pom.xml
+++ b/forms/email-freemarker/pom.xml
@@ -29,6 +29,11 @@
keycloak-model-api
provided
+
+ org.keycloak
+ keycloak-services
+ provided
+
org.keycloak
keycloak-events-api
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
index b620a5968b..f049085452 100755
--- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
@@ -24,7 +24,6 @@ import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.freemarker.FreeMarkerException;
import org.keycloak.freemarker.FreeMarkerUtil;
-import org.keycloak.freemarker.LocaleHelper;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.beans.MessageFormatterMethod;
@@ -110,7 +109,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
try {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
- Locale locale = LocaleHelper.getLocale(realm, user);
+ Locale locale = session.getContext().resolveLocale(user);
attributes.put("locale", locale);
Properties rb = theme.getMessages(locale);
attributes.put("msg", new MessageFormatterMethod(locale, rb));
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 1a16fbcd26..7fc1bcddcb 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
@@ -24,7 +24,6 @@ import org.keycloak.email.EmailProvider;
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
import org.keycloak.freemarker.FreeMarkerException;
import org.keycloak.freemarker.FreeMarkerUtil;
-import org.keycloak.freemarker.LocaleHelper;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
@@ -201,7 +200,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
Properties messagesBundle;
- Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
+ Locale locale = session.getContext().resolveLocale(user);
try {
messagesBundle = theme.getMessages(locale);
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
@@ -292,10 +291,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
for (Map.Entry entry : httpResponseHeaders.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
}
-
- String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
- LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
-
return builder.build();
} catch (FreeMarkerException e) {
logger.error("Failed to process template", e);
@@ -345,7 +340,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
Properties messagesBundle;
- Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
+ Locale locale = session.getContext().resolveLocale(user);
try {
messagesBundle = theme.getMessages(locale);
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
@@ -393,9 +388,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
for (Map.Entry entry : httpResponseHeaders.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
}
-
- String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
- LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
return builder.build();
} catch (FreeMarkerException e) {
logger.error("Failed to process template", e);
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
index 760ffe4624..e66927af63 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
@@ -5,6 +5,7 @@ import org.keycloak.models.utils.RealmImporter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
+import java.util.Locale;
/**
* @author Stian Thorgersen
@@ -33,4 +34,6 @@ public interface KeycloakContext {
RealmImporter getRealmManager();
+ Locale resolveLocale(UserModel user);
+
}
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 f57bb04199..9dec77844c 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -1,6 +1,5 @@
package org.keycloak.models;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -10,11 +9,11 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public interface UserModel {
- public static final String USERNAME = "username";
- 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 USERNAME = "username";
+ String LAST_NAME = "lastName";
+ String FIRST_NAME = "firstName";
+ String EMAIL = "email";
+ String LOCALE = "locale";
String getId();
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
index 665662dcee..ec866eb2df 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
@@ -2,16 +2,15 @@ package org.keycloak.services;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.ClientConnection;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.KeycloakContext;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
+import org.keycloak.models.*;
import org.keycloak.models.utils.RealmImporter;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.services.util.LocaleHelper;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
+import java.util.Locale;
/**
* @author Stian Thorgersen
@@ -87,4 +86,9 @@ public class DefaultKeycloakContext implements KeycloakContext {
manager.setContextPath(getContextPath());
return manager;
}
+
+ @Override
+ public Locale resolveLocale(UserModel user) {
+ return LocaleHelper.getLocale(session, realm, user);
+ }
}
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 73d274edc9..332d71d136 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -33,42 +33,24 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.login.LoginFormsProvider;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.RequiredActionProviderModel;
-import org.keycloak.models.UserConsentModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ProtocolMapperModel;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.*;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
+import org.keycloak.services.Urls;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.IdentityBrokerService;
import org.keycloak.services.resources.RealmsResource;
-import org.keycloak.services.Urls;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.util.Time;
-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.UriInfo;
-
+import javax.ws.rs.core.*;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
-import java.util.Locale;
import java.util.Set;
-import org.keycloak.freemarker.LocaleHelper;
-import org.keycloak.util.TokenUtil;
/**
* Stateless object that manages authentication
@@ -413,7 +395,8 @@ public class AuthenticationManager {
}
}
- handleLoginLocale(realm, userSession, request, uriInfo);
+ // Updates users locale if required
+ session.getContext().resolveLocale(userSession.getUser());
// refresh the cookies!
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
@@ -428,17 +411,6 @@ public class AuthenticationManager {
}
- // If a locale has been set on the login screen, associate that locale with the user
- private static void handleLoginLocale(RealmModel realm, UserSessionModel userSession,
- HttpRequest request, UriInfo uriInfo) {
- Cookie localeCookie = request.getHttpHeaders().getCookies().get(LocaleHelper.LOCALE_COOKIE);
- if (localeCookie == null) return;
-
- UserModel user = userSession.getUser();
- Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, request.getHttpHeaders());
- user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
- }
-
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
ClientConnection clientConnection,
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
diff --git a/services/src/main/java/org/keycloak/services/util/LocaleHelper.java b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
new file mode 100644
index 0000000000..1f523d7132
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.services.util;
+
+import org.jboss.resteasy.spi.HttpResponse;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.util.ServerCookie;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriInfo;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * @author Michael Gerber
+ */
+public class LocaleHelper {
+
+ private static final String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
+ private static final String UI_LOCALES_PARAM = "ui_locales";
+ private static final String KC_LOCALE_PARAM = "kc_locale";
+
+ public static Locale getLocale(KeycloakSession session, RealmModel realm, UserModel user) {
+ if (!realm.isInternationalizationEnabled()) {
+ return Locale.ENGLISH;
+ } else {
+ Locale locale = getUserLocale(session, realm, user);
+ return locale != null ? locale : Locale.forLanguageTag(realm.getDefaultLocale());
+ }
+ }
+
+ private static Locale getUserLocale(KeycloakSession session, RealmModel realm, UserModel user) {
+ UriInfo uriInfo = session.getContext().getUri();
+ HttpHeaders httpHeaders = session.getContext().getRequestHeaders();
+
+ // kc_locale query parameter
+ if (uriInfo != null && uriInfo.getQueryParameters().containsKey(KC_LOCALE_PARAM)) {
+ String localeString = uriInfo.getQueryParameters().getFirst(KC_LOCALE_PARAM);
+ Locale locale = findLocale(realm.getSupportedLocales(), localeString);
+ if (locale != null) {
+ updateLocaleCookie(session, realm, localeString);
+ if (user != null) {
+ updateUsersLocale(user, localeString);
+ }
+ return locale;
+ }
+ }
+
+ // User profile
+ if (user != null && user.getAttributes().containsKey(UserModel.LOCALE)) {
+ String localeString = user.getFirstAttribute(UserModel.LOCALE);
+ Locale locale = findLocale(realm.getSupportedLocales(), localeString);
+ if (locale != null) {
+ updateLocaleCookie(session, realm, localeString);
+ return locale;
+ }
+ }
+
+ // Locale cookie
+ if (httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)) {
+ String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
+ Locale locale = findLocale(realm.getSupportedLocales(), localeString);
+ if (locale != null) {
+ if (user != null) {
+ updateUsersLocale(user, localeString);
+ }
+ return locale;
+ }
+ }
+
+ // ui_locales query parameter
+ if (uriInfo != null && uriInfo.getQueryParameters().containsKey(UI_LOCALES_PARAM)) {
+ String localeString = uriInfo.getQueryParameters().getFirst(UI_LOCALES_PARAM);
+ Locale locale = findLocale(realm.getSupportedLocales(), localeString.split(" "));
+ if (locale != null) {
+ return locale;
+ }
+ }
+
+ // Accept-Language http header
+ if (httpHeaders != null && httpHeaders.getAcceptableLanguages() != null && !httpHeaders.getAcceptableLanguages().isEmpty()) {
+ for (Locale l : httpHeaders.getAcceptableLanguages()) {
+ String localeString = l.toLanguageTag();
+ Locale locale = findLocale(realm.getSupportedLocales(), localeString);
+ if (locale != null) {
+ return locale;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static void updateLocaleCookie(KeycloakSession session,
+ RealmModel realm,
+ String locale) {
+ boolean secure = realm.getSslRequired().isRequired(session.getContext().getUri().getRequestUri().getHost());
+ addCookie(LOCALE_COOKIE, locale, AuthenticationManager.getRealmCookiePath(realm, session.getContext().getUri()), null, null, -1, secure, true);
+ }
+
+ private static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
+ HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
+ StringBuffer cookieBuf = new StringBuffer();
+ ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
+ String cookie = cookieBuf.toString();
+ response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
+ }
+
+ private static Locale findLocale(Set supportedLocales, String... localeStrings) {
+ for (String localeString : localeStrings) {
+ Locale result = null;
+ Locale search = Locale.forLanguageTag(localeString);
+ for (String languageTag : supportedLocales) {
+ Locale locale = Locale.forLanguageTag(languageTag);
+ if (locale.getLanguage().equals(search.getLanguage())) {
+ if (locale.getCountry().equals("") && result == null) {
+ result = locale;
+ }
+ if (locale.getCountry().equals(search.getCountry())) {
+ return locale;
+ }
+ }
+ }
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ private static void updateUsersLocale(UserModel user, String locale) {
+ if (!locale.equals(user.getFirstAttribute("locale"))) {
+ user.setSingleAttribute(UserModel.LOCALE, locale);
+ }
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 087f82faec..2a08ca12a6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -24,7 +24,6 @@ package org.keycloak.testsuite;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
-import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
@@ -37,7 +36,6 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.constants.AdapterConstants;
-import org.keycloak.freemarker.LocaleHelper;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@@ -397,7 +395,7 @@ public class OAuthClient {
b.queryParam(OAuth2Constants.STATE, state);
}
if(uiLocales != null){
- b.queryParam(LocaleHelper.UI_LOCALES_PARAM, uiLocales);
+ b.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
}
if (scope != null) {
b.queryParam(OAuth2Constants.SCOPE, scope);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java
index 31e11a4b97..c0ca3e81c9 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java
@@ -54,7 +54,6 @@ public class EmailTest {
UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
user.setEmail("login@test.com");
user.setEnabled(true);
- user.setSingleAttribute(UserModel.LOCALE, "de");
UserCredentialModel creds = new UserCredentialModel();
creds.setType(CredentialRepresentation.PASSWORD);
@@ -86,7 +85,7 @@ public class EmailTest {
MimeMessage message = greenMail.getReceivedMessages()[0];
- Assert.assertEquals("Passwort zurückzusetzen", message.getSubject());
+ Assert.assertEquals("Reset password", message.getSubject());
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
@@ -106,4 +105,31 @@ public class EmailTest {
Assert.assertEquals("Reset password", message.getSubject());
}
+
+ @Test
+ public void restPasswordEmailGerman() throws IOException, MessagingException {
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ manager.getSession().users().getUserByUsername("login-test", appRealm).setSingleAttribute(UserModel.LOCALE, "de");
+ }
+ });
+
+ loginPage.open();
+ loginPage.resetPassword();
+ resetPasswordPage.changePassword("login-test");
+
+ assertEquals(1, greenMail.getReceivedMessages().length);
+
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+
+ Assert.assertEquals("Passwort zurückzusetzen", message.getSubject());
+
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ manager.getSession().users().getUserByUsername("login-test", appRealm).setSingleAttribute(UserModel.LOCALE, "en");
+ }
+ });
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
index 2d0f9eb11f..c05dac35ac 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
@@ -30,7 +30,6 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.adapters.HttpClientBuilder;
-import org.keycloak.freemarker.LocaleHelper;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
@@ -87,7 +86,7 @@ public class LoginPageTest {
//test if cookie works
oauth.uiLocales("de");
loginPage.open();
- Assert.assertEquals("English", loginPage.getLanguageDropdownText());
+ Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
driver.manage().deleteAllCookies();
loginPage.open();
@@ -113,10 +112,8 @@ public class LoginPageTest {
loginPage.open();
Response response = client.target(driver.getCurrentUrl()).request().acceptLanguage("de").get();
Assert.assertTrue(response.readEntity(String.class).contains("Anmeldung bei test"));
- Assert.assertEquals("de", response.getCookies().get(LocaleHelper.LOCALE_COOKIE).getValue());
response = client.target(driver.getCurrentUrl()).request().acceptLanguage("en").get();
Assert.assertTrue(response.readEntity(String.class).contains("Log in to test"));
- Assert.assertEquals("en", response.getCookies().get(LocaleHelper.LOCALE_COOKIE).getValue());
}
}