KEYCLOAK-8146: Extract LocaleSelectorSPI to allow custom overrides of locale selection
This commit is contained in:
parent
26f257a6ac
commit
d4a5c81034
17 changed files with 430 additions and 232 deletions
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.locale;
|
||||
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public interface LocaleSelectorProvider extends Provider {
|
||||
/**
|
||||
* Resolve the locale which should be used for the request
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
Locale resolveLocale(RealmModel realm, UserModel user);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.locale;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface LocaleSelectorProviderFactory extends ProviderFactory<LocaleSelectorProvider> {
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.locale;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
public class LocaleSelectorSPI implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "localeSelector";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return LocaleSelectorProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return LocaleSelectorProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,7 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.locale.LocaleSelectorSPI
|
||||
org.keycloak.storage.UserStorageProviderSpi
|
||||
org.keycloak.theme.ThemeResourceSpi
|
||||
org.keycloak.theme.ThemeSelectorSpi
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.locale;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Locale;
|
||||
|
||||
public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||
|
||||
protected static final String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
|
||||
protected static final String KC_LOCALE_PARAM = "kc_locale";
|
||||
|
||||
protected final KeycloakSession session;
|
||||
|
||||
public DefaultLocaleSelectorProvider(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale resolveLocale(RealmModel realm, UserModel user) {
|
||||
final HttpHeaders requestHeaders = session.getContext().getRequestHeaders();
|
||||
final UriInfo uri = session.getContext().getUri();
|
||||
return getLocale(realm, user, requestHeaders, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
protected Locale getLocale(RealmModel realm, UserModel user, HttpHeaders requestHeaders, UriInfo uriInfo) {
|
||||
if (!realm.isInternationalizationEnabled()) {
|
||||
return Locale.ENGLISH;
|
||||
} else {
|
||||
Locale locale = getUserLocale(realm, user, requestHeaders, uriInfo);
|
||||
return locale != null ? locale : Locale.forLanguageTag(realm.getDefaultLocale());
|
||||
}
|
||||
}
|
||||
|
||||
protected Locale getUserLocale(RealmModel realm, UserModel user, HttpHeaders requestHeaders, UriInfo uriInfo) {
|
||||
final LocaleSelection kcLocaleQueryParamSelection = getKcLocaleQueryParamSelection(realm, uriInfo);
|
||||
if (kcLocaleQueryParamSelection != null) {
|
||||
updateLocaleCookie(realm, kcLocaleQueryParamSelection.getLocaleString(), uriInfo);
|
||||
if (user != null) {
|
||||
updateUsersLocale(user, kcLocaleQueryParamSelection.getLocaleString());
|
||||
}
|
||||
return kcLocaleQueryParamSelection.getLocale();
|
||||
}
|
||||
|
||||
final LocaleSelection localeCookieSelection = getLocaleCookieSelection(realm, requestHeaders);
|
||||
if (localeCookieSelection != null) {
|
||||
if (user != null) {
|
||||
updateUsersLocale(user, localeCookieSelection.getLocaleString());
|
||||
}
|
||||
return localeCookieSelection.getLocale();
|
||||
}
|
||||
|
||||
final LocaleSelection userProfileSelection = getUserProfileSelection(realm, user);
|
||||
if (userProfileSelection != null) {
|
||||
updateLocaleCookie(realm, userProfileSelection.getLocaleString(), uriInfo);
|
||||
return userProfileSelection.getLocale();
|
||||
}
|
||||
|
||||
final LocaleSelection uiLocalesQueryParamSelection = getUiLocalesQueryParamSelection(realm, uriInfo);
|
||||
if (uiLocalesQueryParamSelection != null) {
|
||||
return uiLocalesQueryParamSelection.getLocale();
|
||||
}
|
||||
|
||||
final LocaleSelection acceptLanguageHeaderSelection = getAcceptLanguageHeaderLocale(realm, requestHeaders);
|
||||
if (acceptLanguageHeaderSelection != null) {
|
||||
return acceptLanguageHeaderSelection.getLocale();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected LocaleSelection getKcLocaleQueryParamSelection(RealmModel realm, UriInfo uriInfo) {
|
||||
if (uriInfo == null || !uriInfo.getQueryParameters().containsKey(KC_LOCALE_PARAM)) {
|
||||
return null;
|
||||
}
|
||||
String localeString = uriInfo.getQueryParameters().getFirst(KC_LOCALE_PARAM);
|
||||
return findLocale(realm, localeString);
|
||||
}
|
||||
|
||||
protected LocaleSelection getLocaleCookieSelection(RealmModel realm, HttpHeaders httpHeaders) {
|
||||
if (httpHeaders == null || !httpHeaders.getCookies().containsKey(LOCALE_COOKIE)) {
|
||||
return null;
|
||||
}
|
||||
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
|
||||
return findLocale(realm, localeString);
|
||||
}
|
||||
|
||||
protected LocaleSelection getUserProfileSelection(RealmModel realm, UserModel user) {
|
||||
if (user == null || !user.getAttributes().containsKey(UserModel.LOCALE)) {
|
||||
return null;
|
||||
}
|
||||
String localeString = user.getFirstAttribute(UserModel.LOCALE);
|
||||
return findLocale(realm, localeString);
|
||||
}
|
||||
|
||||
protected LocaleSelection getUiLocalesQueryParamSelection(RealmModel realm, UriInfo uriInfo) {
|
||||
if (uriInfo == null || !uriInfo.getQueryParameters().containsKey(OAuth2Constants.UI_LOCALES_PARAM)) {
|
||||
return null;
|
||||
}
|
||||
String localeString = uriInfo.getQueryParameters().getFirst(OAuth2Constants.UI_LOCALES_PARAM);
|
||||
return findLocale(realm, localeString.split(" "));
|
||||
}
|
||||
|
||||
protected LocaleSelection getAcceptLanguageHeaderLocale(RealmModel realm, HttpHeaders httpHeaders) {
|
||||
if (httpHeaders == null || httpHeaders.getAcceptableLanguages() == null || httpHeaders.getAcceptableLanguages().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (Locale l : httpHeaders.getAcceptableLanguages()) {
|
||||
String localeString = l.toLanguageTag();
|
||||
LocaleSelection localeSelection = findLocale(realm, localeString);
|
||||
if (localeSelection != null) {
|
||||
return localeSelection;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void updateLocaleCookie(RealmModel realm, String locale, UriInfo uriInfo) {
|
||||
boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost());
|
||||
CookieHelper.addCookie(LOCALE_COOKIE, locale, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, secure, true);
|
||||
}
|
||||
|
||||
protected LocaleSelection findLocale(RealmModel realm, String... localeStrings) {
|
||||
return new LocaleNegotiator(realm.getSupportedLocales()).invoke(localeStrings);
|
||||
}
|
||||
|
||||
protected void updateUsersLocale(UserModel user, String locale) {
|
||||
if (!locale.equals(user.getFirstAttribute("locale"))) {
|
||||
user.setSingleAttribute(UserModel.LOCALE, locale);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.locale;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
public class DefaultLocaleSelectorProviderFactory implements LocaleSelectorProviderFactory {
|
||||
@Override
|
||||
public LocaleSelectorProvider create(KeycloakSession session) {
|
||||
return new DefaultLocaleSelectorProvider(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
}
|
|
@ -14,19 +14,19 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.services.util;
|
||||
package org.keycloak.locale;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
class LocaleNegotiator {
|
||||
public class LocaleNegotiator {
|
||||
private Set<String> supportedLocales;
|
||||
|
||||
LocaleNegotiator(Set<String> supportedLocales) {
|
||||
public LocaleNegotiator(Set<String> supportedLocales) {
|
||||
this.supportedLocales = supportedLocales;
|
||||
}
|
||||
|
||||
Locale invoke(String... localeStrings) {
|
||||
public LocaleSelection invoke(String... localeStrings) {
|
||||
for (String localeString : localeStrings) {
|
||||
if (localeString != null) {
|
||||
Locale result = null;
|
||||
|
@ -38,15 +38,16 @@ class LocaleNegotiator {
|
|||
result = locale;
|
||||
}
|
||||
if (locale.getCountry().equals(search.getCountry())) {
|
||||
return locale;
|
||||
return new LocaleSelection(localeString, locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result != null) {
|
||||
return result;
|
||||
return new LocaleSelection(localeString, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.locale;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class LocaleSelection {
|
||||
private final String localeString;
|
||||
private final Locale locale;
|
||||
|
||||
public LocaleSelection(String localeString, Locale locale) {
|
||||
this.localeString = localeString;
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public String getLocaleString() {
|
||||
return localeString;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.services;
|
|||
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.locale.LocaleSelectorProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -26,7 +27,6 @@ import org.keycloak.models.KeycloakUriInfo;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.services.util.LocaleHelper;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
@ -117,6 +117,6 @@ public class DefaultKeycloakContext implements KeycloakContext {
|
|||
|
||||
@Override
|
||||
public Locale resolveLocale(UserModel user) {
|
||||
return LocaleHelper.getLocale(session, realm, user);
|
||||
return session.getProvider(LocaleSelectorProvider.class).resolveLocale(realm, user);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.keycloak.models.KeycloakTransaction;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.util.LocaleHelper;
|
||||
import org.keycloak.theme.FreeMarkerUtil;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.theme.beans.LocaleBean;
|
||||
|
@ -71,7 +70,7 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
|
|||
|
||||
Theme theme = session.theme().getTheme(Theme.Type.LOGIN);
|
||||
|
||||
Locale locale = LocaleHelper.getLocale(session, realm, null);
|
||||
Locale locale = session.getContext().resolveLocale(null);
|
||||
|
||||
FreeMarkerUtil freeMarker = new FreeMarkerUtil();
|
||||
Map<String, Object> attributes = initAttributes(realm, theme, locale, statusCode);
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.keycloak.services.managers.Auth;
|
|||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.util.LocaleHelper;
|
||||
import org.keycloak.services.util.ResolveRelative;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
||||
|
@ -100,7 +99,7 @@ public class AccountConsole {
|
|||
|
||||
UserModel user = null;
|
||||
if (auth != null) user = auth.getUser();
|
||||
Locale locale = LocaleHelper.getLocale(session, realm, user);
|
||||
Locale locale = session.getContext().resolveLocale(user);
|
||||
map.put("locale", locale.toLanguageTag());
|
||||
Properties messages = theme.getMessages(locale);
|
||||
map.put("msg", new MessageFormatterMethod(locale, messages));
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
*/
|
||||
public class LocaleHelper {
|
||||
|
||||
private static final String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
public static Locale getLocaleFromCookie(KeycloakSession session) {
|
||||
KeycloakContext ctx = session.getContext();
|
||||
|
||||
if (ctx.getRequestHeaders() != null && ctx.getRequestHeaders().getCookies().containsKey(LOCALE_COOKIE)) {
|
||||
String localeString = ctx.getRequestHeaders().getCookies().get(LOCALE_COOKIE).getValue();
|
||||
Locale locale = findLocale(ctx.getRealm().getSupportedLocales(), localeString);
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
String locale = ctx.getRealm().getDefaultLocale();
|
||||
if (locale != null) {
|
||||
return Locale.forLanguageTag(locale);
|
||||
} else {
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// ui_locales query parameter
|
||||
if (uriInfo != null && uriInfo.getQueryParameters().containsKey(OAuth2Constants.UI_LOCALES_PARAM)) {
|
||||
String localeString = uriInfo.getQueryParameters().getFirst(OAuth2Constants.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());
|
||||
CookieHelper.addCookie(LOCALE_COOKIE, locale, AuthenticationManager.getRealmCookiePath(realm, session.getContext().getUri()), null, null, -1, secure, true);
|
||||
}
|
||||
|
||||
private static Locale findLocale(Set<String> supportedLocales, String... localeStrings) {
|
||||
return new LocaleNegotiator(supportedLocales).invoke(localeStrings);
|
||||
}
|
||||
|
||||
private static void updateUsersLocale(UserModel user, String locale) {
|
||||
if (!locale.equals(user.getFirstAttribute("locale"))) {
|
||||
user.setSingleAttribute(UserModel.LOCALE, locale);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,7 +23,6 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.theme.ThemeProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
@ -41,7 +40,7 @@ public class P3PHelper {
|
|||
try {
|
||||
Theme theme = session.theme().getTheme(Theme.Type.LOGIN);
|
||||
|
||||
Locale locale = LocaleHelper.getLocaleFromCookie(session);
|
||||
Locale locale = session.getContext().resolveLocale(null);
|
||||
String p3pValue = theme.getMessages(locale).getProperty("p3pPolicy");
|
||||
|
||||
if (!Validation.isBlank(p3pValue)) {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
org.keycloak.locale.DefaultLocaleSelectorProviderFactory
|
|
@ -0,0 +1,57 @@
|
|||
package org.keycloak.locale;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class LocaleNegotiatorTest {
|
||||
|
||||
private LocaleNegotiator localeNegotiator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Set<String> supportedLocales = new HashSet<>();
|
||||
supportedLocales.add("de");
|
||||
supportedLocales.add("de-AT");
|
||||
supportedLocales.add("de-CH");
|
||||
supportedLocales.add("de-DE");
|
||||
supportedLocales.add("pt-BR");
|
||||
localeNegotiator = new LocaleNegotiator(supportedLocales);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchWithoutCountryCode() {
|
||||
String expectedLocaleString = "de";
|
||||
LocaleSelection actualLocale = localeNegotiator.invoke(expectedLocaleString);
|
||||
Assert.assertEquals(Locale.GERMAN, actualLocale.getLocale());
|
||||
Assert.assertEquals(expectedLocaleString, actualLocale.getLocaleString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchWithPriorityCountryCode() {
|
||||
String expectedLocaleString = "de-CH";
|
||||
LocaleSelection actualLocale = localeNegotiator.invoke(expectedLocaleString, "de");
|
||||
Assert.assertEquals(new Locale("de", "CH"), actualLocale.getLocale());
|
||||
Assert.assertEquals(expectedLocaleString, actualLocale.getLocaleString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchWithPriorityNoCountryCode() {
|
||||
String expectedLocaleString = "de";
|
||||
LocaleSelection actualLocale = localeNegotiator.invoke(expectedLocaleString, "de-CH");
|
||||
Assert.assertEquals(new Locale(expectedLocaleString), actualLocale.getLocale());
|
||||
Assert.assertEquals(expectedLocaleString, actualLocale.getLocaleString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchOmittedCountryCodeWithBestFit() {
|
||||
String expectedLocaleString = "pt";
|
||||
LocaleSelection actualLocale = localeNegotiator.invoke(expectedLocaleString, "es-ES");
|
||||
Assert.assertEquals(new Locale("pt", "BR"), actualLocale.getLocale());
|
||||
Assert.assertEquals(expectedLocaleString, actualLocale.getLocaleString());
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package org.keycloak.services.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.IsNull.nullValue;
|
||||
|
||||
public class LocaleHelperTest {
|
||||
|
||||
@Test
|
||||
public void shouldNotExceptionOnNullLocaleAttributeItem() throws Exception {
|
||||
final Method method = LocaleHelper.class.getDeclaredMethod("findLocale", Set.class, String[].class);
|
||||
method.setAccessible(true);
|
||||
Locale foundLocale = (Locale) method.invoke(null, Stream.of("en", "es", "fr").collect(Collectors.toSet()), new String[]{null});
|
||||
assertThat(foundLocale, nullValue());
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package org.keycloak.services.util;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class LocaleNegotiatorTest {
|
||||
|
||||
private LocaleNegotiator localeNegotiator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Set<String> supportedLocales = new HashSet<>();
|
||||
supportedLocales.add("de");
|
||||
supportedLocales.add("de-AT");
|
||||
supportedLocales.add("de-CH");
|
||||
supportedLocales.add("de-DE");
|
||||
supportedLocales.add("pt-BR");
|
||||
localeNegotiator = new LocaleNegotiator(supportedLocales);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchWithoutCountryCode() {
|
||||
Locale actualLocale = localeNegotiator.invoke("de");
|
||||
Assert.assertEquals(Locale.GERMAN, actualLocale);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchWithPriorityCountryCode() {
|
||||
Locale actualLocale = localeNegotiator.invoke("de-CH", "de");
|
||||
Assert.assertEquals(new Locale("de", "CH"), actualLocale);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchWithPriorityNoCountryCode() {
|
||||
Locale actualLocale = localeNegotiator.invoke("de", "de-CH");
|
||||
Assert.assertEquals(new Locale("de"), actualLocale);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchOmittedCountryCodeWithBestFit() {
|
||||
Locale actualLocale = localeNegotiator.invoke("pt", "es-ES");
|
||||
Assert.assertEquals(new Locale("pt", "BR"), actualLocale);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue