KEYCLOAK-13205 Apply locale resolution strategy to admin console.

This commit is contained in:
Dirk Weinhardt 2020-06-01 22:24:30 +02:00 committed by Stan Silvert
parent 219d2b9a7c
commit 08dca9e89f
4 changed files with 222 additions and 23 deletions

View file

@ -100,6 +100,7 @@ public class AdminConsole {
protected String userId; protected String userId;
protected String realm; protected String realm;
protected String displayName; protected String displayName;
protected Locale locale;
@JsonProperty("createRealm") @JsonProperty("createRealm")
protected boolean createRealm; protected boolean createRealm;
@ -109,12 +110,13 @@ public class AdminConsole {
public WhoAmI() { public WhoAmI() {
} }
public WhoAmI(String userId, String realm, String displayName, boolean createRealm, Map<String, Set<String>> realmAccess) { public WhoAmI(String userId, String realm, String displayName, boolean createRealm, Map<String, Set<String>> realmAccess, Locale locale) {
this.userId = userId; this.userId = userId;
this.realm = realm; this.realm = realm;
this.displayName = displayName; this.displayName = displayName;
this.createRealm = createRealm; this.createRealm = createRealm;
this.realmAccess = realmAccess; this.realmAccess = realmAccess;
this.locale = locale;
} }
public String getUserId() { public String getUserId() {
@ -156,6 +158,14 @@ public class AdminConsole {
public void setRealmAccess(Map<String, Set<String>> realmAccess) { public void setRealmAccess(Map<String, Set<String>> realmAccess) {
this.realmAccess = 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); 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<String, Set<String>> realmAdminAccess) { private void addRealmAccess(RealmModel realm, UserModel user, Map<String, Set<String>> realmAdminAccess) {

View file

@ -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<RealmRepresentation> 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());
}
}

View file

@ -30,6 +30,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -292,4 +293,19 @@ public class RealmBuilder {
rep.setClientOfflineSessionMaxLifespan(clientOfflineSessionMaxLifespan); rep.setClientOfflineSessionMaxLifespan(clientOfflineSessionMaxLifespan);
return this; return this;
} }
public RealmBuilder internationalizationEnabled(boolean internationalizationEnabled) {
rep.setInternationalizationEnabled(internationalizationEnabled);
return this;
}
public RealmBuilder supportedLocales(Set<String> supportedLocales) {
rep.setSupportedLocales(supportedLocales);
return this;
}
public RealmBuilder defaultLocale(String defaultLocale) {
rep.setDefaultLocale(defaultLocale);
return this;
}
} }

View file

@ -80,13 +80,6 @@ angular.element(document).ready(function () {
location.reload(); location.reload();
} }
keycloakAuth.init({ onLoad: 'login-required', pkceMethod: 'S256' }).success(function () {
auth.authz = keycloakAuth;
if (auth.authz.idTokenParsed.locale) {
locale = auth.authz.idTokenParsed.locale;
}
auth.refreshPermissions = function(success, error) { auth.refreshPermissions = function(success, error) {
whoAmI(function(data) { whoAmI(function(data) {
auth.user = data; auth.user = data;
@ -99,13 +92,22 @@ angular.element(document).ready(function () {
}); });
}; };
loadResourceBundle(function(data) {
resourceBundle = data;
auth.refreshPermissions(function () {
module.factory('Auth', function () { module.factory('Auth', function () {
return auth; return auth;
}); });
keycloakAuth.init({ onLoad: 'login-required', pkceMethod: 'S256' }).success(function () {
auth.authz = keycloakAuth;
whoAmI(function(data) {
auth.user = data;
auth.loggedIn = true;
auth.hasAnyAccess = hasAnyAccess(data);
locale = auth.user.locale || locale;
loadResourceBundle(function(data) {
resourceBundle = data;
var injector = angular.bootstrap(document, ["keycloak"]); var injector = angular.bootstrap(document, ["keycloak"]);
injector.get('$translate')('consoleTitle').then(function (consoleTitle) { injector.get('$translate')('consoleTitle').then(function (consoleTitle) {