From 08dca9e89f27ddb92166ee14e40989550ad8cf84 Mon Sep 17 00:00:00 2001 From: Dirk Weinhardt <58369032+devopsix@users.noreply.github.com> Date: Mon, 1 Jun 2020 22:24:30 +0200 Subject: [PATCH] KEYCLOAK-13205 Apply locale resolution strategy to admin console. --- .../resources/admin/AdminConsole.java | 16 +- .../admin/AdminConsoleWhoAmILocaleTest.java | 169 ++++++++++++++++++ .../keycloak/testsuite/util/RealmBuilder.java | 16 ++ .../theme/base/admin/resources/js/app.js | 44 ++--- 4 files changed, 222 insertions(+), 23 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index 0971bef00f..7580140965 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -100,6 +100,7 @@ public class AdminConsole { protected String userId; protected String realm; protected String displayName; + protected Locale locale; @JsonProperty("createRealm") protected boolean createRealm; @@ -109,12 +110,13 @@ public class AdminConsole { public WhoAmI() { } - public WhoAmI(String userId, String realm, String displayName, boolean createRealm, Map> realmAccess) { + public WhoAmI(String userId, String realm, String displayName, boolean createRealm, Map> realmAccess, Locale locale) { this.userId = userId; this.realm = realm; this.displayName = displayName; this.createRealm = createRealm; this.realmAccess = realmAccess; + this.locale = locale; } public String getUserId() { @@ -156,6 +158,14 @@ public class AdminConsole { public void setRealmAccess(Map> realmAccess) { this.realmAccess = realmAccess; } + + public Locale getLocale() { + return locale; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } } /** @@ -215,7 +225,9 @@ public class AdminConsole { addRealmAccess(realm, user, realmAccess); } - return Response.ok(new WhoAmI(user.getId(), realm.getName(), displayName, createRealm, realmAccess)).build(); + Locale locale = session.getContext().resolveLocale(user); + + return Response.ok(new WhoAmI(user.getId(), realm.getName(), displayName, createRealm, realmAccess, locale)).build(); } private void addRealmAccess(RealmModel realm, UserModel user, Map> realmAdminAccess) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java new file mode 100644 index 0000000000..6c9a1805b6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java @@ -0,0 +1,169 @@ +package org.keycloak.testsuite.admin; + +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.broker.provider.util.SimpleHttp; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.keycloak.models.AdminRoles.REALM_ADMIN; +import static org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID; +import static org.keycloak.models.Constants.REALM_MANAGEMENT_CLIENT_ID; +import static org.keycloak.testsuite.util.AdminClientUtil.createAdminClient; + +public class AdminConsoleWhoAmILocaleTest extends AbstractKeycloakTest { + + private static final String REALM_I18N_OFF = "realm-i18n-off"; + private static final String REALM_I18N_ON = "realm-i18n-on"; + private static final String USER_WITHOUT_LOCALE = "user-without-locale"; + private static final String USER_WITH_LOCALE = "user-with-locale"; + private static final String PASSWORD = "password"; + private static final String DEFAULT_LOCALE = "en"; + private static final String REALM_LOCALE = "no"; + private static final String USER_LOCALE = "de"; + private static final String EXTRA_LOCALE = "ru"; + + private CloseableHttpClient client; + + @Before + public void createHttpClient() throws Exception { + client = HttpClientBuilder.create().build(); + } + + @After + public void closeHttpClient() { + try { + client.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void addTestRealms(List testRealms) { + RealmBuilder realm = RealmBuilder.create() + .name(REALM_I18N_OFF) + .internationalizationEnabled(false); + realm.user(UserBuilder.create() + .username(USER_WITHOUT_LOCALE) + .password(PASSWORD) + .role(REALM_MANAGEMENT_CLIENT_ID, REALM_ADMIN)); + realm.user(UserBuilder.create() + .username(USER_WITH_LOCALE) + .password(PASSWORD) + .addAttribute("locale", USER_LOCALE) + .role(REALM_MANAGEMENT_CLIENT_ID, REALM_ADMIN)); + testRealms.add(realm.build()); + + realm = RealmBuilder.create() + .name(REALM_I18N_ON) + .internationalizationEnabled(true) + .supportedLocales(new HashSet<>(asList(REALM_LOCALE, USER_LOCALE, EXTRA_LOCALE))) + .defaultLocale(REALM_LOCALE); + realm.user(UserBuilder.create() + .username(USER_WITHOUT_LOCALE) + .password(PASSWORD) + .role(REALM_MANAGEMENT_CLIENT_ID, REALM_ADMIN)); + realm.user(UserBuilder.create() + .username(USER_WITH_LOCALE) + .password(PASSWORD) + .addAttribute("locale", USER_LOCALE) + .role(REALM_MANAGEMENT_CLIENT_ID, REALM_ADMIN)); + testRealms.add(realm.build()); + } + + private String accessToken(String realmName, String username) throws Exception { + try (Keycloak adminClient = createAdminClient(true, realmName, username, PASSWORD, ADMIN_CLI_CLIENT_ID, null)) { + AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken(); + assertNotNull(accessToken); + return accessToken.getToken(); + } + } + + private String whoAmiUrl(String realmName) { + return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/admin/" + realmName + "/console/whoami"; + } + + @Test + public void testLocaleRealmI18nDisabledUserWithoutLocale() throws Exception { + JsonNode whoAmI = SimpleHttp + .doGet(whoAmiUrl(REALM_I18N_OFF), client) + .header("Accept", "application/json") + .auth(accessToken(REALM_I18N_OFF, USER_WITHOUT_LOCALE)) + .asJson(); + assertEquals(REALM_I18N_OFF, whoAmI.get("realm").asText()); + assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); + } + + @Test + public void testLocaleRealmI18nDisabledUserWithLocale() throws Exception { + JsonNode whoAmI = SimpleHttp + .doGet(whoAmiUrl(REALM_I18N_OFF), client) + .header("Accept", "application/json") + .auth(accessToken(REALM_I18N_OFF, USER_WITH_LOCALE)) + .asJson(); + assertEquals(REALM_I18N_OFF, whoAmI.get("realm").asText()); + assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); + } + + @Test + public void testLocaleRealmI18nEnabledUserWithoutLocale() throws Exception { + JsonNode whoAmI = SimpleHttp + .doGet(whoAmiUrl(REALM_I18N_ON), client) + .header("Accept", "application/json") + .auth(accessToken(REALM_I18N_ON, USER_WITHOUT_LOCALE)) + .asJson(); + assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + assertEquals(REALM_LOCALE, whoAmI.get("locale").asText()); + } + + @Test + public void testLocaleRealmI18nEnabledUserWithLocale() throws Exception { + JsonNode whoAmI = SimpleHttp + .doGet(whoAmiUrl(REALM_I18N_ON), client) + .header("Accept", "application/json") + .auth(accessToken(REALM_I18N_ON, USER_WITH_LOCALE)) + .asJson(); + assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + assertEquals(USER_LOCALE, whoAmI.get("locale").asText()); + } + + @Test + public void testLocaleRealmI18nEnabledAcceptLanguageHeader() throws Exception { + JsonNode whoAmI = SimpleHttp + .doGet(whoAmiUrl(REALM_I18N_ON), client) + .header("Accept", "application/json") + .auth(accessToken(REALM_I18N_ON, USER_WITHOUT_LOCALE)) + .header("Accept-Language", EXTRA_LOCALE) + .asJson(); + assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + assertEquals(EXTRA_LOCALE, whoAmI.get("locale").asText()); + } + + @Test + public void testLocaleRealmI18nEnabledKeycloakLocaleCookie() throws Exception { + JsonNode whoAmI = SimpleHttp + .doGet(whoAmiUrl(REALM_I18N_ON), client) + .header("Accept", "application/json") + .auth(accessToken(REALM_I18N_ON, USER_WITHOUT_LOCALE)) + .header("Cookie", "KEYCLOAK_LOCALE=" + EXTRA_LOCALE) + .asJson(); + assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + assertEquals(EXTRA_LOCALE, whoAmI.get("locale").asText()); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java index a8fa850100..f444253d91 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import java.util.Set; /** * @author Stian Thorgersen @@ -292,4 +293,19 @@ public class RealmBuilder { rep.setClientOfflineSessionMaxLifespan(clientOfflineSessionMaxLifespan); return this; } + + public RealmBuilder internationalizationEnabled(boolean internationalizationEnabled) { + rep.setInternationalizationEnabled(internationalizationEnabled); + return this; + } + + public RealmBuilder supportedLocales(Set supportedLocales) { + rep.setSupportedLocales(supportedLocales); + return this; + } + + public RealmBuilder defaultLocale(String defaultLocale) { + rep.setDefaultLocale(defaultLocale); + return this; + } } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 0235b0cc68..59f25c6eec 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -80,32 +80,34 @@ angular.element(document).ready(function () { location.reload(); } + auth.refreshPermissions = function(success, error) { + whoAmI(function(data) { + auth.user = data; + auth.loggedIn = true; + auth.hasAnyAccess = hasAnyAccess(data); + + success(); + }, function() { + error(); + }); + }; + + module.factory('Auth', function () { + return auth; + }); + keycloakAuth.init({ onLoad: 'login-required', pkceMethod: 'S256' }).success(function () { auth.authz = keycloakAuth; - if (auth.authz.idTokenParsed.locale) { - locale = auth.authz.idTokenParsed.locale; - } + whoAmI(function(data) { + auth.user = data; + auth.loggedIn = true; + auth.hasAnyAccess = hasAnyAccess(data); + locale = auth.user.locale || locale; - auth.refreshPermissions = function(success, error) { - whoAmI(function(data) { - auth.user = data; - auth.loggedIn = true; - auth.hasAnyAccess = hasAnyAccess(data); + loadResourceBundle(function(data) { + resourceBundle = data; - success(); - }, function() { - error(); - }); - }; - - loadResourceBundle(function(data) { - resourceBundle = data; - - auth.refreshPermissions(function () { - module.factory('Auth', function () { - return auth; - }); var injector = angular.bootstrap(document, ["keycloak"]); injector.get('$translate')('consoleTitle').then(function (consoleTitle) {