KEYCLOAK-9632 Improve handling of user locale
This commit is contained in:
parent
4b09a4a2af
commit
42773592ca
24 changed files with 380 additions and 352 deletions
|
@ -215,13 +215,6 @@ declare namespace Keycloak {
|
|||
*/
|
||||
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).
|
||||
* Options 'hidden' and 'location' are not affected by these arguments.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Locale> 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<String> 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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String> supportedLocales;
|
||||
|
||||
public LocaleNegotiator(Set<String> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -41,6 +41,7 @@ public class AuthorizationEndpointRequest {
|
|||
String idpHint;
|
||||
String action;
|
||||
String claims;
|
||||
String uiLocales;
|
||||
Map<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -22,3 +22,4 @@ org.keycloak.authentication.requiredactions.VerifyEmail
|
|||
org.keycloak.authentication.requiredactions.TermsAndConditions
|
||||
org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory
|
||||
org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory
|
||||
org.keycloak.authentication.requiredactions.UpdateUserLocaleAction
|
|
@ -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<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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -132,6 +132,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();
|
||||
|
||||
|
|
|
@ -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 <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<RequiredActionProviderRepresentation> actions = realm.flows().getRequiredActions();
|
||||
|
||||
// Checking if the actions are in alphabetical order
|
||||
List<String> nameList = actions.stream().map(x -> x.getName()).collect(Collectors.toList());
|
||||
log.debug("Obtained required actions: " + nameList);
|
||||
List<String> 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();
|
||||
|
|
|
@ -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 @@
|
|||
</div>
|
||||
</#if>
|
||||
|
||||
<#if realm.internationalizationEnabled && supportedLocales?size gt 1>
|
||||
<div class="pf-c-page__header-tools-group pf-m-icons">
|
||||
<div id="landing-locale-dropdown" class="pf-c-dropdown">
|
||||
<button onclick="toggleLocaleDropdown();" class="pf-c-dropdown__toggle pf-m-plain" id="landing-locale-dropdown-button" aria-expanded="false" aria-haspopup="true">
|
||||
<span class="pf-c-dropdown__toggle-text">
|
||||
${msg("locale_" + locale)}
|
||||
</span>
|
||||
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul id="landing-locale-dropdown-list" class="pf-c-dropdown__menu" aria-labeledby="landing-locale-dropdown-button" role="menu" hidden>
|
||||
<#list supportedLocales as locale, label>
|
||||
<#if referrer?has_content && referrer_uri?has_content>
|
||||
<li id="landing-locale-${locale}" role="none"><a href="${baseUrl}/?kc_locale=${locale}&referrer=${referrer}&referrer_uri=${referrer_uri}" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${label}</a></li>
|
||||
<#else>
|
||||
<li id="landing-locale-${locale}" role="none"><a href="${baseUrl}/?kc_locale=${locale}" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${label}</a></li>
|
||||
</#if>
|
||||
</#list>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="pf-c-page__header-tools-group pf-m-icons">
|
||||
<button id="landingSignInButton" tabindex="0" style="display:none" onclick="keycloak.login();" class="pf-c-button pf-m-primary" type="button">${msg("doLogIn")}</button>
|
||||
<button id="landingSignOutButton" tabindex="0" style="display:none" onclick="keycloak.logout();" class="pf-c-button pf-m-primary" type="button">${msg("doSignOut")}</button>
|
||||
|
@ -195,20 +173,6 @@
|
|||
</li>
|
||||
</#if>
|
||||
|
||||
<!-- locale selector for mobile -->
|
||||
<#if realm.internationalizationEnabled && supportedLocales?size gt 1>
|
||||
<li role="none" aria-expanded="false" onclick="toggleMobileChooseLocale();"><a href="#" id="landing-mobile-local-toggle" class="pf-c-dropdown__menu-item">${msg("locale_" + locale)} <i id="landingMobileLocaleSelectedIcon" class="fas fa-angle-right pf-c-options-menu__menu-item-icon" aria-hidden="true"></i></a></li>
|
||||
<#list supportedLocales as locale, label>
|
||||
<#if referrer?has_content && referrer_uri?has_content>
|
||||
<li role="none" id="landing-mobile-locale-${locale}" style="display:none"><a href="${baseUrl}/?kc_locale=${locale}&referrer=${referrer}&referrer_uri=${referrer_uri}" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${label}</a></li>
|
||||
<#else>
|
||||
<li role="none" id="landing-mobile-locale-${locale}" style="display:none"><a href="${baseUrl}/?kc_locale=${locale}" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${label}</a></li>
|
||||
</#if>
|
||||
</#list>
|
||||
<li id="landingMobileLocaleSeparator" class="pf-c-dropdown__separator" role="separator" style="display:none"></li>
|
||||
</#if>
|
||||
<!-- end locale selector for mobile -->
|
||||
|
||||
<li id="landingSignInLink" role="none" style="display:none">
|
||||
<a href="#" onclick="keycloak.login();" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${msg("doLogIn")}</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in a new issue