KEYCLOAK-13205 Apply locale resolution strategy to admin console.
This commit is contained in:
parent
219d2b9a7c
commit
08dca9e89f
4 changed files with 222 additions and 23 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue