diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java index 4aa4705b3a7..4435f51066d 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminClient.java @@ -12,6 +12,8 @@ public @interface InjectAdminClient { String ref() default ""; + String realmRef() default ""; + Mode mode() default Mode.BOOTSTRAP; String client() default ""; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java index b44c0e5fa00..7cc3d42977f 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java @@ -138,8 +138,8 @@ public class RealmConfigBuilder { return this; } - public RealmConfigBuilder internationalizationEnabled() { - rep.setInternationalizationEnabled(true); + public RealmConfigBuilder internationalizationEnabled(boolean enabled) { + rep.setInternationalizationEnabled(enabled); return this; } @@ -151,6 +151,11 @@ public class RealmConfigBuilder { return this; } + public RealmConfigBuilder defaultLocale(String locale) { + rep.setDefaultLocale(locale); + return this; + } + public RealmConfigBuilder smtp(String host, int port, String from) { Map config = new HashMap<>(); config.put("host", host); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfigBuilder.java index 48da805587e..fc5134568f9 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/UserConfigBuilder.java @@ -82,6 +82,11 @@ public class UserConfigBuilder { return this; } + public UserConfigBuilder attribute(String key, String value) { + rep.singleAttribute(key, value); + return this; + } + public UserRepresentation build() { return rep; } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java b/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java index f58830df7c9..6c1fed837a5 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java @@ -7,9 +7,13 @@ public class ApiUtil { public static String handleCreatedResponse(Response response) { try (response) { - String uuid = getCreatedId(response); - response.close(); - return uuid; + if (response.getStatus() != Response.Status.CONFLICT.getStatusCode()) { + String uuid = getCreatedId(response); + response.close(); + return uuid; + } else { + return null; + } } } diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClientSupplier.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClientSupplier.java index a0b4a92ab1a..2c50127a1a0 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClientSupplier.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthClientSupplier.java @@ -4,7 +4,6 @@ import org.apache.http.client.HttpClient; import org.apache.http.impl.client.CloseableHttpClient; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.testframework.injection.InstanceContext; -import org.keycloak.testframework.injection.LifeCycle; import org.keycloak.testframework.injection.RequestedInstance; import org.keycloak.testframework.injection.Supplier; import org.keycloak.testframework.injection.SupplierHelpers; @@ -52,12 +51,7 @@ public class OAuthClientSupplier implements Supplier a, RequestedInstance b) { - return true; - } - - @Override - public LifeCycle getDefaultLifecycle() { - return LifeCycle.GLOBAL; + return a.getAnnotation().ref().equals(b.getAnnotation().ref()); } @Override diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java index ba0d733614d..e830802a670 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/annotations/InjectOAuthClient.java @@ -1,5 +1,6 @@ package org.keycloak.testframework.oauth.annotations; +import org.keycloak.testframework.injection.LifeCycle; import org.keycloak.testframework.oauth.DefaultOAuthClientConfiguration; import org.keycloak.testframework.realm.ClientConfig; @@ -14,6 +15,10 @@ public @interface InjectOAuthClient { Class config() default DefaultOAuthClientConfiguration.class; + LifeCycle lifecycle() default LifeCycle.CLASS; + + String ref() default ""; + String realmRef() default ""; boolean kcAdmin() default false; diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/AdminConsoleWhoAmILocaleTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/AdminConsoleWhoAmILocaleTest.java new file mode 100644 index 00000000000..3078bee88b3 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/admin/AdminConsoleWhoAmILocaleTest.java @@ -0,0 +1,354 @@ +package org.keycloak.tests.admin; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.ws.rs.core.Response; +import org.apache.http.Header; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicHeader; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.Constants; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.testframework.annotations.InjectClient; +import org.keycloak.testframework.annotations.InjectHttpClient; +import org.keycloak.testframework.annotations.InjectKeycloakUrls; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.InjectUser; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.oauth.OAuthClient; +import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; +import org.keycloak.testframework.realm.ClientConfig; +import org.keycloak.testframework.realm.ClientConfigBuilder; +import org.keycloak.testframework.realm.ManagedClient; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.ManagedUser; +import org.keycloak.testframework.realm.RealmConfig; +import org.keycloak.testframework.realm.RealmConfigBuilder; +import org.keycloak.testframework.realm.UserConfig; +import org.keycloak.testframework.realm.UserConfigBuilder; +import org.keycloak.testframework.server.KeycloakUrls; +import org.keycloak.testsuite.util.oauth.AccessTokenResponse; + +import java.io.IOException; +import java.util.List; + +import static org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID; + +@KeycloakIntegrationTest +public class AdminConsoleWhoAmILocaleTest { + + @InjectRealm(ref = "master", attachTo = "master") + ManagedRealm masterRealm; + + @InjectRealm(ref = "realm-i18n-off", config = LocaleOffRealmConfig.class) + ManagedRealm managedRealmOff; + + @InjectRealm(ref = "realm-i18n-on", config = LocaleOnRealmConfig.class) + ManagedRealm managedRealmOn; + + @InjectUser(realmRef = "master", config = MasterAdminUserConfig.class) + ManagedUser masterAdmin; + + @InjectClient(realmRef = "master", config = MasterAdminClientConfig.class) + ManagedClient masterClient; + + @InjectOAuthClient(ref = "master", realmRef = "master") + OAuthClient oAuthClientMaster; + + @InjectOAuthClient(ref = "locale-off", realmRef = "realm-i18n-off") + OAuthClient oAuthClientLocaleOff; + + @InjectOAuthClient(ref = "locale-on", realmRef = "realm-i18n-on") + OAuthClient oAuthClientLocaleOn; + + @InjectKeycloakUrls + KeycloakUrls keycloakUrls; + + @InjectHttpClient + CloseableHttpClient client; + + 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 USER_NO_ACCESS = "user-no-access"; + private static final String PASSWORD = "password"; + private static final String ADMIN_CLI_NOT_ALLOWED = "admin-cli-not-allowed"; + private static final String SECRET = "secret"; + 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 = "zh-CN"; + + @Test + public void testLocaleRealmI18nDisabledUserWithoutLocale() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOff, ADMIN_CLI_CLIENT_ID, SECRET, USER_WITHOUT_LOCALE, PASSWORD); + + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(managedRealmOff), response); + + Assertions.assertEquals(REALM_I18N_OFF, whoAmI.get("realm").asText()); + Assertions.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_OFF, whoAmI); + + oAuthClientLocaleOff.doLogout(response.getRefreshToken()); + } + + @Test + public void testLocaleRealmI18nDisabledUserWithLocale() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOff, ADMIN_CLI_CLIENT_ID, SECRET, USER_WITH_LOCALE, PASSWORD); + + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(managedRealmOff), response); + + Assertions.assertEquals(REALM_I18N_OFF, whoAmI.get("realm").asText()); + Assertions.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_OFF, whoAmI); + + oAuthClientLocaleOff.doLogout(response.getRefreshToken()); + } + + @Test + public void testLocaleRealmI18nEnabledUserWithoutLocale() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOn, ADMIN_CLI_CLIENT_ID, SECRET, USER_WITHOUT_LOCALE, PASSWORD); + + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(managedRealmOn), response); + + Assertions.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + Assertions.assertEquals(REALM_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_ON, whoAmI); + + oAuthClientLocaleOn.doLogout(response.getRefreshToken()); + } + + @Test + public void testLocaleRealmI18nEnabledUserWithLocale() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOn, ADMIN_CLI_CLIENT_ID, SECRET, USER_WITH_LOCALE, PASSWORD); + + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(managedRealmOn), response); + + Assertions.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + Assertions.assertEquals(USER_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_ON, whoAmI); + + oAuthClientLocaleOn.doLogout(response.getRefreshToken()); + } + + @Test + public void testLocaleRealmI18nEnabledAcceptLanguageHeader() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOn, ADMIN_CLI_CLIENT_ID, SECRET, USER_WITHOUT_LOCALE, PASSWORD); + + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(managedRealmOn), response, new BasicHeader("Accept-Language", EXTRA_LOCALE)); + + Assertions.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + Assertions.assertEquals(EXTRA_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_ON, whoAmI); + + oAuthClientLocaleOn.doLogout(response.getRefreshToken()); + } + + @Test + public void testLocaleRealmI18nEnabledKeycloakLocaleCookie() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOn, ADMIN_CLI_CLIENT_ID, SECRET, USER_WITHOUT_LOCALE, PASSWORD); + + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(managedRealmOn), response, new BasicHeader("Cookie", "KEYCLOAK_LOCALE=" + EXTRA_LOCALE)); + + Assertions.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + Assertions.assertEquals(EXTRA_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_ON, whoAmI); + + oAuthClientLocaleOn.doLogout(response.getRefreshToken()); + } + + @Test + public void testMasterRealm() throws Exception { + updateMasterAdminRole(); + AccessTokenResponse response = accessToken(oAuthClientMaster, masterClient.getClientId(), SECRET, masterAdmin.getUsername(), PASSWORD); + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(masterRealm), response); + Assertions.assertEquals(masterRealm.getName(), whoAmI.get("realm").asText()); + Assertions.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(masterRealm.getName(), whoAmI); + oAuthClientMaster.doLogout(response.getRefreshToken()); + } + + @Test + public void testMasterRealmCurrentRealm() throws Exception { + updateMasterAdminRole(); + AccessTokenResponse response = accessToken(oAuthClientMaster, masterClient.getClientId(), SECRET, masterAdmin.getUsername(), PASSWORD); + JsonNode whoAmI = getHttpJsonResponse(whoAmiUrl(masterRealm, REALM_I18N_ON), response); + Assertions.assertEquals(masterRealm.getName(), whoAmI.get("realm").asText()); + Assertions.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_ON, whoAmI); + oAuthClientMaster.doLogout(response.getRefreshToken()); + } + + @Test + public void testLocaleRealmNoToken() throws Exception { + HttpGet httpGet = new HttpGet(whoAmiUrl(managedRealmOn)); + httpGet.addHeader("Accept", "application/json"); + + CloseableHttpResponse httpGetResponse = client.execute(httpGet); + Assertions.assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), httpGetResponse.getStatusLine().getStatusCode()); + httpGetResponse.close(); + } + + @Test + public void testLocaleRealmUserNoAccess() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOn, ADMIN_CLI_CLIENT_ID, SECRET, USER_NO_ACCESS, PASSWORD); + + HttpGet httpGet = new HttpGet(whoAmiUrl(managedRealmOn)); + httpGet.addHeader("Accept", "application/json"); + httpGet.addHeader("Authorization", "Bearer " + response.getAccessToken()); + + CloseableHttpResponse httpGetResponse = client.execute(httpGet); + + Assertions.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), httpGetResponse.getStatusLine().getStatusCode()); + httpGetResponse.close(); + oAuthClientLocaleOn.doLogout(response.getRefreshToken()); + } + + @Test + public void testLocaleRealmTokenAdminNotAllowed() throws Exception { + AccessTokenResponse response = accessToken(oAuthClientLocaleOn, ADMIN_CLI_NOT_ALLOWED, SECRET, USER_WITH_LOCALE, PASSWORD); + + Assertions.assertNotNull(response.getAccessToken()); + + HttpGet httpGet = new HttpGet(whoAmiUrl(managedRealmOn)); + httpGet.addHeader("Accept", "application/json"); + httpGet.addHeader("Authorization", "Bearer " + response.getAccessToken()); + CloseableHttpResponse httpGetResponse = client.execute(httpGet); + + Assertions.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), httpGetResponse.getStatusLine().getStatusCode()); + httpGetResponse.close(); + } + + private void updateMasterAdminRole() { + RoleRepresentation roleRep = masterRealm.admin().roles().get("admin").toRepresentation(); + masterRealm.admin().users().get(masterAdmin.getId()).roles().realmLevel().add(List.of(roleRep)); + } + + private AccessTokenResponse accessToken(OAuthClient oAuth, String clientId, String clientSecret, String username, String password) { + return oAuth.client(clientId, clientSecret).doPasswordGrantRequest(username, password); + } + + private JsonNode getHttpJsonResponse(String url, AccessTokenResponse response, Header... headers) throws IOException{ + HttpGet httpGet = new HttpGet(url); + httpGet.addHeader("Accept", "application/json"); + httpGet.addHeader("Authorization", "Bearer " + response.getAccessToken()); + + for (Header header : headers) { + httpGet.addHeader(header.getName(), header.getValue()); + } + + return new ObjectMapper().readTree(client.execute(httpGet).getEntity().getContent()); + } + + private String whoAmiUrl(ManagedRealm realm) { + return whoAmiUrl(realm, null); + } + + private String whoAmiUrl(ManagedRealm realm, String currentRealm) { + StringBuilder sb = new StringBuilder() + .append(keycloakUrls.getBaseUrl()) + .append("/admin/") + .append(realm.getName()) + .append("/console/whoami"); + if (currentRealm != null) { + sb.append("?currentRealm=").append(currentRealm); + } + return sb.toString(); + } + + private void checkRealmAccess(String realm, JsonNode whoAmI) { + Assertions.assertNotNull(whoAmI.get("realm_access")); + Assertions.assertNotNull(whoAmI.get("realm_access").get(realm)); + Assertions.assertTrue(whoAmI.get("realm_access").get(realm).isArray()); + Assertions.assertTrue(whoAmI.get("realm_access").get(realm).size() > 0); + } + + private static class LocaleOffRealmConfig implements RealmConfig { + + @Override + public RealmConfigBuilder configure(RealmConfigBuilder realm) { + realm.internationalizationEnabled(false); + realm.addUser(USER_WITHOUT_LOCALE).password(PASSWORD) + .name("My", "Locale Off") + .email("locale-off@email.org").emailVerified() + .clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN); + realm.addUser(USER_WITH_LOCALE).password(PASSWORD) + .name("My", "Locale On") + .email("locale-on@email.org").emailVerified() + .attribute("locale", USER_LOCALE) + .clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN); + realm.addClient(ADMIN_CLI_CLIENT_ID).name(ADMIN_CLI_CLIENT_ID).secret(SECRET) + .attribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, "true") + .directAccessGrants(); + + return realm; + } + } + + private static class LocaleOnRealmConfig implements RealmConfig { + + @Override + public RealmConfigBuilder configure(RealmConfigBuilder realm) { + realm.internationalizationEnabled(true); + realm.supportedLocales(REALM_LOCALE, USER_LOCALE, EXTRA_LOCALE); + realm.defaultLocale(REALM_LOCALE); + + realm.addUser(USER_WITHOUT_LOCALE).password(PASSWORD) + .name("My", "Locale Off") + .email("locale-off@email.org").emailVerified() + .clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN); + realm.addUser(USER_WITH_LOCALE).password(PASSWORD) + .email("locale-on@email.org").emailVerified().name("My", "Locale On") + .attribute("locale", USER_LOCALE) + .clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN); + realm.addUser(USER_NO_ACCESS).password(PASSWORD) + .name("No", "Access") + .email("no-access@email.org").emailVerified() + .attribute("locale", USER_LOCALE); + realm.addClient(ADMIN_CLI_CLIENT_ID).name(ADMIN_CLI_CLIENT_ID).secret(SECRET) + .attribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, "true") + .directAccessGrants(); + realm.addClient(ADMIN_CLI_NOT_ALLOWED).name(ADMIN_CLI_NOT_ALLOWED).secret(SECRET) + .attribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, null) + .directAccessGrants(); + + return realm; + } + } + + private static class MasterAdminUserConfig implements UserConfig { + + @Override + public UserConfigBuilder configure(UserConfigBuilder user) { + user.username("master-admin"); + user.password(PASSWORD); + user.name("My", "Admin"); + user.roles("admin"); + user.email("master-admin@email.org"); + user.emailVerified(); + user.attribute("locale", DEFAULT_LOCALE); + + + return user; + } + } + + private static class MasterAdminClientConfig implements ClientConfig { + + @Override + public ClientConfigBuilder configure(ClientConfigBuilder client) { + client.clientId("master-admin-cli"); + client.name("master-admin-cli"); + client.secret(SECRET); + client.attribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, "true"); + client.directAccessGrants(); + + return client; + } + } +} diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/DeclarativeUserTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/DeclarativeUserTest.java index 90b2dcca0a6..26dc3344790 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/DeclarativeUserTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/DeclarativeUserTest.java @@ -233,7 +233,7 @@ public class DeclarativeUserTest { @Override public RealmConfigBuilder configure(RealmConfigBuilder realm) { - realm.internationalizationEnabled() + realm.internationalizationEnabled(true) .supportedLocales("en", "de"); return realm; } 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 deleted file mode 100644 index dd79ef2dc84..00000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java +++ /dev/null @@ -1,304 +0,0 @@ -package org.keycloak.testsuite.admin; - -import com.fasterxml.jackson.databind.JsonNode; -import jakarta.ws.rs.core.Response; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.keycloak.admin.client.Keycloak; -import org.keycloak.broker.provider.util.SimpleHttp; -import org.keycloak.models.AdminRoles; -import org.keycloak.models.Constants; -import org.keycloak.representations.AccessTokenResponse; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.AbstractKeycloakTest; -import org.keycloak.testsuite.auth.page.AuthRealm; -import org.keycloak.testsuite.broker.util.SimpleHttpDefault; -import org.keycloak.testsuite.updaters.ClientAttributeUpdater; -import org.keycloak.testsuite.util.AdminClientUtil; -import org.keycloak.testsuite.util.RealmBuilder; -import org.keycloak.testsuite.util.UserBuilder; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; - -import static org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID; - -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 USER_NO_ACCESS = "user-no-access"; - 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 = "zh-CN"; - - private CloseableHttpClient client; - - @Before - public void createHttpClient() throws Exception { - client = HttpClientBuilder.create().build(); - - getCleanup().addCleanup(ClientAttributeUpdater.forClient(adminClient, "master", ADMIN_CLI_CLIENT_ID).setAttribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, "true").update()); - getCleanup().addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_I18N_OFF, ADMIN_CLI_CLIENT_ID).setAttribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, "true").update()); - getCleanup().addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_I18N_ON, ADMIN_CLI_CLIENT_ID).setAttribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, "true").update()); - } - - @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(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN)); - realm.user(UserBuilder.create() - .username(USER_WITH_LOCALE) - .password(PASSWORD) - .addAttribute("locale", USER_LOCALE) - .role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN)); - testRealms.add(realm.build()); - - realm = RealmBuilder.create() - .name(REALM_I18N_ON) - .internationalizationEnabled(true) - .supportedLocales(new HashSet<>(Arrays.asList(REALM_LOCALE, USER_LOCALE, EXTRA_LOCALE))) - .defaultLocale(REALM_LOCALE); - realm.user(UserBuilder.create() - .username(USER_WITHOUT_LOCALE) - .password(PASSWORD) - .role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN)); - realm.user(UserBuilder.create() - .username(USER_WITH_LOCALE) - .password(PASSWORD) - .addAttribute("locale", USER_LOCALE) - .role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN)); - realm.user(UserBuilder.create() - .username(USER_NO_ACCESS) - .password(PASSWORD) - .addAttribute("locale", USER_LOCALE)); - testRealms.add(realm.build()); - } - - private org.keycloak.testsuite.util.oauth.AccessTokenResponse accessToken(String realmName, String username, String password) throws Exception { - return oauth.realm(realmName).client(ADMIN_CLI_CLIENT_ID).doPasswordGrantRequest(username, password); - } - - private String whoAmiUrl(String realmName) { - return whoAmiUrl(realmName, null); - } - - private String whoAmiUrl(String realmName, String currentRealm) { - StringBuilder sb = new StringBuilder() - .append(suiteContext.getAuthServerInfo().getContextRoot().toString()) - .append("/auth/admin/") - .append(realmName) - .append("/console/whoami"); - if (currentRealm != null) { - sb.append("?currentRealm=").append(currentRealm); - } - return sb.toString(); - } - - private void checkRealmAccess(String realm, JsonNode whoAmI) { - Assert.assertNotNull(whoAmI.get("realm_access")); - Assert.assertNotNull(whoAmI.get("realm_access").get(realm)); - Assert.assertTrue(whoAmI.get("realm_access").get(realm).isArray()); - Assert.assertTrue(whoAmI.get("realm_access").get(realm).size() > 0); - } - - @Test - public void testLocaleRealmI18nDisabledUserWithoutLocale() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = oauth.realm(REALM_I18N_OFF).client(ADMIN_CLI_CLIENT_ID).doPasswordGrantRequest(USER_WITHOUT_LOCALE, PASSWORD); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_OFF), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .asJson(); - Assert.assertEquals(REALM_I18N_OFF, whoAmI.get("realm").asText()); - Assert.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_OFF, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testLocaleRealmI18nDisabledUserWithLocale() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(REALM_I18N_OFF, USER_WITH_LOCALE, PASSWORD); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_OFF), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .asJson(); - Assert.assertEquals(REALM_I18N_OFF, whoAmI.get("realm").asText()); - Assert.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_OFF, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testLocaleRealmI18nEnabledUserWithoutLocale() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(REALM_I18N_ON, USER_WITHOUT_LOCALE, PASSWORD); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .asJson(); - Assert.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); - Assert.assertEquals(REALM_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_ON, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testLocaleRealmI18nEnabledUserWithLocale() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(REALM_I18N_ON, USER_WITH_LOCALE, PASSWORD); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .asJson(); - Assert.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); - Assert.assertEquals(USER_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_ON, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testLocaleRealmI18nEnabledAcceptLanguageHeader() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(REALM_I18N_ON, USER_WITHOUT_LOCALE, PASSWORD); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .header("Accept-Language", EXTRA_LOCALE) - .asJson(); - Assert.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); - Assert.assertEquals(EXTRA_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_ON, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testLocaleRealmI18nEnabledKeycloakLocaleCookie() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(REALM_I18N_ON, USER_WITHOUT_LOCALE, PASSWORD); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .header("Cookie", "KEYCLOAK_LOCALE=" + EXTRA_LOCALE) - .asJson(); - Assert.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); - Assert.assertEquals(EXTRA_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_ON, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testMasterRealm() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(AuthRealm.MASTER, AuthRealm.ADMIN, AuthRealm.ADMIN); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(AuthRealm.MASTER), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .asJson(); - Assert.assertEquals(AuthRealm.MASTER, whoAmI.get("realm").asText()); - Assert.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(AuthRealm.MASTER, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testMasterRealmCurrentRealm() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(AuthRealm.MASTER, AuthRealm.ADMIN, AuthRealm.ADMIN); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(AuthRealm.MASTER, REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .asJson(); - Assert.assertEquals(AuthRealm.MASTER, whoAmI.get("realm").asText()); - Assert.assertEquals(DEFAULT_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_ON, whoAmI); - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testLocaleRealmNoToken() throws Exception { - try (SimpleHttp.Response response = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .asResponse()) { - Assert.assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatus()); - } - } - - @Test - public void testLocaleRealmUserNoAccess() throws Exception { - org.keycloak.testsuite.util.oauth.AccessTokenResponse response = accessToken(REALM_I18N_ON, USER_NO_ACCESS, PASSWORD); - try (SimpleHttp.Response res = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(response.getAccessToken()) - .asResponse()) { - Assert.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), res.getStatus()); - } - oauth.doLogout(response.getRefreshToken()); - } - - @Test - public void testLocaleRealmTokenForOtherClient() throws Exception { - try (var c = ClientAttributeUpdater.forClient(adminClient, REALM_I18N_ON, ADMIN_CLI_CLIENT_ID).setAttribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, null).update(); - Keycloak adminCliClient = AdminClientUtil.createAdminClient(true, REALM_I18N_ON, - USER_WITH_LOCALE, PASSWORD, Constants.ADMIN_CLI_CLIENT_ID, null)) { - AccessTokenResponse accessToken = adminCliClient.tokenManager().getAccessToken(); - Assert.assertNotNull(accessToken); - String token = accessToken.getToken(); - try (SimpleHttp.Response response = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(token) - .asResponse()) { - Assert.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus()); - } - } - } - - @Test - public void testLocaleRealmTokenForOtherClientButAllowed() throws Exception { - try (ClientAttributeUpdater updater = ClientAttributeUpdater.forClient(adminClient, REALM_I18N_ON, Constants.ADMIN_CLI_CLIENT_ID) - .setAttribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, Boolean.TRUE.toString()) - .update(); - Keycloak adminCliClient = AdminClientUtil.createAdminClient(true, REALM_I18N_ON, - USER_WITH_LOCALE, PASSWORD, Constants.ADMIN_CLI_CLIENT_ID, null)) { - AccessTokenResponse accessToken = adminCliClient.tokenManager().getAccessToken(); - Assert.assertNotNull(accessToken); - String token = accessToken.getToken(); - JsonNode whoAmI = SimpleHttpDefault - .doGet(whoAmiUrl(REALM_I18N_ON), client) - .header("Accept", "application/json") - .auth(token) - .asJson(); - Assert.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); - Assert.assertEquals(USER_LOCALE, whoAmI.get("locale").asText()); - checkRealmAccess(REALM_I18N_ON, whoAmI); - } - } -}