From f6676ccd7634c9f52fb7563dbcc2bdf8175dd034 Mon Sep 17 00:00:00 2001 From: stianst Date: Thu, 27 Nov 2025 11:06:06 +0100 Subject: [PATCH] Migrate i18n package to new testsuite Closes #44520 Signed-off-by: stianst --- .../realm/RealmConfigBuilder.java | 5 + .../ui/page/AbstractLoginPage.java | 66 +++ .../testframework/ui/page/ErrorPage.java | 2 +- .../testframework/ui/page/InfoPage.java | 2 +- .../ui/page/LoginExpiredPage.java | 33 ++ .../testframework/ui/page/LoginPage.java | 31 +- .../ui/page/LoginPasswordResetPage.java | 39 ++ .../ui/page/LoginPasswordUpdatePage.java | 2 +- .../testframework/ui/page/OAuthGrantPage.java | 63 +++ .../ui/page/TermsAndConditionsPage.java | 31 ++ .../ui/webdriver/AssertionUtils.java | 4 +- .../ui/webdriver/CookieUtils.java | 16 + .../ui/webdriver/ManagedWebDriver.java | 5 + .../ui/webdriver/NavigateUtils.java | 28 + .../org/keycloak/tests}/i18n/EmailTest.java | 177 +++--- .../keycloak/tests/i18n/LoginPageTest.java | 507 ++++++++++++++++++ .../tests}/i18n/RealmLocalizationTest.java | 15 +- .../i18n/RealmWithInternationalization.java | 13 + .../keycloak/tests/suites/Base2TestSuite.java | 1 + .../tests/suites/LoginV1TestSuite.java | 7 +- .../forms/ClickThroughAuthenticator.java | 132 +++++ ...ycloak.authentication.AuthenticatorFactory | 1 + .../org/keycloak/testsuite/util/FlowUtil.java | 0 .../util/oauth/AbstractOAuthClient.java | 6 +- .../testsuite/util/oauth/LogoutRequest.java | 21 +- .../testsuite/i18n/AbstractI18NTest.java | 51 -- .../testsuite/i18n/LoginPageTest.java | 488 ----------------- .../tests/base/testsuites/base-suite | 1 - .../tests/base/testsuites/login-suite | 1 - 29 files changed, 1117 insertions(+), 631 deletions(-) create mode 100644 test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/AbstractLoginPage.java create mode 100644 test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginExpiredPage.java create mode 100644 test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordResetPage.java create mode 100644 test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/OAuthGrantPage.java create mode 100644 test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/TermsAndConditionsPage.java create mode 100644 test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/NavigateUtils.java rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/i18n/EmailTest.java (60%) mode change 100755 => 100644 create mode 100644 tests/base/src/test/java/org/keycloak/tests/i18n/LoginPageTest.java rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/i18n/RealmLocalizationTest.java (58%) create mode 100644 tests/base/src/test/java/org/keycloak/tests/i18n/RealmWithInternationalization.java create mode 100644 tests/custom-providers/src/main/java/org/keycloak/testsuite/forms/ClickThroughAuthenticator.java create mode 100755 tests/custom-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory rename {testsuite/integration-arquillian/tests/base/src/test => tests/utils-shared/src/main}/java/org/keycloak/testsuite/util/FlowUtil.java (100%) delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AbstractI18NTest.java delete mode 100755 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java 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 b115efa69f3..13d8205e847 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 @@ -240,6 +240,11 @@ public class RealmConfigBuilder { return this; } + public RealmConfigBuilder resetPasswordAllowed(boolean allowed) { + rep.setResetPasswordAllowed(allowed); + return this; + } + public RealmConfigBuilder clientPolicy(ClientPolicyRepresentation clienPolicyRep) { ClientPoliciesRepresentation clientPolicies = rep.getParsedClientPolicies(); if (clientPolicies == null) { diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/AbstractLoginPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/AbstractLoginPage.java new file mode 100644 index 00000000000..cc7a2ad5f37 --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/AbstractLoginPage.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testframework.ui.page; + +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; + +import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +public abstract class AbstractLoginPage extends AbstractPage { + + @FindBy(xpath = "//select[@aria-label='languages']/option[@selected]") + private WebElement selectedLanguage; + + @FindBy(xpath = "//select[@aria-label='languages']") + private WebElement languages; + + @FindBy(id = "kc-current-locale-link") + private WebElement languageTextBase; // base theme + + @FindBy(id = "kc-locale-dropdown") + private WebElement localeDropdownBase; // base theme + + public AbstractLoginPage(ManagedWebDriver driver) { + super(driver); + } + + public String getSelectedLanguage() { + try { + final String text = selectedLanguage.getText(); + return text == null ? text : text.trim(); + } catch (NoSuchElementException ex) { + // Fallback for Login v1 + return languageTextBase.getText(); + } + } + + public void selectLanguage(String language){ + try { + WebElement langLink = languages.findElement(By.xpath("//option[text()[contains(.,'" + language + "')]]")); + langLink.click(); + } catch (NoSuchElementException ex) { + // Fallback for Login v1 + WebElement langLink = localeDropdownBase.findElement(By.xpath("//a[text()[contains(.,'" + language + "')]]")); + langLink.click(); + } + } + +} diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/ErrorPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/ErrorPage.java index af584a8b185..1fc5f9ff460 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/ErrorPage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/ErrorPage.java @@ -24,7 +24,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class ErrorPage extends AbstractPage { +public class ErrorPage extends AbstractLoginPage { @FindBy(className = "instruction") private WebElement errorMessage; diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/InfoPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/InfoPage.java index d60e6187e49..2bde860c1f7 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/InfoPage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/InfoPage.java @@ -25,7 +25,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class InfoPage extends AbstractPage { +public class InfoPage extends AbstractLoginPage { @FindBy(className = "instruction") private WebElement infoMessage; diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginExpiredPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginExpiredPage.java new file mode 100644 index 00000000000..2c4fa4a3111 --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginExpiredPage.java @@ -0,0 +1,33 @@ +package org.keycloak.testframework.ui.page; + +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +public class LoginExpiredPage extends AbstractLoginPage { + + public LoginExpiredPage(ManagedWebDriver driver) { + super(driver); + } + + @FindBy(id = "loginRestartLink") + private WebElement loginRestartLink; + + @FindBy(id = "loginContinueLink") + private WebElement loginContinueLink; + + + public void clickLoginRestartLink() { + loginRestartLink.click(); + } + + public void clickLoginContinueLink() { + loginContinueLink.click(); + } + + @Override + public String getExpectedPageId() { + return "login-login-page-expired"; + } +} diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java index 76e26da948b..532c686a4a6 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPage.java @@ -3,10 +3,11 @@ package org.keycloak.testframework.ui.page; import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; -public class LoginPage extends AbstractPage { +public class LoginPage extends AbstractLoginPage { @FindBy(id = "username") private WebElement usernameInput; @@ -20,12 +21,23 @@ public class LoginPage extends AbstractPage { @FindBy(id = "rememberMe") private WebElement rememberMe; + @FindBy(linkText = "Forgot Password?") + private WebElement resetPasswordLink; + + @FindBy(className = "pf-m-success") + private WebElement loginSuccessMessage; + + @FindBy(id = "input-error-username") + private WebElement userNameInputError; + public LoginPage(ManagedWebDriver driver) { super(driver); } public void fillLogin(String username, String password) { + usernameInput.clear(); usernameInput.sendKeys(username); + passwordInput.clear(); passwordInput.sendKeys(password); } @@ -54,6 +66,14 @@ public class LoginPage extends AbstractPage { return rememberMe.isSelected(); } + public void resetPassword() { + resetPasswordLink.click(); + } + + public String getSuccessMessage() { + return loginSuccessMessage != null ? loginSuccessMessage.getText() : null; + } + @Override public String getExpectedPageId() { return "login-login"; @@ -66,4 +86,13 @@ public class LoginPage extends AbstractPage { public void clearUsernameInput() { usernameInput.clear(); } + + public String getUsernameInputError() { + try { + return userNameInputError.getText(); + } catch (NoSuchElementException e) { + return null; + } + } + } diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordResetPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordResetPage.java new file mode 100644 index 00000000000..380bf14db4e --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordResetPage.java @@ -0,0 +1,39 @@ +package org.keycloak.testframework.ui.page; + +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +public class LoginPasswordResetPage extends AbstractLoginPage { + + @FindBy(id = "username") + private WebElement usernameInput; + + @FindBy(css = "[type=\"submit\"]") + private WebElement submitButton; + + @FindBy(id = "kc-reset-password-form") + private WebElement formResetPassword; + + public LoginPasswordResetPage(ManagedWebDriver driver) { + super(driver); + } + + public void changePassword(String username) { + usernameInput.clear(); + usernameInput.sendKeys(username); + + submitButton.click(); + } + + public String getFormUrl() { + return formResetPassword.getAttribute("action"); + } + + @Override + public String getExpectedPageId() { + return "login-login-reset-password"; + } + +} diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordUpdatePage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordUpdatePage.java index 63d44c857db..73531c0edac 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordUpdatePage.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/LoginPasswordUpdatePage.java @@ -24,7 +24,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class LoginPasswordUpdatePage extends AbstractPage { +public class LoginPasswordUpdatePage extends AbstractLoginPage { @FindBy(id = "password-new") private WebElement newPasswordInput; diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/OAuthGrantPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/OAuthGrantPage.java new file mode 100644 index 00000000000..b2d17200c3e --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/OAuthGrantPage.java @@ -0,0 +1,63 @@ +package org.keycloak.testframework.ui.page; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; + +import org.junit.jupiter.api.Assertions; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +public class OAuthGrantPage extends AbstractLoginPage { + + // Locale-resolved built-in client scope consents + public static final String PROFILE_CONSENT_TEXT = "User profile"; + public static final String EMAIL_CONSENT_TEXT = "Email address"; + public static final String ADDRESS_CONSENT_TEXT = "Address"; + public static final String PHONE_CONSENT_TEXT = "Phone number"; + public static final String OFFLINE_ACCESS_CONSENT_TEXT = "Offline Access"; + public static final String ROLES_CONSENT_TEXT = "User roles"; + + @FindBy(css = "[name=\"accept\"]") + private WebElement acceptButton; + @FindBy(css = "[name=\"cancel\"]") + private WebElement cancelButton; + + public OAuthGrantPage(ManagedWebDriver driver) { + super(driver); + } + + public void accept(){ + acceptButton.click(); + } + + public void cancel(){ + cancelButton.click(); + } + + public List getDisplayedGrants() { + List table = new ArrayList<>(); + WebElement divKcOauth = driver.findElement(By.id("kc-oauth")); + for (WebElement li : divKcOauth.findElements(By.tagName("li"))) { + WebElement span = li.findElement(By.tagName("span")); + table.add(span.getText()); + } + return table; + } + + public void assertGrants(String... expectedGrants) { + List displayed = getDisplayedGrants(); + List expected = Arrays.asList(expectedGrants); + Assertions.assertTrue(displayed.containsAll(expected) && expected.containsAll(displayed), + "Not matched grants. Displayed grants: " + displayed + ", expected grants: " + expected); + } + + @Override + public String getExpectedPageId() { + return "login-login-oauth-grant"; + } + +} diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/TermsAndConditionsPage.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/TermsAndConditionsPage.java new file mode 100644 index 00000000000..e47fd4e5341 --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/page/TermsAndConditionsPage.java @@ -0,0 +1,31 @@ +package org.keycloak.testframework.ui.page; + +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +public class TermsAndConditionsPage extends AbstractLoginPage { + + @FindBy(id = "kc-accept") + private WebElement submitButton; + + @FindBy(id = "kc-decline") + private WebElement cancelButton; + + public TermsAndConditionsPage(ManagedWebDriver driver) { + super(driver); + } + + public void acceptTerms() { + submitButton.click(); + } + public void declineTerms() { + cancelButton.click(); + } + + @Override + public String getExpectedPageId() { + return "login-terms"; + } +} diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/AssertionUtils.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/AssertionUtils.java index 747431a9f9f..401027b2b9d 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/AssertionUtils.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/AssertionUtils.java @@ -1,6 +1,7 @@ package org.keycloak.testframework.ui.webdriver; import org.junit.jupiter.api.Assertions; +import org.openqa.selenium.By; public class AssertionUtils { @@ -11,7 +12,8 @@ public class AssertionUtils { } public void assertTitle(String title) { - Assertions.assertEquals(title, managed.page().getTitle()); + String kcPageTitle = managed.findElement(By.id("kc-page-title")).getText(); + Assertions.assertEquals(title, kcPageTitle); } } diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/CookieUtils.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/CookieUtils.java index 3e2260221de..8065e2ea019 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/CookieUtils.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/CookieUtils.java @@ -1,5 +1,9 @@ package org.keycloak.testframework.ui.webdriver; +import java.util.Set; + +import org.keycloak.cookie.CookieType; + import org.openqa.selenium.Cookie; public class CookieUtils { @@ -14,6 +18,18 @@ public class CookieUtils { managed.driver().manage().addCookie(cookie); } + public Cookie get(CookieType cookieType) { + return managed.driver().manage().getCookieNamed(cookieType.getName()); + } + + public Set getAll() { + return managed.driver().manage().getCookies(); + } + + public Cookie get(String name) { + return managed.driver().manage().getCookieNamed(name); + } + public void deleteAll() { managed.driver().manage().deleteAllCookies(); } diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/ManagedWebDriver.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/ManagedWebDriver.java index 2f9e07e0f92..d48f824a4c3 100644 --- a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/ManagedWebDriver.java +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/ManagedWebDriver.java @@ -16,6 +16,7 @@ public class ManagedWebDriver { private AssertionUtils assertionUtils = new AssertionUtils(this); private CookieUtils cookieUtils = new CookieUtils(this); private PageUtils pageUtils = new PageUtils(this); + private NavigateUtils navigateUtils = new NavigateUtils(this); private WaitUtils waitUtils = new WaitUtils(this); public ManagedWebDriver(WebDriver driver) { @@ -65,6 +66,10 @@ public class ManagedWebDriver { return pageUtils; } + public NavigateUtils navigate() { + return navigateUtils; + } + public WaitUtils waiting() { return waitUtils; } diff --git a/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/NavigateUtils.java b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/NavigateUtils.java new file mode 100644 index 00000000000..1aa69ac5c0d --- /dev/null +++ b/test-framework/ui/src/main/java/org/keycloak/testframework/ui/webdriver/NavigateUtils.java @@ -0,0 +1,28 @@ +package org.keycloak.testframework.ui.webdriver; + +import org.keycloak.testframework.ui.page.AbstractPage; + +public class NavigateUtils { + + private final ManagedWebDriver driver; + + NavigateUtils(ManagedWebDriver driver) { + this.driver = driver; + } + + public void refresh() { + driver.driver().navigate().refresh(); + } + + public void backWithRefresh(AbstractPage expectedPage) { + driver.driver().navigate().back(); + + String currentPageId = driver.page().getCurrentPageId(); + if (!expectedPage.getExpectedPageId().equals(currentPageId) && driver.getBrowserType().equals(BrowserType.CHROME)) { + driver.driver().navigate().refresh(); + } + + expectedPage.assertCurrent(); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java b/tests/base/src/test/java/org/keycloak/tests/i18n/EmailTest.java old mode 100755 new mode 100644 similarity index 60% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java rename to tests/base/src/test/java/org/keycloak/tests/i18n/EmailTest.java index 94e4010f06f..dab9e3ff078 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/i18n/EmailTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.testsuite.i18n; +package org.keycloak.tests.i18n; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -29,20 +29,34 @@ import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import jakarta.ws.rs.core.HttpHeaders; +import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.http.simple.SimpleHttp; import org.keycloak.http.simple.SimpleHttpResponse; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.testsuite.admin.ApiUtil; -import org.keycloak.testsuite.broker.util.SimpleHttpDefault; -import org.keycloak.testsuite.pages.InfoPage; -import org.keycloak.testsuite.pages.LoginPage; -import org.keycloak.testsuite.pages.LoginPasswordResetPage; -import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; -import org.keycloak.testsuite.util.DroneUtils; -import org.keycloak.testsuite.util.GreenMailRule; -import org.keycloak.testsuite.util.MailUtils; -import org.keycloak.testsuite.util.WaitUtils; +import org.keycloak.testframework.annotations.InjectAdminClient; +import org.keycloak.testframework.annotations.InjectKeycloakUrls; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.InjectSimpleHttp; +import org.keycloak.testframework.annotations.InjectUser; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.mail.MailServer; +import org.keycloak.testframework.mail.annotations.InjectMailServer; +import org.keycloak.testframework.oauth.OAuthClient; +import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.ManagedUser; +import org.keycloak.testframework.server.KeycloakUrls; +import org.keycloak.testframework.ui.annotations.InjectPage; +import org.keycloak.testframework.ui.annotations.InjectWebDriver; +import org.keycloak.testframework.ui.page.InfoPage; +import org.keycloak.testframework.ui.page.LoginPage; +import org.keycloak.testframework.ui.page.LoginPasswordResetPage; +import org.keycloak.testframework.ui.page.LoginPasswordUpdatePage; +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; +import org.keycloak.tests.common.BasicUserConfig; +import org.keycloak.tests.utils.MailUtils; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; @@ -51,10 +65,9 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; -import org.jboss.arquillian.graphene.page.Page; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.Cookie; @@ -63,33 +76,55 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Michael Gerber * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. */ -public class EmailTest extends AbstractI18NTest { +@KeycloakIntegrationTest +public class EmailTest { - @Rule - public GreenMailRule greenMail = new GreenMailRule(); + @InjectRealm(config = RealmWithInternationalization.class) + ManagedRealm realm; - @Page - protected LoginPage loginPage; + @InjectUser(config = BasicUserConfig.class) + ManagedUser user; - @Page - protected LoginPasswordResetPage resetPasswordPage; + @InjectMailServer + MailServer mailServer; - @Page - private InfoPage infoPage; + @InjectAdminClient + Keycloak adminClient; - @Page - private LoginPasswordUpdatePage loginPasswordUpdatePage; + @InjectPage + LoginPage loginPage; + + @InjectPage + LoginPasswordResetPage resetPasswordPage; + + @InjectPage + InfoPage infoPage; + + @InjectSimpleHttp + SimpleHttp simpleHttp; + + @InjectWebDriver + ManagedWebDriver driver; + + @InjectOAuthClient + OAuthClient oauth; + + @InjectPage + LoginPasswordUpdatePage loginPasswordUpdatePage; + + @InjectKeycloakUrls + KeycloakUrls keycloakUrls; private void changeUserLocale(String locale) { - UserRepresentation user = findUser("login-test"); - user.singleAttribute(UserModel.LOCALE, locale); - ApiUtil.findUserByUsernameId(testRealm(), "login-test").update(user); + UserRepresentation userRep = user.admin().toRepresentation(); + userRep.singleAttribute(UserModel.LOCALE, locale); + user.admin().update(userRep); } @Test @@ -112,16 +147,16 @@ public class EmailTest extends AbstractI18NTest { String subjectEn = "Subject EN"; String expectedBodyContentEn = "Body EN"; String bodyMessageEn = expectedBodyContentEn + placeholders; - testRealm().localization().saveRealmLocalizationText(Locale.ENGLISH.toLanguageTag(), subjectMessageKey, subjectEn); - testRealm().localization().saveRealmLocalizationText(Locale.ENGLISH.toLanguageTag(), bodyMessageKey, bodyMessageEn); - getCleanup().addLocalization(Locale.ENGLISH.toLanguageTag()); + realm.cleanup().add(r -> r.localization().deleteRealmLocalizationTexts(Locale.ENGLISH.toLanguageTag())); + realm.admin().localization().saveRealmLocalizationText(Locale.ENGLISH.toLanguageTag(), subjectMessageKey, subjectEn); + realm.admin().localization().saveRealmLocalizationText(Locale.ENGLISH.toLanguageTag(), bodyMessageKey, bodyMessageEn); String subjectDe = "Subject DE"; String expectedBodyContentDe = "Body DE"; String bodyMessageDe = expectedBodyContentDe + placeholders; - testRealm().localization().saveRealmLocalizationText(Locale.GERMAN.toLanguageTag(), subjectMessageKey, subjectDe); - testRealm().localization().saveRealmLocalizationText(Locale.GERMAN.toLanguageTag(), bodyMessageKey, bodyMessageDe); - getCleanup().addLocalization(Locale.GERMAN.toLanguageTag()); + realm.cleanup().add(r -> r.localization().deleteRealmLocalizationTexts(Locale.GERMAN.toLanguageTag())); + realm.admin().localization().saveRealmLocalizationText(Locale.GERMAN.toLanguageTag(), subjectMessageKey, subjectDe); + realm.admin().localization().saveRealmLocalizationText(Locale.GERMAN.toLanguageTag(), bodyMessageKey, bodyMessageDe); try { sendResetPasswordEmail(); @@ -150,12 +185,11 @@ public class EmailTest extends AbstractI18NTest { } @Test - public void updatePasswordFromAdmin() throws MessagingException, IOException { + public void updatePasswordFromAdmin() { changeUserLocale(null); try { - UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test"); - CloseableHttpClient httpClient = HttpClientBuilder.create().build(); - SimpleHttpResponse responseGet = SimpleHttpDefault.doPut(getAuthServerRoot() + "admin/realms/test/users/" + testUser.toRepresentation().getId() + "/execute-actions-email", httpClient) + UserResource testUser = user.admin(); + SimpleHttpResponse responseGet = simpleHttp.doPut(keycloakUrls.getAdmin() + "/realms/" + realm.getName() + "/users/" + testUser.toRepresentation().getId() + "/execute-actions-email") .auth(adminClient.tokenManager().getAccessTokenString()) .header("Accept-Language", "de") .json(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())) @@ -163,13 +197,13 @@ public class EmailTest extends AbstractI18NTest { assertEquals(responseGet.getStatus(), 204); - MimeMessage message = greenMail.getReceivedMessages()[0]; + MimeMessage message = mailServer.getReceivedMessages()[0]; String textBody = MailUtils.getBody(message).getText(); - Assert.assertThat(textBody, containsString("Your administrator has just requested")); + MatcherAssert.assertThat(textBody, containsString("Your administrator has just requested")); } catch (Exception e) { - Assert.fail(e.getMessage()); + Assertions.fail(e.getMessage()); } finally { // Revert changeUserLocale("en"); @@ -181,12 +215,12 @@ public class EmailTest extends AbstractI18NTest { changeUserLocale(null); try { - loginPage.open(); + oauth.openLoginForm(); loginPage.resetPassword(); try (CloseableHttpClient client = HttpClientBuilder.create().build()) { - Set cookies = oauth.getDriver().manage().getCookies(); + Set cookies = driver.cookies().getAll(); String cookieHeader = cookies.stream() .map(cookie -> cookie.getName() + "=" + cookie.getValue()) .collect(Collectors.joining("; ")); @@ -197,7 +231,7 @@ public class EmailTest extends AbstractI18NTest { post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "de"); List parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair("username", "login-test")); + parameters.add(new BasicNameValuePair("username", "basic-user")); UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8); post.setEntity(formEntity); @@ -213,16 +247,18 @@ public class EmailTest extends AbstractI18NTest { private void sendResetPasswordEmail() { - loginPage.open(); + oauth.openLoginForm(); loginPage.resetPassword(); - resetPasswordPage.changePassword("login-test"); + resetPasswordPage.assertCurrent(); + resetPasswordPage.changePassword("basic-user"); } private void verifyResetPassword(String expectedSubject, String expectedTextBodyContent, String expectedHtmlBodyContent, int expectedMsgCount) throws MessagingException, IOException { - assertEquals(expectedMsgCount, greenMail.getReceivedMessages().length); + mailServer.waitForIncomingEmail(expectedMsgCount); + assertEquals(expectedMsgCount, mailServer.getReceivedMessages().length); - MimeMessage message = greenMail.getReceivedMessages()[expectedMsgCount - 1]; + MimeMessage message = mailServer.getReceivedMessages()[expectedMsgCount - 1]; assertEquals(expectedSubject, message.getSubject()); @@ -242,44 +278,41 @@ public class EmailTest extends AbstractI18NTest { // Issue 13922 @Test public void changeLocaleOnInfoPage() throws InterruptedException, IOException { - UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test"); + UserResource testUser = user.admin(); testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())); - if (!greenMail.waitForIncomingEmail(1)) { - Assert.fail("Error when receiving email"); + if (!mailServer.waitForIncomingEmail(1)) { + Assertions.fail("Error when receiving email"); } - String link = MailUtils.getPasswordResetEmailLink(greenMail.getLastReceivedMessage()); + String link = MailUtils.getPasswordResetEmailLink(mailServer.getLastReceivedMessage()); // Make sure kc_locale added to link doesn't set locale link += "&kc_locale=de"; - DroneUtils.getCurrentDriver().navigate().to(link); - WaitUtils.waitForPageToLoad(); + driver.open(link); - Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent()); - assertThat(infoPage.getLanguageDropdownText(), is(equalTo("English"))); + infoPage.assertCurrent(); + assertThat(infoPage.getSelectedLanguage(), is(equalTo("English"))); - infoPage.openLanguage("Deutsch"); + infoPage.selectLanguage("Deutsch"); - assertThat(DroneUtils.getCurrentDriver().getPageSource(), containsString("Passwort aktualisieren")); + assertThat(driver.page().getPageSource(), containsString("Passwort aktualisieren")); - infoPage.clickToContinueDe(); + driver.findElement(By.linkText("» Klicken Sie hier um fortzufahren")).click(); - loginPasswordUpdatePage.openLanguage("English"); + loginPasswordUpdatePage.selectLanguage("English"); loginPasswordUpdatePage.changePassword("pass", "pass"); - WaitUtils.waitForPageToLoad(); - Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent()); assertThat(infoPage.getInfo(), containsString("Your account has been updated.")); // Change language again when on final info page with the message about updated account (authSession removed already at this point) - infoPage.openLanguage("Deutsch"); - assertEquals("Deutsch", infoPage.getLanguageDropdownText()); + infoPage.selectLanguage("Deutsch"); + assertEquals("Deutsch", infoPage.getSelectedLanguage()); assertThat(infoPage.getInfo(), containsString("Ihr Benutzerkonto wurde aktualisiert.")); - infoPage.openLanguage("English"); - assertEquals("English", infoPage.getLanguageDropdownText()); + infoPage.selectLanguage("English"); + assertEquals("English", infoPage.getSelectedLanguage()); assertThat(infoPage.getInfo(), containsString("Your account has been updated.")); } @@ -289,16 +322,16 @@ public class EmailTest extends AbstractI18NTest { public void resetPasswordOriginalUiLocalePreservedAfterForgetPassword() throws MessagingException, IOException { // Assert login page is in german oauth.loginForm().uiLocales("de").open(); - assertEquals("Deutsch", loginPage.getLanguageDropdownText()); + assertEquals("Deutsch", loginPage.getSelectedLanguage()); // Click "Forget password" driver.findElement(By.linkText("Passwort vergessen?")).click(); - assertEquals("Deutsch", resetPasswordPage.getLanguageDropdownText()); - resetPasswordPage.changePassword("login-test"); + assertEquals("Deutsch", resetPasswordPage.getSelectedLanguage()); + resetPasswordPage.changePassword("basic-user"); // Ensure that page is still in german (after authenticationSession was forked on server). The emailSentMessage should be also displayed in german loginPage.assertCurrent(); - assertEquals("Deutsch", loginPage.getLanguageDropdownText()); + assertEquals("Deutsch", loginPage.getSelectedLanguage()); assertEquals("Sie sollten in Kürze eine E-Mail mit weiteren Instruktionen erhalten.", loginPage.getSuccessMessage()); } diff --git a/tests/base/src/test/java/org/keycloak/tests/i18n/LoginPageTest.java b/tests/base/src/test/java/org/keycloak/tests/i18n/LoginPageTest.java new file mode 100644 index 00000000000..8685182b6f4 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/i18n/LoginPageTest.java @@ -0,0 +1,507 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.tests.i18n; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.common.util.KeycloakUriBuilder; +import org.keycloak.cookie.CookieType; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; +import org.keycloak.forms.login.freemarker.DetachedInfoStateChecker; +import org.keycloak.http.simple.SimpleHttp; +import org.keycloak.locale.LocaleSelectorProvider; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testframework.annotations.InjectEvents; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.InjectSimpleHttp; +import org.keycloak.testframework.annotations.InjectUser; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.events.EventAssertion; +import org.keycloak.testframework.events.Events; +import org.keycloak.testframework.injection.LifeCycle; +import org.keycloak.testframework.oauth.OAuthClient; +import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; +import org.keycloak.testframework.realm.ClientConfigBuilder; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.ManagedUser; +import org.keycloak.testframework.realm.RealmConfigBuilder; +import org.keycloak.testframework.remote.runonserver.InjectRunOnServer; +import org.keycloak.testframework.remote.runonserver.RunOnServerClient; +import org.keycloak.testframework.server.KeycloakServerConfig; +import org.keycloak.testframework.server.KeycloakServerConfigBuilder; +import org.keycloak.testframework.ui.annotations.InjectPage; +import org.keycloak.testframework.ui.annotations.InjectWebDriver; +import org.keycloak.testframework.ui.page.AbstractLoginPage; +import org.keycloak.testframework.ui.page.ErrorPage; +import org.keycloak.testframework.ui.page.LoginExpiredPage; +import org.keycloak.testframework.ui.page.LoginPage; +import org.keycloak.testframework.ui.page.LoginPasswordUpdatePage; +import org.keycloak.testframework.ui.page.OAuthGrantPage; +import org.keycloak.testframework.ui.page.TermsAndConditionsPage; +import org.keycloak.testframework.ui.webdriver.ManagedWebDriver; +import org.keycloak.tests.common.BasicUserConfig; +import org.keycloak.testsuite.forms.ClickThroughAuthenticator; +import org.keycloak.testsuite.util.FlowUtil; +import org.keycloak.testsuite.util.IdentityProviderBuilder; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Cookie; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Michael Gerber + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +@KeycloakIntegrationTest(config = LoginPageTest.ServerConfig.class) +public class LoginPageTest { + + @InjectRealm(config = LoginPageRealmConfig.class) + ManagedRealm realm; + + @InjectUser(config = BasicUserConfig.class, lifecycle = LifeCycle.METHOD) + ManagedUser user; + + @InjectOAuthClient + protected OAuthClient oauth; + + @InjectPage + protected LoginPage loginPage; + + @InjectPage + protected ErrorPage errorPage; + + @InjectPage + protected LoginPasswordUpdatePage changePasswordPage; + + @InjectPage + protected OAuthGrantPage grantPage; + + @InjectPage + protected LoginExpiredPage loginExpiredPage; + + @InjectPage + protected TermsAndConditionsPage termsPage; + + @InjectWebDriver + protected ManagedWebDriver driver; + + @InjectSimpleHttp + protected SimpleHttp simpleHttp; + + @InjectEvents + protected Events events; + + @InjectRunOnServer + protected RunOnServerClient runOnServer; + + @Test + public void languageDropdown() { + oauth.openLoginForm(); + assertEquals("English", loginPage.getSelectedLanguage()); + + switchLanguageToGermanAndBack("Username or email", "Benutzername oder E-Mail", loginPage); + } + + @Test + public void uiLocalesParameter() { + oauth.loginForm().open(); + assertEquals("English", loginPage.getSelectedLanguage()); + + //test if cookie works + oauth.loginForm().uiLocales("de").open(); + assertEquals("Deutsch", loginPage.getSelectedLanguage()); + + driver.cookies().deleteAll(); + oauth.loginForm().uiLocales("de").open(); + assertEquals("Deutsch", loginPage.getSelectedLanguage()); + + driver.cookies().deleteAll(); + oauth.loginForm().uiLocales("en de").open(); + assertEquals("English", loginPage.getSelectedLanguage()); + + driver.cookies().deleteAll(); + oauth.loginForm().uiLocales("fr de").open(); + assertEquals("Deutsch", loginPage.getSelectedLanguage()); + } + + @Test + public void htmlLangAttributeWithInternationalizationEnabled() { + oauth.openLoginForm(); + assertEquals("en", getHtmlLanguage()); + + oauth.loginForm().uiLocales("de").open(); + assertEquals("de", getHtmlLanguage()); + } + + @Test + public void htmlLangAttributeWithInternationalizationDisabled() { + realm.updateWithCleanup(r -> r.internationalizationEnabled(false)); + + oauth.openLoginForm(); + assertEquals("en", getHtmlLanguage()); + } + + @Test + public void acceptLanguageHeader() throws IOException { + String responseDe = simpleHttp.doGet(oauth.loginForm().build()).header("Accept-Language", "de").header("Accept", "text/html").asString(); + Assertions.assertTrue(responseDe.contains("Bei Ihrem Konto anmelden")); + + String responseEn = simpleHttp.doGet(oauth.loginForm().build()).header("Accept-Language", "en").header("Accept", "text/html").asString(); + Assertions.assertTrue(responseEn.contains("Sign in to your account")); + } + + @Test + public void testIdentityProviderCapitalization(){ + oauth.openLoginForm(); + // contains even name of sub-item - svg element in this case + assertThat(loginPage.findSocialButton("github").getText(), is("GitHub")); + assertThat(loginPage.findSocialButton("mysaml").getText(), is("mysaml")); + assertThat(loginPage.findSocialButton("myoidc").getText(), is("MyOIDC")); + } + + + // KEYCLOAK-3887 + @Test + public void languageChangeRequiredActions() { + UserResource user = this.user.admin(); + UserRepresentation userRep = user.toRepresentation(); + userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())); + user.update(userRep); + + oauth.openLoginForm(); + oauth.fillLoginForm("basic-user", "password"); + + changePasswordPage.assertCurrent(); + assertEquals("English", changePasswordPage.getSelectedLanguage()); + + // Switch language + switchLanguageToGermanAndBack("Update password", "Passwort aktualisieren", changePasswordPage); + + // Update password + changePasswordPage.changePassword("password", "password"); + + Assertions.assertNotNull(oauth.parseLoginResponse().getCode()); + } + + + // KEYCLOAK-3887 + @Test + public void languageChangeConsentScreen() { + // Set client, which requires consent + oauth.client("third-party", "password"); + + oauth.openLoginForm(); + oauth.fillLoginForm("basic-user", "password"); + + grantPage.assertCurrent(); + assertEquals("English", grantPage.getSelectedLanguage()); + + // Switch language + switchLanguageToGermanAndBack("Do you grant these access privileges?", "Wollen Sie diese Zugriffsrechte", changePasswordPage); + + // Confirm grant + grantPage.accept(); + + Assertions.assertNotNull(oauth.parseLoginResponse().getCode()); + + // Revert client + oauth.client("test-app", "password"); + } + + @Test + public void languageUserUpdates() throws InterruptedException { + oauth.openLoginForm(); + loginPage.selectLanguage("Deutsch"); + + assertEquals("Deutsch", loginPage.getSelectedLanguage()); + + Cookie localeCookie = driver.cookies().get(CookieType.LOCALE); + assertEquals("de", localeCookie.getValue()); + + loginPage.fillLogin("basic-user", "password"); + loginPage.submit(); + + Assertions.assertNotNull(oauth.parseLoginResponse().getCode()); + + EventAssertion.assertSuccess(events.poll()).type(EventType.UPDATE_PROFILE).userId(user.getId()).details(Details.PREF_UPDATED + UserModel.LOCALE, "de"); + EventAssertion.assertSuccess(events.poll()).type(EventType.LOGIN).userId(user.getId()); + + UserRepresentation userRep = user.admin().toRepresentation(); + assertEquals("de", userRep.getAttributes().get("locale").get(0)); + + String code = oauth.parseLoginResponse().getCode(); + String idTokenHint = oauth.doAccessTokenRequest(code).getIdToken(); + oauth.logoutRequest().idTokenHint(idTokenHint).send(); + oauth.openLoginForm(); + + assertEquals("Deutsch", loginPage.getSelectedLanguage()); + + userRep.getAttributes().remove("locale"); + user.admin().update(userRep); + + oauth.doLogin("basic-user", "password"); + + // User locale should not be updated due to previous cookie + userRep = user.admin().toRepresentation(); + Assertions.assertNull(userRep.getAttributes()); + + code = oauth.parseLoginResponse().getCode(); + idTokenHint = oauth.doAccessTokenRequest(code).getIdToken(); + oauth.logoutRequest().idTokenHint(idTokenHint).send(); + + oauth.openLoginForm(); + + // Cookie should be removed as last user to login didn't have a locale + localeCookie = driver.cookies().get(CookieType.LOCALE); + Assertions.assertNull(localeCookie); + } + + + // Test for user updating locale on the error page (when authenticationSession is not available) + @Test + public void languageUserUpdatesOnErrorPage() { + // Login page with invalid redirect_uri + String redirectUri = oauth.getRedirectUri(); + oauth.redirectUri("http://invalid"); + oauth.openLoginForm(); + + errorPage.assertCurrent(); + Assertions.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); + + // Change language should be OK + errorPage.selectLanguage("Deutsch"); + assertEquals("Deutsch", errorPage.getSelectedLanguage()); + Assertions.assertEquals("Ungültiger Parameter: redirect_uri", errorPage.getError()); + + // Refresh browser button should keep german language + driver.navigate().refresh(); + assertEquals("Deutsch", errorPage.getSelectedLanguage()); + Assertions.assertEquals("Ungültiger Parameter: redirect_uri", errorPage.getError()); + + // Changing to english should work + errorPage.selectLanguage("English"); + assertEquals("English", errorPage.getSelectedLanguage()); + Assertions.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); + + oauth.redirectUri(redirectUri); + } + + @Test + public void languageUserUpdatesOnErrorPageStateCheckerTest() throws URISyntaxException { + String redirectUri = oauth.getRedirectUri(); + + // Login page with invalid redirect_uri + oauth.redirectUri("http://invalid"); + oauth.openLoginForm(); + + errorPage.assertCurrent(); + Assertions.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); + + errorPage.selectLanguage("Deutsch"); + Assertions.assertEquals("Ungültiger Parameter: redirect_uri", errorPage.getError()); + + // Add incorrect state checker parameter. Error page should be shown about expired action. Language won't be changed + String currentUrl = driver.getCurrentUrl(); + String newUrl = KeycloakUriBuilder.fromUri(new URI(currentUrl)) + .replaceQueryParam(LocaleSelectorProvider.KC_LOCALE_PARAM, "en") + .replaceQueryParam(DetachedInfoStateChecker.STATE_CHECKER_PARAM, "invalid").buildAsString(); + driver.open(newUrl); + + Assertions.assertEquals("Die Aktion ist nicht mehr gültig.", errorPage.getError()); // Action expired. + + oauth.redirectUri(redirectUri); + } + + @Test + public void languageUserUpdatesOnExpiredPage() throws Exception { + UserRepresentation userRep = user.admin().toRepresentation(); + userRep.setRequiredActions(Collections.singletonList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())); + user.admin().update(userRep); + + oauth.openLoginForm(); + oauth.fillLoginForm("basic-user", "invalid-password"); + loginPage.assertCurrent(); + + assertThat(loginPage.getUsernameInputError(), is("Invalid username or password.")); + loginPage.fillLogin("basic-user", "password"); + loginPage.submit(); + + changePasswordPage.assertCurrent(); + + // navigate back to the login expired page and change language to german + driver.navigate().backWithRefresh(loginExpiredPage); + errorPage.selectLanguage("Deutsch"); + assertEquals("Deutsch", errorPage.getSelectedLanguage()); + driver.assertions().assertTitle("Diese Seite ist nicht mehr gültig."); + + // continue should show password update in german + loginExpiredPage.clickLoginContinueLink(); + assertEquals("Deutsch", changePasswordPage.getSelectedLanguage()); + driver.assertions().assertTitle("Passwort aktualisieren"); + } + + // GH issue 41292 + @Test + public void languageUserUpdatesOnCustomAuthenticatorPage() { + configureBrowserFlowWithClickThroughAuthenticator(); + + oauth.openLoginForm(); + termsPage.assertCurrent(); + + // Change language on the custom page + switchLanguageToGermanAndBack("Terms and Conditions", "Bedingungen und Konditionen", termsPage); + + // Revert dummy flow + RealmRepresentation rep = realm.admin().toRepresentation(); + rep.setBrowserFlow("browser"); + realm.admin().update(rep); + } + + @Test + public void realmLocalizationMessagesAreApplied() { + String realmLocalizationMessageKey = "loginAccountTitle"; + + String realmLocalizationMessageValueEn = "Localization Test EN"; + saveLocalizationText(Locale.ENGLISH.toLanguageTag(), realmLocalizationMessageKey, + realmLocalizationMessageValueEn); + String realmLocalizationMessageValueDe = "Localization Test DE"; + saveLocalizationText(Locale.GERMAN.toLanguageTag(), realmLocalizationMessageKey, + realmLocalizationMessageValueDe); + + oauth.openLoginForm(); + switchLanguageToGermanAndBack(realmLocalizationMessageValueEn, realmLocalizationMessageValueDe, loginPage); + } + + // KEYCLOAK-18590 + @Test + public void realmLocalizationMessagesAreNotCachedWithinTheTheme() { + final String locale = Locale.ENGLISH.toLanguageTag(); + + final String realmLocalizationMessageKey = "loginAccountTitle"; + final String realmLocalizationMessageValue = "Localization Test"; + + saveLocalizationText(locale, realmLocalizationMessageKey, realmLocalizationMessageValue); + oauth.openLoginForm(); + assertThat(driver.page().getPageSource(), containsString(realmLocalizationMessageValue)); + + realm.admin().localization().deleteRealmLocalizationText(locale, realmLocalizationMessageKey); + oauth.openLoginForm(); + assertThat(driver.page().getPageSource(), not(containsString(realmLocalizationMessageValue))); + } + + @Test + public void realmLocalizationMessagesUsedDuringErrorHandling() { + final String locale = Locale.ENGLISH.toLanguageTag(); + + final String realmLocalizationMessageKey = "errorTitle"; + final String realmLocalizationMessageValue = "We are really sorry..."; + + saveLocalizationText(locale, realmLocalizationMessageKey, realmLocalizationMessageValue); + String nonExistingUrl = oauth.loginForm().build().split("protocol")[0] + "incorrect-path"; + driver.open(nonExistingUrl); + + assertThat(driver.page().getPageSource(), containsString(realmLocalizationMessageValue)); + } + + private String getHtmlLanguage() { + return driver.findElement(By.xpath("//html")).getAttribute("lang"); + } + + private void saveLocalizationText(String locale, String key, String value) { + realm.admin().localization().saveRealmLocalizationText(locale, key, value); + realm.cleanup().add(r -> r.localization().deleteRealmLocalizationTexts(locale)); + } + + private void switchLanguageToGermanAndBack(String expectedEnglishMessage, String expectedGermanMessage, AbstractLoginPage page) { + // Switch language to Deutsch + page.selectLanguage("Deutsch"); + assertEquals("Deutsch", page.getSelectedLanguage()); + String pageSource = driver.page().getPageSource(); + assertThat(pageSource, not(containsString(expectedEnglishMessage))); + assertThat(pageSource, containsString(expectedGermanMessage)); + + // Revert language + page.selectLanguage("English"); + assertEquals("English", page.getSelectedLanguage()); + pageSource = driver.page().getPageSource(); + assertThat(pageSource, containsString(expectedEnglishMessage)); + assertThat(pageSource, not(containsString(expectedGermanMessage))); + } + + private void configureBrowserFlowWithClickThroughAuthenticator() { + final String newFlowAlias = "browser - rule"; + runOnServer.run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias)); + runOnServer.run(session -> FlowUtil.inCurrentRealm(session) + .selectFlow(newFlowAlias) + .inForms(forms -> forms + .clear() + // Update the browser forms with a UsernamePasswordForm + .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, ClickThroughAuthenticator.PROVIDER_ID) + ) + .defineAsBrowserFlow() + ); + } + + public static class LoginPageRealmConfig extends RealmWithInternationalization { + + @Override + public RealmConfigBuilder configure(RealmConfigBuilder realm) { + realm = super.configure(realm); + realm.identityProvider(IdentityProviderBuilder.create() + .providerId("github") + .alias("github") + .build()); + realm.identityProvider(IdentityProviderBuilder.create() + .providerId("saml") + .alias("mysaml") + .build()); + realm.identityProvider(IdentityProviderBuilder.create() + .providerId("oidc") + .alias("myoidc") + .displayName("MyOIDC") + .build()); + realm.client(ClientConfigBuilder.create().clientId("third-party").secret("password").consentRequired(true).redirectUris("*").build()); + return realm; + } + } + + protected static class ServerConfig implements KeycloakServerConfig { + + @Override + public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) { + return config.dependency("org.keycloak.tests", "keycloak-tests-custom-providers"); + } + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/RealmLocalizationTest.java b/tests/base/src/test/java/org/keycloak/tests/i18n/RealmLocalizationTest.java similarity index 58% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/RealmLocalizationTest.java rename to tests/base/src/test/java/org/keycloak/tests/i18n/RealmLocalizationTest.java index eb3e6c978b9..d69572d178f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/RealmLocalizationTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/i18n/RealmLocalizationTest.java @@ -1,15 +1,22 @@ -package org.keycloak.testsuite.i18n; +package org.keycloak.tests.i18n; import java.util.Map; import org.keycloak.admin.client.resource.RealmLocalizationResource; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.realm.ManagedRealm; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; -public class RealmLocalizationTest extends AbstractI18NTest { +@KeycloakIntegrationTest +public class RealmLocalizationTest { + + @InjectRealm(config = RealmWithInternationalization.class) + ManagedRealm managedRealm; /** * Make sure that realm localization texts support unicode (). @@ -19,7 +26,7 @@ public class RealmLocalizationTest extends AbstractI18NTest { String locale = "en"; String key = "Äǜṳǚǘǖ"; String text = "Öṏṏ"; - RealmLocalizationResource localizationResource = testRealm().localization(); + RealmLocalizationResource localizationResource = managedRealm.admin().localization(); localizationResource.saveRealmLocalizationText(locale, key, text); Map localizationTexts = localizationResource.getRealmLocalizationTexts(locale, false); diff --git a/tests/base/src/test/java/org/keycloak/tests/i18n/RealmWithInternationalization.java b/tests/base/src/test/java/org/keycloak/tests/i18n/RealmWithInternationalization.java new file mode 100644 index 00000000000..a63e59365d8 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/i18n/RealmWithInternationalization.java @@ -0,0 +1,13 @@ +package org.keycloak.tests.i18n; + +import org.keycloak.testframework.realm.RealmConfig; +import org.keycloak.testframework.realm.RealmConfigBuilder; + +public class RealmWithInternationalization implements RealmConfig { + + @Override + public RealmConfigBuilder configure(RealmConfigBuilder realm) { + return realm.resetPasswordAllowed(true).internationalizationEnabled(true).supportedLocales("de", "en"); + } + +} diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java index ec866fbccc0..108566888cf 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java @@ -10,6 +10,7 @@ import org.junit.platform.suite.api.Suite; "org.keycloak.tests.cors", "org.keycloak.tests.db", "org.keycloak.tests.forms", + "org.keycloak.tests.i18n", "org.keycloak.tests.infinispan", "org.keycloak.tests.keys", "org.keycloak.tests.oauth", diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/LoginV1TestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/LoginV1TestSuite.java index edf23f7452e..2c6aaf49ba7 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/LoginV1TestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/LoginV1TestSuite.java @@ -1,12 +1,13 @@ package org.keycloak.tests.suites; -import org.keycloak.tests.admin.AdminHeadersTest; +import org.keycloak.tests.i18n.LoginPageTest; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; @Suite -// TODO: Select relevant test classes or packages once they have been migrated -@SelectClasses({AdminHeadersTest.class}) +@SelectClasses({ + LoginPageTest.class +}) public class LoginV1TestSuite { } diff --git a/tests/custom-providers/src/main/java/org/keycloak/testsuite/forms/ClickThroughAuthenticator.java b/tests/custom-providers/src/main/java/org/keycloak/testsuite/forms/ClickThroughAuthenticator.java new file mode 100644 index 00000000000..c763c3ad43b --- /dev/null +++ b/tests/custom-providers/src/main/java/org/keycloak/testsuite/forms/ClickThroughAuthenticator.java @@ -0,0 +1,132 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.forms; + +import java.util.List; + +import jakarta.ws.rs.core.Response; + +import org.keycloak.Config; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ClickThroughAuthenticator implements Authenticator, AuthenticatorFactory { + public static final String PROVIDER_ID = "testsuite-dummy-click-through"; + + @Override + public void authenticate(AuthenticationFlowContext context) { + Response challenge = context.form().createForm("terms.ftl"); + context.challenge(challenge); + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + + } + + @Override + public void action(AuthenticationFlowContext context) { + if (context.getHttpRequest().getDecodedFormParameters().containsKey("cancel")) { + authenticate(context); + return; + } + + context.success(); + } + + @Override + public String getDisplayType() { + return "Testsuite Dummy Click Thru"; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public boolean isConfigurable() { + return false; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + + @Override + public String getHelpText() { + return "Testsuite Dummy authenticator. User needs to click through the page to continue."; + } + + @Override + public List getConfigProperties() { + return null; + } + + @Override + public void close() { + + } + + @Override + public Authenticator create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/tests/custom-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/tests/custom-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory new file mode 100755 index 00000000000..8218081a64c --- /dev/null +++ b/tests/custom-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -0,0 +1 @@ +org.keycloak.testsuite.forms.ClickThroughAuthenticator \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/FlowUtil.java similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java rename to tests/utils-shared/src/main/java/org/keycloak/testsuite/util/FlowUtil.java diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java index 8ef242e4641..057343e9a60 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java @@ -150,12 +150,12 @@ public abstract class AbstractOAuthClient { logoutForm().open(); } - public LogoutRequest logoutRequest(String refreshToken) { - return new LogoutRequest(refreshToken, this); + public LogoutRequest logoutRequest() { + return new LogoutRequest(this); } public LogoutResponse doLogout(String refreshToken) { - return logoutRequest(refreshToken).send(); + return logoutRequest().refreshToken(refreshToken).send(); } public BackchannelLogoutRequest backchannelLogoutRequest(String logoutToken) { diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LogoutRequest.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LogoutRequest.java index 17fb0af90f1..f48e75bac81 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LogoutRequest.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LogoutRequest.java @@ -8,11 +8,21 @@ import org.apache.http.client.methods.CloseableHttpResponse; public class LogoutRequest extends AbstractHttpPostRequest { - private final String refreshToken; + private String refreshToken; + private String idTokenHint; - LogoutRequest(String refreshToken, AbstractOAuthClient client) { + LogoutRequest(AbstractOAuthClient client) { super(client); + } + + public LogoutRequest refreshToken(String refreshToken) { this.refreshToken = refreshToken; + return this; + } + + public LogoutRequest idTokenHint(String idTokenHint) { + this.idTokenHint = idTokenHint; + return this; } @Override @@ -21,7 +31,12 @@ public class LogoutRequest extends AbstractHttpPostRequestMichael Gerber - * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. - */ -public class LoginPageTest extends AbstractI18NTest { - - @Page - protected AppPage appPage; - - @Page - protected LoginPage loginPage; - - @Page - protected ErrorPage errorPage; - - @Page - protected LoginPasswordUpdatePage changePasswordPage; - - @Page - protected OAuthGrantPage grantPage; - - @Page - protected LoginExpiredPage loginExpiredPage; - - @Page - protected TermsAndConditionsPage termsPage; - - @Rule - public AssertEvents events = new AssertEvents(this); - - @Before - public void before() { - setRealmInternationalization(true); - } - - @Override - public void configureTestRealm(RealmRepresentation testRealm) { - testRealm.addIdentityProvider(IdentityProviderBuilder.create() - .providerId("github") - .alias("github") - .build()); - testRealm.addIdentityProvider(IdentityProviderBuilder.create() - .providerId("saml") - .alias("mysaml") - .build()); - testRealm.addIdentityProvider(IdentityProviderBuilder.create() - .providerId("oidc") - .alias("myoidc") - .displayName("MyOIDC") - .build()); - - } - - @Test - public void languageDropdown() { - loginPage.open(); - assertEquals("English", loginPage.getLanguageDropdownText()); - - switchLanguageToGermanAndBack("Username or email", "Benutzername oder E-Mail", loginPage); - } - - @Test - public void uiLocalesParameter() { - oauth.loginForm().open(); - assertEquals("English", loginPage.getLanguageDropdownText()); - - //test if cookie works - oauth.loginForm().uiLocales("de").open(); - assertEquals("Deutsch", loginPage.getLanguageDropdownText()); - - driver.manage().deleteAllCookies(); - oauth.loginForm().uiLocales("de").open(); - assertEquals("Deutsch", loginPage.getLanguageDropdownText()); - - driver.manage().deleteAllCookies(); - oauth.loginForm().uiLocales("en de").open(); - assertEquals("English", loginPage.getLanguageDropdownText()); - - driver.manage().deleteAllCookies(); - oauth.loginForm().uiLocales("fr de").open(); - assertEquals("Deutsch", loginPage.getLanguageDropdownText()); - } - - @Test - public void htmlLangAttributeWithInternationalizationEnabled() { - loginPage.open(); - assertEquals("en", loginPage.getHtmlLanguage()); - - oauth.loginForm().uiLocales("de").open(); - assertEquals("de", loginPage.getHtmlLanguage()); - } - - @Test - public void htmlLangAttributeWithInternationalizationDisabled() { - setRealmInternationalization(false); - - loginPage.open(); - assertEquals("en", loginPage.getHtmlLanguage()); - } - - @Test - public void acceptLanguageHeader() throws IOException { - try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient); - ResteasyClient client = ((ResteasyClientBuilder) ResteasyClientBuilder.newBuilder()).httpEngine(engine).build(); - - loginPage.open(); - - try(Response responseDe = client.target(driver.getCurrentUrl()).request().acceptLanguage("de").get()) { - Assert.assertTrue(responseDe.readEntity(String.class).contains("Anmeldung bei test")); - - try(Response responseEn = client.target(driver.getCurrentUrl()).request().acceptLanguage("en").get()) { - Assert.assertTrue(responseEn.readEntity(String.class).contains("Sign in to test")); - } - } - - client.close(); - } - } - - @Test - public void testIdentityProviderCapitalization(){ - loginPage.open(); - // contains even name of sub-item - svg element in this case - assertThat(loginPage.findSocialButton("github").getText(), is("GitHub")); - assertThat(loginPage.findSocialButton("mysaml").getText(), is("mysaml")); - assertThat(loginPage.findSocialButton("myoidc").getText(), is("MyOIDC")); - } - - - // KEYCLOAK-3887 - @Test - public void languageChangeRequiredActions() { - UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost"); - UserRepresentation userRep = user.toRepresentation(); - userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())); - user.update(userRep); - - loginPage.open(); - - loginPage.login("test-user@localhost", "password"); - changePasswordPage.assertCurrent(); - assertEquals("English", changePasswordPage.getLanguageDropdownText()); - - // Switch language - switchLanguageToGermanAndBack("Update password", "Passwort aktualisieren", changePasswordPage); - - // Update password - changePasswordPage.changePassword("password", "password"); - - assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.parseLoginResponse().getCode()); - } - - - // KEYCLOAK-3887 - @Test - public void languageChangeConsentScreen() { - // Set client, which requires consent - oauth.client("third-party", "password"); - - loginPage.open(); - - loginPage.login("test-user@localhost", "password"); - - grantPage.assertCurrent(); - assertEquals("English", grantPage.getLanguageDropdownText()); - - // Switch language - switchLanguageToGermanAndBack("Do you grant these access privileges?", "Wollen Sie diese Zugriffsrechte", changePasswordPage); - - // Confirm grant - grantPage.accept(); - - assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.parseLoginResponse().getCode()); - - // Revert client - oauth.client("test-app", "password"); - } - - @Test - public void languageUserUpdates() { - loginPage.open(); - loginPage.openLanguage("Deutsch"); - - assertEquals("Deutsch", loginPage.getLanguageDropdownText()); - - Cookie localeCookie = driver.manage().getCookieNamed(CookieType.LOCALE.getName()); - assertEquals("de", localeCookie.getValue()); - - UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost"); - String userId = user.toRepresentation().getId(); - loginPage.login("test-user@localhost", "password"); - - events.expect(EventType.UPDATE_PROFILE) - .user(userId) - .client("test-app") - .detail(Details.PREF_UPDATED + UserModel.LOCALE, "de") - .assertEvent(); - events.expectLogin() - .user(userId) - .client("test-app") - .assertEvent(); - - UserRepresentation userRep = user.toRepresentation(); - assertEquals("de", userRep.getAttributes().get("locale").get(0)); - - String code = oauth.parseLoginResponse().getCode(); - String idTokenHint = oauth.doAccessTokenRequest(code).getIdToken(); - appPage.logout(idTokenHint); - - loginPage.open(); - - assertEquals("Deutsch", loginPage.getLanguageDropdownText()); - - userRep.getAttributes().remove("locale"); - user.update(userRep); - - loginPage.open(); - loginPage.login("test-user@localhost", "password"); - - // User locale should not be updated due to previous cookie - userRep = user.toRepresentation(); - Assert.assertNull(userRep.getAttributes()); - - code = oauth.parseLoginResponse().getCode(); - idTokenHint = oauth.doAccessTokenRequest(code).getIdToken(); - appPage.logout(idTokenHint); - - loginPage.open(); - - // Cookie should be removed as last user to login didn't have a locale - localeCookie = driver.manage().getCookieNamed(CookieType.LOCALE.getName()); - Assert.assertNull(localeCookie); - } - - - // Test for user updating locale on the error page (when authenticationSession is not available) - @Test - public void languageUserUpdatesOnErrorPage() { - // Login page with invalid redirect_uri - oauth.redirectUri("http://invalid"); - loginPage.open(); - - errorPage.assertCurrent(); - Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); - - // Change language should be OK - errorPage.openLanguage("Deutsch"); - assertEquals("Deutsch", errorPage.getLanguageDropdownText()); - Assert.assertEquals("Ungültiger Parameter: redirect_uri", errorPage.getError()); - - // Refresh browser button should keep german language - driver.navigate().refresh(); - assertEquals("Deutsch", errorPage.getLanguageDropdownText()); - Assert.assertEquals("Ungültiger Parameter: redirect_uri", errorPage.getError()); - - // Changing to english should work - errorPage.openLanguage("English"); - assertEquals("English", errorPage.getLanguageDropdownText()); - Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); - } - - @Test - public void languageUserUpdatesOnErrorPageStateCheckerTest() throws URISyntaxException { - // Login page with invalid redirect_uri - oauth.redirectUri("http://invalid"); - loginPage.open(); - - errorPage.assertCurrent(); - Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); - - errorPage.openLanguage("Deutsch"); - Assert.assertEquals("Ungültiger Parameter: redirect_uri", errorPage.getError()); - - // Add incorrect state checker parameter. Error page should be shown about expired action. Language won't be changed - String currentUrl = driver.getCurrentUrl(); - String newUrl = KeycloakUriBuilder.fromUri(new URI(currentUrl)) - .replaceQueryParam(LocaleSelectorProvider.KC_LOCALE_PARAM, "en") - .replaceQueryParam(DetachedInfoStateChecker.STATE_CHECKER_PARAM, "invalid").buildAsString(); - driver.navigate().to(newUrl); - - Assert.assertEquals("Die Aktion ist nicht mehr gültig.", errorPage.getError()); // Action expired. - } - - @Test - public void languageUserUpdatesOnExpiredPage() throws Exception { - try (UserAttributeUpdater userUpdater = UserAttributeUpdater.forUserByUsername(testRealm(), "test-user@localhost") - .setRequiredActions(UserModel.RequiredAction.UPDATE_PASSWORD).update()) { - // login with a failure attempt - loginPage.open(); - loginPage.login("test-user@localhost", "invalid-password"); - loginPage.assertCurrent(); - assertThat(loginPage.getUsernameInputError(), is("Invalid username or password.")); - loginPage.login("test-user@localhost", "password"); - changePasswordPage.assertCurrent(); - - // navigate back to the login expired page and change language to german - UIUtils.navigateBackWithRefresh(driver, loginExpiredPage); - errorPage.openLanguage("Deutsch"); - assertEquals("Deutsch", errorPage.getLanguageDropdownText()); - assertThat(PageUtils.getPageTitle(driver), is("Diese Seite ist nicht mehr gültig.")); - - // continue should show password update in german - loginExpiredPage.clickLoginContinueLink(); - assertEquals("Deutsch", changePasswordPage.getLanguageDropdownText()); - assertThat(PageUtils.getPageTitle(driver), is("Passwort aktualisieren")); - } - } - - // GH issue 41292 - @Test - public void languageUserUpdatesOnCustomAuthenticatorPage() { - configureBrowserFlowWithClickThroughAuthenticator(); - - loginPage.open(); - Assert.assertTrue(termsPage.isCurrent()); - - // Change language on the custom page - switchLanguageToGermanAndBack("Terms and Conditions", "Bedingungen und Konditionen", termsPage); - - // Revert dummy flow - RealmRepresentation rep = testRealm().toRepresentation(); - rep.setBrowserFlow("browser"); - testRealm().update(rep); - } - - @Test - public void realmLocalizationMessagesAreApplied() { - String realmLocalizationMessageKey = "loginAccountTitle"; - - String realmLocalizationMessageValueEn = "Localization Test EN"; - saveLocalizationText(Locale.ENGLISH.toLanguageTag(), realmLocalizationMessageKey, - realmLocalizationMessageValueEn); - String realmLocalizationMessageValueDe = "Localization Test DE"; - saveLocalizationText(Locale.GERMAN.toLanguageTag(), realmLocalizationMessageKey, - realmLocalizationMessageValueDe); - - loginPage.open(); - switchLanguageToGermanAndBack(realmLocalizationMessageValueEn, realmLocalizationMessageValueDe, loginPage); - } - - // KEYCLOAK-18590 - @Test - public void realmLocalizationMessagesAreNotCachedWithinTheTheme() { - final String locale = Locale.ENGLISH.toLanguageTag(); - - final String realmLocalizationMessageKey = "loginAccountTitle"; - final String realmLocalizationMessageValue = "Localization Test"; - - saveLocalizationText(locale, realmLocalizationMessageKey, realmLocalizationMessageValue); - loginPage.open(); - assertThat(driver.getPageSource(), containsString(realmLocalizationMessageValue)); - - testRealm().localization().deleteRealmLocalizationText(locale, realmLocalizationMessageKey); - loginPage.open(); - assertThat(driver.getPageSource(), not(containsString(realmLocalizationMessageValue))); - } - - @Test - public void realmLocalizationMessagesUsedDuringErrorHandling() { - final String locale = Locale.ENGLISH.toLanguageTag(); - - final String realmLocalizationMessageKey = "errorTitle"; - final String realmLocalizationMessageValue = "We are really sorry..."; - - saveLocalizationText(locale, realmLocalizationMessageKey, realmLocalizationMessageValue); - String nonExistingUrl = oauth.loginForm().build().split("protocol")[0] + "incorrect-path"; - driver.navigate().to(nonExistingUrl); - - assertThat(driver.getPageSource(), containsString(realmLocalizationMessageValue)); - } - - private void saveLocalizationText(String locale, String key, String value) { - testRealm().localization().saveRealmLocalizationText(locale, key, value); - getCleanup().addLocalization(locale); - } - - private void switchLanguageToGermanAndBack(String expectedEnglishMessage, String expectedGermanMessage, LanguageComboboxAwarePage page) { - // Switch language to Deutsch - page.openLanguage("Deutsch"); - assertEquals("Deutsch", page.getLanguageDropdownText()); - String pageSource = driver.getPageSource(); - assertThat(pageSource, not(containsString(expectedEnglishMessage))); - assertThat(pageSource, containsString(expectedGermanMessage)); - - // Revert language - page.openLanguage("English"); - assertEquals("English", page.getLanguageDropdownText()); - pageSource = driver.getPageSource(); - assertThat(pageSource, containsString(expectedEnglishMessage)); - assertThat(pageSource, not(containsString(expectedGermanMessage))); - } - - private void setRealmInternationalization(final boolean enabled) { - final var realmResource = testRealm(); - RealmRepresentation realm = realmResource.toRepresentation(); - realm.setInternationalizationEnabled(enabled); - realmResource.update(realm); - } - - private void configureBrowserFlowWithClickThroughAuthenticator() { - final String newFlowAlias = "browser - rule"; - testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias)); - testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session) - .selectFlow(newFlowAlias) - .inForms(forms -> forms - .clear() - // Update the browser forms with a UsernamePasswordForm - .addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, ClickThroughAuthenticator.PROVIDER_ID) - ) - .defineAsBrowserFlow() - ); - } -} diff --git a/testsuite/integration-arquillian/tests/base/testsuites/base-suite b/testsuite/integration-arquillian/tests/base/testsuites/base-suite index 78eb086edda..1c5cfade774 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/base-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/base-suite @@ -17,7 +17,6 @@ exportimport,4 feature,4 federation,5 forms,5 -i18n,5 login,4 migration,4 model,6 diff --git a/testsuite/integration-arquillian/tests/base/testsuites/login-suite b/testsuite/integration-arquillian/tests/base/testsuites/login-suite index 0b677864827..589db0046cc 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/login-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/login-suite @@ -3,4 +3,3 @@ org.keycloak.testsuite.forms KcOidcBrokerLoginHintTest KcOidcFirstBrokerLoginTest KcOidcMultipleTabsBrokerTest -LoginPageTest