From 42773592ca61bca3953d274e6f6053f4f792361c Mon Sep 17 00:00:00 2001 From: stianst Date: Thu, 6 Feb 2020 14:23:44 +0100 Subject: [PATCH] KEYCLOAK-9632 Improve handling of user locale --- .../oidc/js/src/main/resources/keycloak.d.ts | 7 - .../oidc/js/src/main/resources/keycloak.js | 4 - .../migration/migrators/MigrateTo9_0_0.java | 2 + .../models/utils/DefaultRequiredActions.java | 13 + .../locale/LocaleSelectorProvider.java | 20 +- .../UpdateUserLocaleAction.java | 66 ++++ .../locale/DefaultLocaleSelectorProvider.java | 281 +++++++++++------- .../org/keycloak/locale/LocaleNegotiator.java | 53 ---- .../org/keycloak/locale/LocaleSelection.java | 37 --- .../oidc/endpoints/AuthorizationEndpoint.java | 3 +- .../request/AuthorizationEndpointRequest.java | 5 + .../request/AuthzEndpointRequestParser.java | 1 + .../services/DefaultKeycloakContext.java | 2 +- .../resources/LoginActionsService.java | 25 +- .../resources/account/AccountFormService.java | 7 + .../AuthenticationManagementResource.java | 1 + ...cloak.authentication.RequiredActionFactory | 3 +- .../keycloak/locale/LocaleNegotiatorTest.java | 57 ---- .../authentication/RequiredActionsTest.java | 1 + .../keycloak/testsuite/i18n/EmailTest.java | 3 + .../testsuite/i18n/LoginPageTest.java | 45 +++ .../javascript/JavascriptAdapterTest.java | 26 -- .../migration/AbstractMigrationTest.java | 32 +- .../theme/keycloak-preview/account/index.ftl | 38 +-- 24 files changed, 380 insertions(+), 352 deletions(-) create mode 100644 services/src/main/java/org/keycloak/authentication/requiredactions/UpdateUserLocaleAction.java delete mode 100644 services/src/main/java/org/keycloak/locale/LocaleNegotiator.java delete mode 100644 services/src/main/java/org/keycloak/locale/LocaleSelection.java delete mode 100644 services/src/test/java/org/keycloak/locale/LocaleNegotiatorTest.java diff --git a/adapters/oidc/js/src/main/resources/keycloak.d.ts b/adapters/oidc/js/src/main/resources/keycloak.d.ts index ba860c311c..2b8fd68292 100644 --- a/adapters/oidc/js/src/main/resources/keycloak.d.ts +++ b/adapters/oidc/js/src/main/resources/keycloak.d.ts @@ -214,13 +214,6 @@ declare namespace Keycloak { * of the OIDC 1.0 specification. */ locale?: string; - - /** - * Specifies the desired Keycloak locale for the UI. This differs from - * the locale param in that it tells the Keycloak server to set a cookie and update - * the user's profile to a new preferred locale. - */ - kcLocale?: string; /** * Specifies arguments that are passed to the Cordova in-app-browser (if applicable). diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index f48c1bb64b..a268e7cb0c 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -439,10 +439,6 @@ url += '&ui_locales=' + encodeURIComponent(options.locale); } - if (options && options.kcLocale) { - url += '&kc_locale=' + encodeURIComponent(options.kcLocale); - } - if (kc.pkceMethod) { var codeVerifier = generateCodeVerifier(96); callbackState.pkceCodeVerifier = codeVerifier; diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo9_0_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo9_0_0.java index 186e0f32ff..3d734518b3 100644 --- a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo9_0_0.java +++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo9_0_0.java @@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.utils.DefaultRequiredActions; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.RealmRepresentation; @@ -58,6 +59,7 @@ public class MigrateTo9_0_0 implements Migration { addAccountConsoleClient(realm); addAccountApiRoles(realm); enablePkceAdminAccountClients(realm); + DefaultRequiredActions.addUpdateLocaleAction(realm); } private void addAccountApiRoles(RealmModel realm) { diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java index 19b802ce8a..f2ecb6bef1 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java @@ -83,6 +83,19 @@ public class DefaultRequiredActions { realm.addRequiredActionProvider(termsAndConditions); } + addUpdateLocaleAction(realm); + } + public static void addUpdateLocaleAction(RealmModel realm) { + if (realm.getRequiredActionProviderByAlias("update_user_locale") == null) { + RequiredActionProviderModel updateUserLocale = new RequiredActionProviderModel(); + updateUserLocale.setEnabled(true); + updateUserLocale.setAlias("update_user_locale"); + updateUserLocale.setName("Update User Locale"); + updateUserLocale.setProviderId("update_user_locale"); + updateUserLocale.setDefaultAction(false); + updateUserLocale.setPriority(1000); + realm.addRequiredActionProvider(updateUserLocale); + } } } diff --git a/server-spi/src/main/java/org/keycloak/locale/LocaleSelectorProvider.java b/server-spi/src/main/java/org/keycloak/locale/LocaleSelectorProvider.java index a835dc4d05..bae4e72303 100644 --- a/server-spi/src/main/java/org/keycloak/locale/LocaleSelectorProvider.java +++ b/server-spi/src/main/java/org/keycloak/locale/LocaleSelectorProvider.java @@ -21,13 +21,29 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; +import javax.ws.rs.core.UriInfo; import java.util.Locale; public interface LocaleSelectorProvider extends Provider { + + String LOCALE_COOKIE = "KEYCLOAK_LOCALE"; + String KC_LOCALE_PARAM = "kc_locale"; + + String CLIENT_REQUEST_LOCALE = "locale_client_requested"; + String USER_REQUEST_LOCALE = "locale_user_requested"; + /** * Resolve the locale which should be used for the request + * * @param user * @return */ - Locale resolveLocale(RealmModel realm, UserModel user); -} + Locale resolveLocale(UserModel user); + + void updateUsersLocale(UserModel user, String locale); + + void updateLocaleCookie(RealmModel realm, String locale, UriInfo uriInfo); + + void expireLocaleCookie(RealmModel realm, UriInfo uriInfo); + +} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateUserLocaleAction.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateUserLocaleAction.java new file mode 100644 index 0000000000..295a6c682c --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateUserLocaleAction.java @@ -0,0 +1,66 @@ +package org.keycloak.authentication.requiredactions; + +import org.keycloak.Config; +import org.keycloak.authentication.RequiredActionContext; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.locale.LocaleSelectorProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.UserModel; + +public class UpdateUserLocaleAction implements RequiredActionProvider, RequiredActionFactory { + + @Override + public String getDisplayText() { + return "Update User Locale"; + } + + @Override + public void evaluateTriggers(RequiredActionContext context) { + String userRequestedLocale = context.getAuthenticationSession().getAuthNote(LocaleSelectorProvider.USER_REQUEST_LOCALE); + if (userRequestedLocale != null) { + LocaleSelectorProvider provider = context.getSession().getProvider(LocaleSelectorProvider.class); + provider.updateUsersLocale(context.getUser(), userRequestedLocale); + } else { + String userLocale = context.getUser().getFirstAttribute(UserModel.LOCALE); + LocaleSelectorProvider provider = context.getSession().getProvider(LocaleSelectorProvider.class); + if (userLocale != null) { + provider.updateLocaleCookie(context.getRealm(), userLocale, context.getUriInfo()); + } else { + provider.expireLocaleCookie(context.getRealm(), context.getUriInfo()); + } + } + } + + @Override + public void requiredActionChallenge(RequiredActionContext context) { + } + + @Override + public void processAction(RequiredActionContext context) { + } + + @Override + public RequiredActionProvider create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return "update_user_locale"; + } + +} diff --git a/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java b/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java index ef4d50bba4..e2c488c36b 100644 --- a/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java +++ b/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java @@ -17,152 +17,207 @@ package org.keycloak.locale; import org.jboss.logging.Logger; -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 org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.storage.ReadOnlyException; +import javax.ws.rs.core.Cookie; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; +import java.util.List; import java.util.Locale; +import java.util.Set; public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider { - private static final Logger logger = Logger.getLogger(DefaultLocaleSelectorProvider.class); + private static final Logger logger = Logger.getLogger(LocaleSelectorProvider.class); - protected static final String LOCALE_COOKIE = "KEYCLOAK_LOCALE"; - protected static final String KC_LOCALE_PARAM = "kc_locale"; - - protected final KeycloakSession session; + private 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); - } + public Locale resolveLocale(UserModel user) { + RealmModel realm = session.getContext().getRealm(); + HttpHeaders requestHeaders = session.getContext().getRequestHeaders(); + AuthenticationSessionModel session = this.session.getContext().getAuthenticationSession(); - @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()); } + + Locale userLocale = getUserLocale(realm, session, user, requestHeaders); + if (userLocale != null) { + return userLocale; + } + + String realmDefaultLocale = realm.getDefaultLocale(); + if (realmDefaultLocale != null) { + return Locale.forLanguageTag(realmDefaultLocale); + } + + return Locale.ENGLISH; } - 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) { + public void updateUsersLocale(UserModel user, String locale) { if (!locale.equals(user.getFirstAttribute("locale"))) { try { user.setSingleAttribute(UserModel.LOCALE, locale); + updateLocaleCookie(session.getContext().getRealm(), locale, session.getContext().getUri()); } catch (ReadOnlyException e) { logger.debug("Attempt to store 'locale' attribute to read only user model. Ignoring exception", e); } } + logger.debugv("Setting locale for user {0} to {1}", user.getUsername(), locale); + } + + public void updateLocaleCookie(RealmModel realm, String locale, UriInfo uriInfo) { + boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost()); + CookieHelper.addCookie(LocaleSelectorProvider.LOCALE_COOKIE, locale, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, secure, true); + logger.debugv("Updating locale cookie to {0}", locale); + } + + public void expireLocaleCookie(RealmModel realm, UriInfo uriInfo) { + boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost()); + CookieHelper.addCookie(LocaleSelectorProvider.LOCALE_COOKIE, "", AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, "Expiring cookie", 0, secure, true); + } + + private Locale getUserLocale(RealmModel realm, AuthenticationSessionModel session, UserModel user, HttpHeaders requestHeaders) { + Locale locale; + + locale = getUserSelectedLocale(realm, session); + if (locale != null) { + return locale; + } + + locale = getUserProfileSelection(realm, user); + if (locale != null) { + return locale; + } + + locale = getClientSelectedLocale(realm, session); + if (locale != null) { + return locale; + } + + locale = getLocaleCookieSelection(realm, requestHeaders); + if (locale != null) { + return locale; + } + + locale = getAcceptLanguageHeaderLocale(realm, requestHeaders); + if (locale != null) { + return locale; + } + + return null; + } + + private Locale getUserSelectedLocale(RealmModel realm, AuthenticationSessionModel session) { + if (session == null) { + return null; + } + + String locale = session.getAuthNote(USER_REQUEST_LOCALE); + if (locale == null) { + return null; + } + + return findLocale(realm, locale); + } + + private Locale getUserProfileSelection(RealmModel realm, UserModel user) { + if (user == null) { + return null; + } + + String locale = user.getFirstAttribute(UserModel.LOCALE); + if (locale == null) { + return null; + } + + return findLocale(realm, locale); + } + + private Locale getClientSelectedLocale(RealmModel realm, AuthenticationSessionModel session) { + if (session == null) { + return null; + } + + String locale = session.getAuthNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE); + if (locale == null) { + return null; + } + + return findLocale(realm, locale.split(" ")); + } + + private Locale getLocaleCookieSelection(RealmModel realm, HttpHeaders httpHeaders) { + if (httpHeaders == null) { + return null; + } + + Cookie localeCookie = httpHeaders.getCookies().get(LOCALE_COOKIE); + if (localeCookie == null) { + return null; + } + + return findLocale(realm, localeCookie.getValue()); + } + + private Locale getAcceptLanguageHeaderLocale(RealmModel realm, HttpHeaders httpHeaders) { + if (httpHeaders == null) { + return null; + } + + List acceptableLanguages = httpHeaders.getAcceptableLanguages(); + if (acceptableLanguages == null || acceptableLanguages.isEmpty()) { + return null; + } + + for (Locale l : acceptableLanguages) { + Locale locale = findLocale(realm, l.toLanguageTag()); + if (locale != null) { + return locale; + } + } + + return null; + } + + private Locale findLocale(RealmModel realm, String... localeStrings) { + Set supportedLocales = realm.getSupportedLocales(); + for (String localeString : localeStrings) { + if (localeString != null) { + Locale result = null; + Locale search = Locale.forLanguageTag(localeString); + for (String languageTag : supportedLocales) { + Locale locale = Locale.forLanguageTag(languageTag); + if (locale.getLanguage().equals(search.getLanguage())) { + if (search.getCountry().equals("") ^ locale.getCountry().equals("") && result == null) { + result = locale; + } + if (locale.getCountry().equals(search.getCountry())) { + return locale; + } + } + } + if (result != null) { + return result; + } + } + } + return null; + } + + @Override + public void close() { } } diff --git a/services/src/main/java/org/keycloak/locale/LocaleNegotiator.java b/services/src/main/java/org/keycloak/locale/LocaleNegotiator.java deleted file mode 100644 index 2d422856d8..0000000000 --- a/services/src/main/java/org/keycloak/locale/LocaleNegotiator.java +++ /dev/null @@ -1,53 +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.locale; - -import java.util.Locale; -import java.util.Set; - -public class LocaleNegotiator { - private Set supportedLocales; - - public LocaleNegotiator(Set supportedLocales) { - this.supportedLocales = supportedLocales; - } - - public LocaleSelection invoke(String... localeStrings) { - for (String localeString : localeStrings) { - if (localeString != null) { - Locale result = null; - Locale search = Locale.forLanguageTag(localeString); - for (String languageTag : supportedLocales) { - Locale locale = Locale.forLanguageTag(languageTag); - if (locale.getLanguage().equals(search.getLanguage())) { - if (search.getCountry().equals("") ^ locale.getCountry().equals("") && result == null) { - result = locale; - } - if (locale.getCountry().equals(search.getCountry())) { - return new LocaleSelection(localeString, locale); - } - } - } - if (result != null) { - return new LocaleSelection(localeString, result); - } - } - } - return null; - } - -} diff --git a/services/src/main/java/org/keycloak/locale/LocaleSelection.java b/services/src/main/java/org/keycloak/locale/LocaleSelection.java deleted file mode 100644 index 0630377a27..0000000000 --- a/services/src/main/java/org/keycloak/locale/LocaleSelection.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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; - } -} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index e595b34b41..c9b4079744 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -26,6 +26,7 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; +import org.keycloak.locale.LocaleSelectorProvider; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; @@ -55,7 +56,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -444,6 +444,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { if (request.getClaims()!= null) authenticationSession.setClientNote(OIDCLoginProtocol.CLAIMS_PARAM, request.getClaims()); if (request.getAcr() != null) authenticationSession.setClientNote(OIDCLoginProtocol.ACR_PARAM, request.getAcr()); if (request.getDisplay() != null) authenticationSession.setAuthNote(OAuth2Constants.DISPLAY, request.getDisplay()); + if (request.getUiLocales() != null) authenticationSession.setAuthNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE, request.getUiLocales()); // https://tools.ietf.org/html/rfc7636#section-4 if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java index b3426d9165..248a20c433 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java @@ -41,6 +41,7 @@ public class AuthorizationEndpointRequest { String idpHint; String action; String claims; + String uiLocales; Map additionalReqParams = new HashMap<>(); // https://tools.ietf.org/html/rfc7636#section-6.1 @@ -126,4 +127,8 @@ public class AuthorizationEndpointRequest { public String getInvalidRequestMessage() { return invalidRequestMessage; } + + public String getUiLocales() { + return uiLocales; + } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java index 50cbc40353..92b61e7c08 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java @@ -94,6 +94,7 @@ abstract class AuthzEndpointRequestParser { request.claims = replaceIfNotNull(request.claims, getParameter(OIDCLoginProtocol.CLAIMS_PARAM)); request.acr = replaceIfNotNull(request.acr, getParameter(OIDCLoginProtocol.ACR_PARAM)); request.display = replaceIfNotNull(request.display, getParameter(OAuth2Constants.DISPLAY)); + request.uiLocales = replaceIfNotNull(request.uiLocales, getParameter(OAuth2Constants.UI_LOCALES_PARAM)); // https://tools.ietf.org/html/rfc7636#section-6.1 request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM)); diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java index cd22b0a6dc..f842be6707 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java @@ -129,7 +129,7 @@ public class DefaultKeycloakContext implements KeycloakContext { @Override public Locale resolveLocale(UserModel user) { - return session.getProvider(LocaleSelectorProvider.class).resolveLocale(realm, user); + return session.getProvider(LocaleSelectorProvider.class).resolveLocale(user); } @Override 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 5a2a623a69..dd269eed58 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -47,6 +47,7 @@ import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.exceptions.TokenNotActiveException; +import org.keycloak.locale.LocaleSelectorProvider; import org.keycloak.models.ActionTokenKeyModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; @@ -258,9 +259,22 @@ public class LoginActionsService { AuthenticationSessionModel authSession = checks.getAuthenticationSession(); boolean actionRequest = checks.isActionRequest(); + processLocaleParam(authSession); + return processAuthentication(actionRequest, execution, authSession, null); } + protected void processLocaleParam(AuthenticationSessionModel authSession) { + if (authSession != null && realm.isInternationalizationEnabled()) { + String locale = session.getContext().getUri().getQueryParameters().getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM); + if (locale != null) { + authSession.setAuthNote(LocaleSelectorProvider.USER_REQUEST_LOCALE, locale); + LocaleSelectorProvider localeSelectorProvider = session.getProvider(LocaleSelectorProvider.class); + localeSelectorProvider.updateLocaleCookie(realm, locale, session.getContext().getUri()); + } + } + } + protected Response processAuthentication(boolean action, String execution, AuthenticationSessionModel authSession, String errorMessage) { return processFlow(action, execution, authSession, AUTHENTICATE_PATH, AuthenticationFlowResolver.resolveBrowserFlow(authSession), errorMessage, new AuthenticationProcessor()); } @@ -356,6 +370,7 @@ public class LoginActionsService { @QueryParam(Constants.TAB_ID) String tabId) { ClientModel client = realm.getClientByClientId(clientId); AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm, client, tabId); + processLocaleParam(authSession); // we allow applications to link to reset credentials without going through OAuth or SAML handshakes if (authSession == null && code == null) { @@ -543,6 +558,8 @@ public class LoginActionsService { authSession = handler.startFreshAuthenticationSession(token, tokenContext); tokenContext.setAuthenticationSession(authSession, true); + + processLocaleParam(authSession); } initLoginEvent(authSession); @@ -678,6 +695,8 @@ public class LoginActionsService { AuthenticationSessionModel authSession = checks.getAuthenticationSession(); + processLocaleParam(authSession); + AuthenticationManager.expireIdentityCookie(realm, session.getContext().getUri(), clientConnection); return processRegistration(checks.isActionRequest(), execution, authSession, null); @@ -738,6 +757,8 @@ public class LoginActionsService { event.detail(Details.CODE_ID, code); final AuthenticationSessionModel authSession = checks.getAuthenticationSession(); + processLocaleParam(authSession); + String noteKey = firstBrokerLogin ? AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE : PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT; SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, noteKey); if (serializedCtx == null) { @@ -761,7 +782,6 @@ public class LoginActionsService { event.detail(Details.IDENTITY_PROVIDER, identityProviderAlias) .detail(Details.IDENTITY_PROVIDER_USERNAME, brokerContext.getUsername()); - AuthenticationProcessor processor = new AuthenticationProcessor() { @Override @@ -956,6 +976,9 @@ public class LoginActionsService { } AuthenticationSessionModel authSession = checks.getAuthenticationSession(); + + processLocaleParam(authSession); + if (!checks.isActionRequest()) { initLoginEvent(authSession); event.event(EventType.CUSTOM_REQUIRED_ACTION); diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java index e2ed221247..df96293c75 100755 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java @@ -39,6 +39,7 @@ import org.keycloak.events.EventType; import org.keycloak.forms.account.AccountPages; import org.keycloak.forms.account.AccountProvider; import org.keycloak.forms.login.LoginFormsProvider; +import org.keycloak.locale.LocaleSelectorProvider; import org.keycloak.models.AccountRoles; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; @@ -221,6 +222,12 @@ public class AccountFormService extends AbstractSecuredLocalService { } } + String locale = session.getContext().getUri().getQueryParameters().getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM); + if (locale != null) { + LocaleSelectorProvider localeSelectorProvider = session.getProvider(LocaleSelectorProvider.class); + localeSelectorProvider.updateUsersLocale(auth.getUser(), locale); + } + return account.createResponse(page); } else { return login(path); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index a82537ec95..6ba347954f 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -920,6 +920,7 @@ public class AuthenticationManagementResource { public static RequiredActionProviderRepresentation toRepresentation(RequiredActionProviderModel model) { RequiredActionProviderRepresentation rep = new RequiredActionProviderRepresentation(); rep.setAlias(model.getAlias()); + rep.setProviderId(model.getProviderId()); rep.setName(model.getName()); rep.setDefaultAction(model.isDefaultAction()); rep.setPriority(model.getPriority()); diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory index 8de4613a91..6729020fab 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory @@ -21,4 +21,5 @@ org.keycloak.authentication.requiredactions.UpdateTotp org.keycloak.authentication.requiredactions.VerifyEmail org.keycloak.authentication.requiredactions.TermsAndConditions org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory -org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory \ No newline at end of file +org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory +org.keycloak.authentication.requiredactions.UpdateUserLocaleAction \ No newline at end of file diff --git a/services/src/test/java/org/keycloak/locale/LocaleNegotiatorTest.java b/services/src/test/java/org/keycloak/locale/LocaleNegotiatorTest.java deleted file mode 100644 index 0da68d50c5..0000000000 --- a/services/src/test/java/org/keycloak/locale/LocaleNegotiatorTest.java +++ /dev/null @@ -1,57 +0,0 @@ -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 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()); - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java index 9634513661..00f949155c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java @@ -49,6 +49,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest { addRequiredAction(expected, "UPDATE_PROFILE", "Update Profile", true, false, null); addRequiredAction(expected, "VERIFY_EMAIL", "Verify Email", true, false, null); addRequiredAction(expected, "terms_and_conditions", "Terms and Conditions", false, false, null); + addRequiredAction(expected, "update_user_locale", "Update User Locale", true, false, null); compareRequiredActions(expected, sort(result)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java index a7c33c4b34..b687587537 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java @@ -131,6 +131,9 @@ public class EmailTest extends AbstractI18NTest { } String link = MailUtils.getPasswordResetEmailLink(greenMail.getLastReceivedMessage()); + + // Make sure kc_locale added to link doesn't set locale + link += "&kc_locale=de"; DroneUtils.getCurrentDriver().navigate().to(link); WaitUtils.waitForPageToLoad(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java index cc1c3108a2..687351790b 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.adapters.HttpClientBuilder; import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.locale.LocaleSelectorProvider; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -41,6 +42,7 @@ import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; import org.keycloak.testsuite.pages.OAuthGrantPage; import org.keycloak.testsuite.util.IdentityProviderBuilder; +import org.openqa.selenium.Cookie; /** * @author Michael Gerber @@ -197,6 +199,49 @@ public class LoginPageTest extends AbstractI18NTest { oauth.clientId("test-app"); } + @Test + public void languageUserUpdates() { + ProfileAssume.assumeCommunity(); + + loginPage.open(); + loginPage.openLanguage("Deutsch"); + + Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText()); + + Cookie localeCookie = driver.manage().getCookieNamed(LocaleSelectorProvider.LOCALE_COOKIE); + Assert.assertEquals("de", localeCookie.getValue()); + + loginPage.login("test-user@localhost", "password"); + + UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost"); + UserRepresentation userRep = user.toRepresentation(); + Assert.assertEquals("de", userRep.getAttributes().get("locale").get(0)); + + appPage.logout(); + + loginPage.open(); + + Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText()); + + userRep.getAttributes().remove("locale"); + user.update(userRep); + + loginPage.open(); + loginPage.login("test-user@localhost", "password"); + + // User locale should not be updated due to previous cookie + userRep = user.toRepresentation(); + Assert.assertNull(userRep.getAttributes()); + + appPage.logout(); + + loginPage.open(); + + // Cookie should be removed as last user to login didn't have a locale + localeCookie = driver.manage().getCookieNamed(LocaleSelectorProvider.LOCALE_COOKIE); + Assert.assertNull(localeCookie); + } + private void switchLanguageToGermanAndBack(String expectedEnglishMessage, String expectedGermanMessage, LanguageComboboxAwarePage page) { // Switch language to Deutsch diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java index 29261e0663..7531898db9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java @@ -133,32 +133,6 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest { .init(defaultArguments(), this::assertInitNotAuth); } - @Test - public void testLoginWithKCLocale() { - ProfileAssume.assumeCommunity(); - - RealmRepresentation testRealmRep = testRealmResource().toRepresentation(); - testRealmRep.setInternationalizationEnabled(true); - testRealmRep.setDefaultLocale("en"); - testRealmRep.setSupportedLocales(Stream.of("en", "de").collect(Collectors.toSet())); - testRealmResource().update(testRealmRep); - - testExecutor.init(defaultArguments(), this::assertInitNotAuth) - .login(this::assertOnLoginPage) - .loginForm(testUser, this::assertOnTestAppUrl) - .init(defaultArguments(), this::assertSuccessfullyLoggedIn) - .logout(this::assertOnTestAppUrl) - - .init(defaultArguments(), this::assertInitNotAuth) - .login("{kcLocale: 'de'}", assertLocaleIsSet("de")) - .loginForm(testUser, this::assertOnTestAppUrl) - .init(defaultArguments(), this::assertSuccessfullyLoggedIn) - .logout(this::assertOnTestAppUrl) - - .init(defaultArguments(), this::assertInitNotAuth) - .login("{kcLocale: 'en'}", assertLocaleIsSet("en")); - } - @Test public void testLoginWithPkceS256() { JSObjectBuilder pkceS256 = defaultArguments().pkceS256(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java index 83a3bbfb0b..cc9e34a27f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java @@ -79,14 +79,10 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT; @@ -284,6 +280,8 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { testAccountClient(migrationRealm); testAdminClientPkce(masterRealm); testAdminClientPkce(migrationRealm); + testUserLocaleActionAdded(masterRealm); + testUserLocaleActionAdded(migrationRealm); } private void testAccountClient(RealmResource realm) { @@ -724,17 +722,15 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { log.info("Taking required actions from realm: " + realm.toRepresentation().getRealm()); List actions = realm.flows().getRequiredActions(); - // Checking if the actions are in alphabetical order - List nameList = actions.stream().map(x -> x.getName()).collect(Collectors.toList()); - log.debug("Obtained required actions: " + nameList); - List sortedByName = nameList.stream().sorted().collect(Collectors.toList()); - log.debug("Manually sorted required actions: " + sortedByName); - assertThat(nameList, is(equalTo(sortedByName))); - // Checking the priority int priority = 10; for (RequiredActionProviderRepresentation action : actions) { - assertThat(action.getPriority(), is(equalTo(priority))); + if (action.getAlias().equals("update_user_locale")) { + assertEquals(1000, action.getPriority()); + } else { + assertEquals(priority, action.getPriority()); + } + priority += 10; } } @@ -817,6 +813,18 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { Assert.assertEquals(subflowExecution.getLevel() + 1, childEx2.getLevel()); } + protected void testUserLocaleActionAdded(RealmResource realm) { + RequiredActionProviderRepresentation rep = realm.flows().getRequiredAction("update_user_locale"); + + assertNotNull(rep); + assertEquals("update_user_locale", rep.getAlias()); + assertEquals("update_user_locale", rep.getProviderId()); + assertEquals("Update User Locale", rep.getName()); + assertEquals(1000, rep.getPriority()); + assertTrue(rep.isEnabled()); + assertFalse(rep.isDefaultAction()); + } + protected void testMigrationTo2_x() throws Exception { testMigrationTo2_0_0(); testMigrationTo2_1_0(); diff --git a/themes/src/main/resources/theme/keycloak-preview/account/index.ftl b/themes/src/main/resources/theme/keycloak-preview/account/index.ftl index 9972fed0ee..61b9aa9acb 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/index.ftl +++ b/themes/src/main/resources/theme/keycloak-preview/account/index.ftl @@ -29,7 +29,7 @@ var features = { isRegistrationEmailAsUsername : ${realm.registrationEmailAsUsername?c}, isEditUserNameAllowed : ${realm.editUsernameAllowed?c}, - isInternationalizationEnabled : ${realm.internationalizationEnabled?c}, + isInternationalizationEnabled : false, isLinkedAccountsEnabled : ${realm.identityFederationEnabled?c}, isEventsEnabled : ${isEventsEnabled?c}, isMyResourcesEnabled : ${(realm.userManagedAccessAllowed && isAuthorizationEnabled)?c}, @@ -155,28 +155,6 @@ - <#if realm.internationalizationEnabled && supportedLocales?size gt 1> -
-
- - -
-
- -
@@ -195,20 +173,6 @@ - - <#if realm.internationalizationEnabled && supportedLocales?size gt 1> - - <#list supportedLocales as locale, label> - <#if referrer?has_content && referrer_uri?has_content> - - <#else> - - - - - - -