diff --git a/adapters/oidc/js/src/main/resources/keycloak.d.ts b/adapters/oidc/js/src/main/resources/keycloak.d.ts index fe1ac482d12..2a1c46dde66 100644 --- a/adapters/oidc/js/src/main/resources/keycloak.d.ts +++ b/adapters/oidc/js/src/main/resources/keycloak.d.ts @@ -149,10 +149,18 @@ declare namespace Keycloak { */ idpHint?: string; - /** - * Specifies the desired locale for the UI. + /** + * Sets the 'ui_locales' query param in compliance with section 3.1.2.1 + * of the OIDC 1.0 specification. */ 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; } type KeycloakPromiseCallback = (result: T) => void; diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index c4ddcc1cfef..271cd500f10 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -282,6 +282,10 @@ if (options && options.locale) { url += '&ui_locales=' + encodeURIComponent(options.locale); } + + if (options && options.kcLocale) { + url += '&kc_locale=' + encodeURIComponent(options.kcLocale); + } return url; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/AbstractJavascriptTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/AbstractJavascriptTest.java index e42e4b0ff43..2ec7beaed70 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/AbstractJavascriptTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/AbstractJavascriptTest.java @@ -26,6 +26,9 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.collection.IsMapContaining.hasEntry; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith; import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; +import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad; +import org.openqa.selenium.Cookie; +import org.openqa.selenium.WebDriver.Options; /** * @author mhajas @@ -142,6 +145,18 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest { public void assertOutputWebElementContains(String value, WebDriver driver1, Object output, WebElement events) { waitUntilElement((WebElement) output).text().contains(value); } + + public void assertLocaleCookie(String locale, WebDriver driver1, Object output, WebElement events) { + waitForPageToLoad(); + Options ops = driver1.manage(); + Cookie cookie = ops.getCookieNamed("KEYCLOAK_LOCALE"); + Assert.assertNotNull(cookie); + Assert.assertEquals(locale, cookie.getValue()); + } + + public JavascriptStateValidator assertLocaleIsSet(String locale) { + return buildFunction(this::assertLocaleCookie, locale); + } public void assertOutputContains(String value, WebDriver driver1, Object output, WebElement events) { if (output instanceof WebElement) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptAdapterTest.java index 1232e45c235..e4343947afc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptAdapterTest.java @@ -33,6 +33,8 @@ import java.util.List; import java.util.Map; import static java.lang.Math.toIntExact; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.hamcrest.CoreMatchers.both; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.greaterThan; @@ -42,6 +44,7 @@ import static org.hamcrest.collection.IsMapContaining.hasEntry; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import org.keycloak.testsuite.ProfileAssume; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith; import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; @@ -113,6 +116,25 @@ 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) + .login("{kcLocale: 'de'}", assertLocaleIsSet("de")) + .init(defaultArguments(), this::assertSuccessfullyLoggedIn) + .login("{kcLocale: 'en'}", assertLocaleIsSet("en")); + } + @Test public void testRefreshToken() { testExecutor.init(defaultArguments(), this::assertInitNotAuth) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptTestExecutor.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptTestExecutor.java index 8db7dee03f5..d856f36c856 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptTestExecutor.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptTestExecutor.java @@ -36,11 +36,19 @@ public class JavascriptTestExecutor { } public JavascriptTestExecutor login() { - return login(null); + return login(null, null); } - + public JavascriptTestExecutor login(JavascriptStateValidator validator) { - jsExecutor.executeScript("keycloak.login()"); + return login(null, validator); + } + + public JavascriptTestExecutor login(String options, JavascriptStateValidator validator) { + if (options == null) + jsExecutor.executeScript("keycloak.login()"); + else { + jsExecutor.executeScript("keycloak.login(" + options + ")"); + } if (validator != null) { validator.validate(jsDriver, output, events);