From 9aad6f650da8425844eb656f98d8563e208a5243 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 11 Sep 2024 20:52:49 +0200 Subject: [PATCH] added more style fixes for the login.v2 (#32708) * added more style fixes for the login.v2 related: #32522 Signed-off-by: Erik Jan de Wit * fixed grant screen Signed-off-by: Erik Jan de Wit * test fixes Signed-off-by: Erik Jan de Wit * fix for code.ftl Signed-off-by: Erik Jan de Wit * test fixes Signed-off-by: Erik Jan de Wit * fixed tests Signed-off-by: Erik Jan de Wit --------- Signed-off-by: Erik Jan de Wit --- .../login/DeleteAccountActionConfirmPage.java | 2 +- .../auth/page/login/LoginActions.java | 4 +- .../testsuite/auth/page/login/OAuthGrant.java | 4 +- .../auth/page/login/OneTimeCode.java | 2 +- .../page/LoginPasswordUpdatePage.java | 2 +- .../pages/LoginPasswordResetPage.java | 2 +- .../pages/LoginPasswordUpdatePage.java | 6 +- .../testsuite/pages/LoginTotpPage.java | 17 ++-- .../testsuite/pages/OAuthGrantPage.java | 4 +- .../util/saml/RequiredConsentBuilder.java | 30 ++++---- .../testsuite/forms/BrowserFlowTest.java | 46 +++++------ .../testsuite/forms/ResetPasswordTest.java | 2 +- .../theme/keycloak.v2/login/buttons.ftl | 7 +- .../theme/keycloak.v2/login/code.ftl | 20 +++++ .../login/delete-account-confirm.ftl | 40 ++++++++++ .../keycloak.v2/login/delete-credential.ftl | 21 +++++ .../theme/keycloak.v2/login/field.ftl | 10 +-- .../keycloak.v2/login/login-oauth-grant.ftl | 59 ++++++++++++++ .../theme/keycloak.v2/login/login-otp.ftl | 45 +++++++++++ .../login/login-reset-password.ftl | 27 +++++++ .../login/login-update-password.ftl | 77 ++++--------------- .../keycloak.v2/login/login-username.ftl | 2 +- .../theme/keycloak.v2/login/login.ftl | 4 +- .../theme/keycloak.v2/login/template.ftl | 12 +-- .../theme/keycloak.v2/login/terms.ftl | 21 +++++ .../theme/keycloak.v2/login/theme.properties | 9 +++ .../login/user-profile-commons.ftl | 60 ++++++++++----- .../keycloak.v2/login/webauthn-register.ftl | 61 +++++++++++++++ 28 files changed, 438 insertions(+), 158 deletions(-) create mode 100755 themes/src/main/resources/theme/keycloak.v2/login/code.ftl create mode 100644 themes/src/main/resources/theme/keycloak.v2/login/delete-account-confirm.ftl create mode 100644 themes/src/main/resources/theme/keycloak.v2/login/delete-credential.ftl create mode 100755 themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl create mode 100755 themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl create mode 100644 themes/src/main/resources/theme/keycloak.v2/login/login-reset-password.ftl create mode 100755 themes/src/main/resources/theme/keycloak.v2/login/terms.ftl create mode 100644 themes/src/main/resources/theme/keycloak.v2/login/webauthn-register.ftl diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/DeleteAccountActionConfirmPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/DeleteAccountActionConfirmPage.java index a0f739ee3b..63cdbdc50d 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/DeleteAccountActionConfirmPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/DeleteAccountActionConfirmPage.java @@ -12,7 +12,7 @@ public class DeleteAccountActionConfirmPage extends RequiredActions { @FindBy(css = "button[name='cancel-aia']") WebElement cancelActionButton; - @FindBy(css = "input[type='submit']") + @FindBy(css = "button[type='submit']") WebElement confirmActionButton; @Override diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/LoginActions.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/LoginActions.java index e1087bb129..369e154184 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/LoginActions.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/LoginActions.java @@ -36,10 +36,10 @@ public class LoginActions extends LoginBase { .path("login-actions"); } - @FindBy(css = "input[type='submit']") + @FindBy(css = "button[type='submit']") private WebElement submitButton; - @FindBy(css = "button[type='submit']") + @FindBy(css = "button[name='cancel-aia']") private WebElement cancelButton; public void submit() { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java index 4d055dfe59..4e9ce9ff35 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java @@ -34,10 +34,10 @@ import static org.keycloak.testsuite.util.UIUtils.clickLink; * @author Vaclav Muzikar */ public class OAuthGrant extends RequiredActions { - @FindBy(css = "input[name=\"accept\"]") + @FindBy(css = "button[name=\"accept\"]") private WebElement acceptButton; - @FindBy(css = "input[name=\"cancel\"]") + @FindBy(css = "button[name=\"cancel\"]") private WebElement cancelButton; @FindBy(xpath = "//div[@id='kc-oauth']/ul/li/span") diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OneTimeCode.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OneTimeCode.java index bfa88cfdd6..6c8cbe9f13 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OneTimeCode.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OneTimeCode.java @@ -37,7 +37,7 @@ public class OneTimeCode extends Authenticate { @FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']") private WebElement loginErrorMessage; - @FindBy(id = "input-error-otp-code") + @FindBy(id = "input-error-otp") private WebElement totpInputCodeError; public String getOtpLabel() { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/LoginPasswordUpdatePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/LoginPasswordUpdatePage.java index 1e71b4e101..3b8e330514 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/LoginPasswordUpdatePage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/LoginPasswordUpdatePage.java @@ -40,7 +40,7 @@ public class LoginPasswordUpdatePage { @FindBy(id = "password-confirm") private WebElement passwordConfirmInput; - @FindBy(css = "input[type=\"submit\"]") + @FindBy(css = "button[type=\"submit\"]") private WebElement submitButton; @FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']") diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java index d80a109dc6..16b7904f59 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java @@ -32,7 +32,7 @@ public class LoginPasswordResetPage extends LanguageComboboxAwarePage { @FindBy(id = "input-error-username") private WebElement usernameError; - @FindBy(css = "input[type=\"submit\"]") + @FindBy(css = "button[type=\"submit\"]") private WebElement submitButton; @FindBy(className = "pf-v5-c-success") diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java index 4bed0ee168..2c87801415 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java @@ -32,7 +32,7 @@ public class LoginPasswordUpdatePage extends LogoutSessionsPage { @FindBy(id = "password-confirm") private WebElement passwordConfirmInput; - @FindBy(css = "input[type=\"submit\"]") + @FindBy(css = "button[type=\"submit\"]") private WebElement submitButton; @FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']") @@ -40,7 +40,7 @@ public class LoginPasswordUpdatePage extends LogoutSessionsPage { @FindBy(className = "kc-feedback-text") private WebElement feedbackMessage; - + @FindBy(name = "cancel-aia") private WebElement cancelAIAButton; @@ -50,7 +50,7 @@ public class LoginPasswordUpdatePage extends LogoutSessionsPage { submitButton.click(); } - + public void cancel() { cancelAIAButton.click(); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginTotpPage.java index b8b985212e..2f9971b90c 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginTotpPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginTotpPage.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import org.junit.Assert; import org.keycloak.common.util.Retry; import org.keycloak.testsuite.util.UIUtils; +import org.keycloak.testsuite.util.WaitUtils; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; @@ -38,13 +39,13 @@ public class LoginTotpPage extends LanguageComboboxAwarePage { @FindBy(id = "password-token") private WebElement passwordToken; - @FindBy(css = "input[type=\"submit\"]") + @FindBy(css = "button[type=\"submit\"]") private WebElement submitButton; @FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']") private WebElement loginErrorMessage; - @FindBy(id = "input-error-otp-code") + @FindBy(id = "input-error-otp") private WebElement totpInputCodeError; public void login(String totp) { @@ -88,7 +89,7 @@ public class LoginTotpPage extends LanguageComboboxAwarePage { // If false, we don't expect that credentials combobox is available. If true, we expect that it is available on the page public void assertOtpCredentialSelectorAvailability(boolean expectedAvailability) { try { - driver.findElement(By.className("pf-c-tile")); + driver.findElement(By.className("pf-v5-c-tile")); Assert.assertTrue(expectedAvailability); } catch (NoSuchElementException nse) { Assert.assertFalse(expectedAvailability); @@ -113,21 +114,19 @@ public class LoginTotpPage extends LanguageComboboxAwarePage { } private By getXPathForLookupAllCards() { - return By.xpath("//span[contains(@class, 'pf-c-tile__title')]"); + return By.xpath("//span[contains(@class, 'pf-v5-c-tile__title')]"); } private By getCssSelectorForLookupActiveCard() { - return By.cssSelector(".pf-c-tile__input:checked + .pf-c-tile .pf-c-tile__title"); + return By.cssSelector(".pf-m-selected"); } private By getXPathForLookupCardWithName(String credentialName) { - return By.xpath("//label[contains(@class, 'pf-c-tile')][normalize-space() = '"+ credentialName +"']"); + return By.xpath("//div[contains(@class, 'pf-v5-c-tile')][normalize-space() = '"+ credentialName +"']"); } public void selectOtpCredential(String credentialName) { - waitForElement(getCssSelectorForLookupActiveCard()); - WebElement webElement = driver.findElement( getXPathForLookupCardWithName(credentialName)); UIUtils.clickLink(webElement); @@ -143,4 +142,4 @@ public class LoginTotpPage extends LanguageComboboxAwarePage { }, 10, 10); } -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java index 778e45fb85..32fb891b31 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java @@ -41,9 +41,9 @@ public class OAuthGrantPage extends LanguageComboboxAwarePage { public static final String OFFLINE_ACCESS_CONSENT_TEXT = "Offline Access"; public static final String ROLES_CONSENT_TEXT = "User roles"; - @FindBy(css = "input[name=\"accept\"]") + @FindBy(css = "button[name=\"accept\"]") private WebElement acceptButton; - @FindBy(css = "input[name=\"cancel\"]") + @FindBy(css = "button[name=\"cancel\"]") private WebElement cancelButton; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/RequiredConsentBuilder.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/RequiredConsentBuilder.java index da0e436742..d9e41b6995 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/RequiredConsentBuilder.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/RequiredConsentBuilder.java @@ -1,13 +1,13 @@ /* * Copyright 2017 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. @@ -18,11 +18,13 @@ package org.keycloak.testsuite.util.saml; import org.keycloak.testsuite.util.SamlClient.Step; import org.keycloak.testsuite.util.SamlClientBuilder; + import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.LinkedList; import java.util.List; import java.util.Objects; + import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import org.apache.http.NameValuePair; @@ -37,12 +39,12 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; + import static org.hamcrest.Matchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC; /** - * * @author hmlnarik */ public class RequiredConsentBuilder implements Step { @@ -88,18 +90,18 @@ public class RequiredConsentBuilder implements Step { for (Element form : theLoginPage.getElementsByTag("form")) { String method = form.attr("method"); String action = form.attr("action"); - boolean isPost = method != null && "post".equalsIgnoreCase(method); + boolean isPost = "post".equalsIgnoreCase(method); + + Element submitButton; + if (approveConsent) { + submitButton = form.getElementById("kc-login"); + } else { + submitButton = form.getElementById("kc-cancel"); + } + parameters.add(new BasicNameValuePair(submitButton.attr("name"), submitButton.attr("value"))); for (Element input : form.getElementsByTag("input")) { - if (Objects.equals(input.id(), "kc-login")) { - if (approveConsent) - parameters.add(new BasicNameValuePair(input.attr("name"), input.attr("value"))); - } else if (Objects.equals(input.id(), "kc-cancel")) { - if (!approveConsent) - parameters.add(new BasicNameValuePair(input.attr("name"), input.attr("value"))); - } else { - parameters.add(new BasicNameValuePair(input.attr("name"), input.val())); - } + parameters.add(new BasicNameValuePair(input.attr("name"), input.val())); } if (isPost) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java index 38ce45b54a..d683e9319b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java @@ -181,6 +181,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent()); loginTotpPage.assertCurrent(); loginTotpPage.assertOtpCredentialSelectorAvailability(true); + loginTotpPage.selectOtpCredential("first"); // Check that selected credential is "first" Assert.assertEquals("first", loginTotpPage.getSelectedOtpCredential()); @@ -207,6 +208,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent()); loginTotpPage.assertCurrent(); loginTotpPage.assertOtpCredentialSelectorAvailability(true); + loginTotpPage.selectOtpCredential(orderedCredentials.get(0)); // Check that preferred credential is selected Assert.assertEquals(orderedCredentials.get(0), loginTotpPage.getSelectedOtpCredential()); @@ -252,7 +254,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { // A conditional flow without conditional authenticator should automatically be disabled @Test - + public void testFlowDisabledWhenConditionalAuthenticatorIsMissing() { try { configureBrowserFlowWithConditionalSubFlowHavingConditionalAuthenticator("browser - non missing conditional authenticator", true); @@ -288,7 +290,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { // A conditional flow with disabled conditional authenticator should automatically be disabled @Test - + public void testFlowDisabledWhenConditionalAuthenticatorIsDisabled() { try { configureBrowserFlowWithConditionalSubFlowHavingDisabledConditionalAuthenticator("browser - disabled conditional authenticator"); @@ -321,7 +323,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { // Configure a conditional authenticator in a non-conditional sub-flow // In such case, the flow is evaluated and the conditional authenticator is considered as disabled @Test - + public void testConditionalAuthenticatorInNonConditionalFlow() { try { configureBrowserFlowWithConditionalAuthenticatorInNonConditionalFlow(); @@ -364,7 +366,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { // user-with-two-configured-otp has the "user" role and should be asked for an OTP code // user-with-one-configured-otp does not have the role. He should not be asked for an OTP code @Test - + public void testConditionalRoleAuthenticator() { String requiredRole = "user"; // A browser flow is configured with an OTPForm for users having the role "user" @@ -392,7 +394,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { // user-with-two-configured-otp has the "composite-realm-role-1" role and should be asked for an OTP code // user-with-one-configured-otp does not have the role. He should not be asked for an OTP code @Test - + public void testConditionalRoleAuthenticatorWithRealmRoleIncludedInCompositeRealmRole() { // Create composite-realm-role-1 @@ -439,7 +441,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { // user-with-two-configured-otp has the "composite-client-role-1" role and should be asked for an OTP code // user-with-one-configured-otp does not have the role. He should not be asked for an OTP code @Test - + public void testConditionalRoleAuthenticatorWithClientRoleIncludedInCompositeClientRole() { String clientName = "test-app"; @@ -521,7 +523,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { // Configure a conditional authenticator with a condition which change while the flow evaluation // In such case, all the required authenticator inside the subflow should be evaluated even if the condition has changed @Test - + public void testConditionalAuthenticatorWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation() { try { configureBrowserFlowWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation(); @@ -595,7 +597,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { } @Test - + public void testSwitchExecutionNotAllowedWithRequiredPasswordAndAlternativeOTP() { String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithRequiredPasswordFormAndAlternativeOTP(newFlowAlias); @@ -631,7 +633,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { @Test - + public void testSocialProvidersPresentOnLoginUsernameOnlyPageIfConfigured() { String testRealm = "test"; // Test setup - Configure the testing Keycloak instance with UsernameForm & PasswordForm (both REQUIRED) and OTPFormAuthenticator (ALTERNATIVE) @@ -688,7 +690,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { } @Test - + public void testConditionalFlowWithConditionalAuthenticatorEvaluatingToFalseActsAsDisabled(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithConditionalFlowWithOTP(newFlowAlias); @@ -707,7 +709,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { } @Test - + public void testConditionalFlowWithConditionalAuthenticatorEvaluatingToTrueActsAsRequired(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithConditionalFlowWithOTP(newFlowAlias); @@ -769,7 +771,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * In this test the user is expected to have to log in with OTP */ @Test - + public void testConditionalFlowWithMultipleConditionalAuthenticatorsWithUserWithRoleAndOTP() { String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithConditionalFlowWithMultipleConditionalAuthenticators(newFlowAlias); @@ -800,7 +802,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * In this test, the user is expected to have to login with username and password only, as the conditional branch evaluates to false, and is therefore DISABLED */ @Test - + public void testConditionalFlowWithMultipleConditionalAuthenticatorsWithUserWithRoleButNotOTP() { String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithConditionalFlowWithMultipleConditionalAuthenticators(newFlowAlias); @@ -866,7 +868,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * and will instead raise an credential setup required error. */ @Test - + public void testLoginWithWithNoOTPCredentialAndNoRequiredActionProviderRegistered(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithRequiredOTP(newFlowAlias); @@ -893,7 +895,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * and will instead raise an credential setup required error. */ @Test - + public void testLoginWithWithNoOTPCredentialAndRequiredActionProviderDisabled(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithRequiredOTP(newFlowAlias); @@ -918,7 +920,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * has its requiredActionProvider enabled, than it will login and show the otpSetup page. */ @Test - + public void testLoginWithWithNoOTPCredential(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithRequiredOTP(newFlowAlias);; @@ -961,7 +963,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * and will instead raise an credential setup required error. */ @Test - + public void testLoginWithWithNoWebAuthnCredentialAndRequiredActionProviderDisabled(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithRequiredWebAuthn(newFlowAlias); @@ -984,7 +986,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * has its requiredActionProvider enabled, then it will login and show the WebAuthn registration page. */ @Test - + public void testLoginWithWithNoWebAuthnCredential(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithRequiredWebAuthn(newFlowAlias); @@ -1027,7 +1029,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * then the selection mechanism will see that there's no viable alternative, and move on to the next execution (in this case the flow) */ @Test - + public void testLoginWithWithNoOTPCredentialAndAlternativeActionProvider(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithAlternativeOTPAndPassword(newFlowAlias); @@ -1073,7 +1075,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * This test checks the error messages, when the credentials are invalid and UsernameForm and PasswordForm are separated. */ @Test - + public void testLoginMultiFactorWithWrongCredentialsMessage() { UserRepresentation user = testRealm().users().search("test-user@localhost").get(0); Assert.assertNotNull(user); @@ -1155,7 +1157,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * then it will not try to create the required action, and will instead move to the next alternative */ @Test - + public void testLoginWithWithNoWebAuthnCredentialAndAlternativeActionProvider(){ String newFlowAlias = "browser - copy 1"; configureBrowserFlowWithAlternativeWebAuthnAndPassword(newFlowAlias); @@ -1182,7 +1184,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { * After login with password and fulfill the conditional subflow2, the subflow1 should be considered successful as well and the OTP authentication should not be needed */ @Test - + public void testLoginWithAlternativeOTPAndConditionalPassword(){ String newFlowAlias = "browser - copy 2"; configureBrowserFlowWithAlternativeOTPAndConditionalPassword(newFlowAlias); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index 70edaec9b5..5f72235f42 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -1370,7 +1370,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest { newPassword.sendKeys("resetPassword"); final WebElement confirmPassword = driver.findElement(By.id("password-confirm")); confirmPassword.sendKeys("resetPassword"); - final WebElement submit = driver.findElement(By.cssSelector("input[type=\"submit\"]")); + final WebElement submit = driver.findElement(By.cssSelector("button[type=\"submit\"]")); submit.click(); } diff --git a/themes/src/main/resources/theme/keycloak.v2/login/buttons.ftl b/themes/src/main/resources/theme/keycloak.v2/login/buttons.ftl index f37c8f9105..90e499cc9c 100644 --- a/themes/src/main/resources/theme/keycloak.v2/login/buttons.ftl +++ b/themes/src/main/resources/theme/keycloak.v2/login/buttons.ftl @@ -3,12 +3,17 @@
<#nested>
+ -<#macro button id name label class=["kcButtonPrimaryClass"]> +<#macro button label id="" name="" class=["kcButtonPrimaryClass"]> +<#macro buttonLink href label id="" class=["kcButtonSecondaryClass"]> + ${kcSanitize(msg(label))?no_esc} + + <#macro loginButton> <@buttons.actionGroup> <@buttons.button id="kc-login" name="login" label="doLogIn" class=["kcButtonPrimaryClass", "kcButtonBlockClass"] /> diff --git a/themes/src/main/resources/theme/keycloak.v2/login/code.ftl b/themes/src/main/resources/theme/keycloak.v2/login/code.ftl new file mode 100755 index 0000000000..61f855e381 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/code.ftl @@ -0,0 +1,20 @@ +<#import "template.ftl" as layout> +<#import "field.ftl" as field> +<@layout.registrationLayout; section> + <#if section = "header"> + <#if code.success> + ${msg("codeSuccessTitle")} + <#else> + ${kcSanitize(msg("codeErrorTitle", code.error))} + + <#elseif section = "form"> +
+ <#if code.success> +

${msg("copyCodeInstruction")}

+ <@field.input name="code" label="" value=code.code /> + <#else> +

${kcSanitize(code.error)}

+ +
+ + diff --git a/themes/src/main/resources/theme/keycloak.v2/login/delete-account-confirm.ftl b/themes/src/main/resources/theme/keycloak.v2/login/delete-account-confirm.ftl new file mode 100644 index 0000000000..c755fad61d --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/delete-account-confirm.ftl @@ -0,0 +1,40 @@ +<#import "template.ftl" as layout> +<#import "buttons.ftl" as buttons> + +<@layout.registrationLayout; section> + + + <#if section = "header"> + ${msg("deleteAccountConfirm")} + + <#elseif section = "form"> + +
+ +
+
+ +
+ + ${msg("irreversibleAction")} + +
+ +

${msg("deletingImplies")}

+
    +
  • ${msg("loggingOutImmediately")}
  • +
  • ${msg("errasingData")}
  • +
+ + + + <@buttons.actionGroup> + <@buttons.button label="doConfirmDelete" class=["kcButtonPrimaryClass"]/> + <#if triggered_from_aia> + <@buttons.button name="cancel-aia" label="doCancel" class=["kcButtonSecondaryClass"]/> + + +
+ + + \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak.v2/login/delete-credential.ftl b/themes/src/main/resources/theme/keycloak.v2/login/delete-credential.ftl new file mode 100644 index 0000000000..fe6a815e86 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/delete-credential.ftl @@ -0,0 +1,21 @@ +<#import "template.ftl" as layout> +<#import "buttons.ftl" as buttons> + +<@layout.registrationLayout displayMessage=false; section> + + + <#if section = "header"> + ${msg("deleteCredentialTitle", credentialLabel)} + <#elseif section = "form"> +
+ ${msg("deleteCredentialMessage", credentialLabel)} +
+ +
+ <@buttons.actionGroup> + <@buttons.button name="accept" id="kc-accept" label="doConfirmDelete" class=["kcButtonPrimaryClass"]/> + <@buttons.button name="cancel-aia" id="kc-decline" label="doDecline" class=["kcButtonSecondaryClass"]/> + +
+ diff --git a/themes/src/main/resources/theme/keycloak.v2/login/field.ftl b/themes/src/main/resources/theme/keycloak.v2/login/field.ftl index ba43a85f3c..b508d1838b 100644 --- a/themes/src/main/resources/theme/keycloak.v2/login/field.ftl +++ b/themes/src/main/resources/theme/keycloak.v2/login/field.ftl @@ -41,19 +41,19 @@ -<#macro input name label value="" required=false> - <#assign error=kcSanitize(messagesPerField.get(name))?no_esc> +<#macro input name label value="" required=false autocomplete="off" fieldName=name> + <#assign error=kcSanitize(messagesPerField.get(fieldName))?no_esc> <@field.group name=name label=label error=error required=required> - <@errorIcon error=error/> -<#macro password name label value="" required=false forgotPassword=false> - <#assign error=kcSanitize(messagesPerField.get(name))?no_esc> +<#macro password name label value="" required=false forgotPassword=false fieldName=name> + <#assign error=kcSanitize(messagesPerField.get(fieldName))?no_esc> <@field.group name=name label=label error=error required=required>
diff --git a/themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl b/themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl new file mode 100755 index 0000000000..5ffc8a2b46 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl @@ -0,0 +1,59 @@ +<#import "template.ftl" as layout> +<#import "buttons.ftl" as buttons> +<@layout.registrationLayout bodyClass="oauth"; section> + <#if section = "header"> + <#if client.attributes.logoUri??> + + +

+ <#if client.name?has_content> + ${msg("oauthGrantTitle",advancedMsg(client.name))} + <#else> + ${msg("oauthGrantTitle",client.clientId)} + +

+ <#elseif section = "form"> +
+

${msg("oauthGrantRequest")}

+
    + <#if oauth.clientScopesRequested??> + <#list oauth.clientScopesRequested as clientScope> +
  • + <#if !clientScope.dynamicScopeParameter??> + ${advancedMsg(clientScope.consentScreenText)} + <#else> + ${advancedMsg(clientScope.consentScreenText)}: ${clientScope.dynamicScopeParameter} + + +
  • + + +
+ <#if client.attributes.policyUri?? || client.attributes.tosUri??> +

+ <#if client.name?has_content> + ${msg("oauthGrantInformation",advancedMsg(client.name))} + <#else> + ${msg("oauthGrantInformation",client.clientId)} + + <#if client.attributes.tosUri??> + ${msg("oauthGrantReview")} + ${msg("oauthGrantTos")} + + <#if client.attributes.policyUri??> + ${msg("oauthGrantReview")} + ${msg("oauthGrantPolicy")} + +

+ + +
+ + <@buttons.actionGroup> + <@buttons.button id="kc-login" name="accept" label="doYes"/> + <@buttons.button id="kc-cancel" name="cancel" label="doNo" class=["kcButtonSecondaryClass"]/> + +
+
+ + diff --git a/themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl b/themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl new file mode 100755 index 0000000000..f9aa4c60e8 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl @@ -0,0 +1,45 @@ +<#import "template.ftl" as layout> +<#import "field.ftl" as field> +<#import "buttons.ftl" as buttons> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('totp'); section> + + + <#if section="header"> + ${msg("doLogIn")} + <#elseif section="form"> +
+ + <#if otpLogin.userOtpCredentials?size gt 1> +
+
+ <#list otpLogin.userOtpCredentials as otpCredential> +
+ + + + + ${otpCredential.userLabel} + +
+ +
+
+ + + <@field.input name="otp" label=msg("loginOtpOneTime") autocomplete="one-time-code" fieldName="totp" /> + + <@buttons.loginButton /> +
+ + + \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak.v2/login/login-reset-password.ftl b/themes/src/main/resources/theme/keycloak.v2/login/login-reset-password.ftl new file mode 100644 index 0000000000..cfce6e9495 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/login-reset-password.ftl @@ -0,0 +1,27 @@ +<#import "template.ftl" as layout> +<#import "field.ftl" as field> +<#import "buttons.ftl" as buttons> +<@layout.registrationLayout displayInfo=true displayMessage=!messagesPerField.existsError('username'); section> + <#if section = "header"> + ${msg("emailForgotTitle")} + <#elseif section = "form"> +
+ <#assign label> + <#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")} + + <@field.input name="username" label=label value=auth.attemptedUsername!''/> + + <@buttons.actionGroup> + <@buttons.button id="kc-form-buttons" label="doSubmit" class=["kcButtonPrimaryClass", "kcButtonBlockClass"]/> + <@buttons.buttonLink href=url.loginUrl label="backToLogin" class=["kcButtonSecondaryClass", "kcButtonBlockClass"]/> + + +
+ <#elseif section = "info" > + <#if realm.duplicateEmailsAllowed> + ${msg("emailInstructionUsername")} + <#else> + ${msg("emailInstruction")} + + + \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak.v2/login/login-update-password.ftl b/themes/src/main/resources/theme/keycloak.v2/login/login-update-password.ftl index 1e43ae2408..41688789ce 100755 --- a/themes/src/main/resources/theme/keycloak.v2/login/login-update-password.ftl +++ b/themes/src/main/resources/theme/keycloak.v2/login/login-update-password.ftl @@ -1,79 +1,28 @@ <#import "template.ftl" as layout> <#import "password-commons.ftl" as passwordCommons> +<#import "field.ftl" as field> +<#import "buttons.ftl" as buttons> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section> + <#if section = "header"> ${msg("updatePasswordTitle")} <#elseif section = "form">
-
- -
- - - - -
- - <#if messagesPerField.existsError('password')> - - ${kcSanitize(messagesPerField.get('password'))?no_esc} - - -
- -
- -
- - - - -
- - <#if messagesPerField.existsError('password-confirm')> - - ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} - - -
+ <@field.password name="password-new" label=msg("passwordNew") fieldName="password" /> + <@field.password name="password-confirm" label=msg("passwordConfirm") />
<@passwordCommons.logoutOtherSessions/>
-
-
- <#if isAppInitiatedAction??> - - - <#else> - - -
-
+ <@buttons.actionGroup> + <#if isAppInitiatedAction??> + <@buttons.button label="doSubmit" class=["kcButtonPrimaryClass"]/> + <@buttons.button label="doCancel" name="cancel-aia" class=["kcButtonSecondaryClass"]/> + <#else> + <@buttons.button label="doSubmit" class=["kcButtonPrimaryClass", "kcButtonBlockClass"]/> + +
diff --git a/themes/src/main/resources/theme/keycloak.v2/login/login-username.ftl b/themes/src/main/resources/theme/keycloak.v2/login/login-username.ftl index 1325aa85f6..4d3cc44c53 100755 --- a/themes/src/main/resources/theme/keycloak.v2/login/login-username.ftl +++ b/themes/src/main/resources/theme/keycloak.v2/login/login-username.ftl @@ -17,7 +17,7 @@ <#assign label> <#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")} - <@field.input name="username" label=label value="${(login.username!'')}" /> + <@field.input name="username" label=label value=login.username!'' /> <#if messagesPerField.existsError('username')> diff --git a/themes/src/main/resources/theme/keycloak.v2/login/login.ftl b/themes/src/main/resources/theme/keycloak.v2/login/login.ftl index 4bf7adf012..7605cd967c 100755 --- a/themes/src/main/resources/theme/keycloak.v2/login/login.ftl +++ b/themes/src/main/resources/theme/keycloak.v2/login/login.ftl @@ -15,7 +15,7 @@ <#assign label> <#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")} - <@field.input name="username" label=label value="${(login.username!'')}" /> + <@field.input name="username" label=label value=login.username!'' /> <@field.password name="password" label=msg("password") forgotPassword=realm.resetPasswordAllowed/> @@ -42,7 +42,7 @@
<#elseif section = "socialProviders" > - <#if realm.password && social?? && social.providers?has_content> + <#if realm.password && social.providers?? && social.providers?has_content> - +
@@ -181,11 +181,11 @@ <#if auth?has_content && auth.showTryAnotherWayLink()>
- + + + ${kcSanitize(msg("doTryAnotherWay"))?no_esc} +
diff --git a/themes/src/main/resources/theme/keycloak.v2/login/terms.ftl b/themes/src/main/resources/theme/keycloak.v2/login/terms.ftl new file mode 100755 index 0000000000..d92daaf385 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/terms.ftl @@ -0,0 +1,21 @@ +<#import "template.ftl" as layout> +<#import "buttons.ftl" as buttons> + +<@layout.registrationLayout displayMessage=false; section> + + + <#if section = "header"> + ${msg("termsTitle")} + <#elseif section = "form"> +
+ ${kcSanitize(msg("termsText"))?no_esc} +
+
+ <@buttons.actionGroup> + <@buttons.button name="accept" id="kc-accept" label="doAccept" class=["kcButtonPrimaryClass"]/> + <@buttons.button name="cancel" id="kc-decline" label="doDecline" class=["kcButtonSecondaryClass"]/> + +
+
+ + diff --git a/themes/src/main/resources/theme/keycloak.v2/login/theme.properties b/themes/src/main/resources/theme/keycloak.v2/login/theme.properties index 6dba6fbd81..8f1c36ee36 100644 --- a/themes/src/main/resources/theme/keycloak.v2/login/theme.properties +++ b/themes/src/main/resources/theme/keycloak.v2/login/theme.properties @@ -44,6 +44,7 @@ kcListClass=pf-v5-c-list kcButtonClass=pf-v5-c-button kcButtonPrimaryClass=pf-v5-c-button pf-m-primary +kcButtonSecondaryClass=pf-v5-c-button pf-m-secondary kcButtonBlockClass=pf-m-block kcButtonLinkClass=pf-v5-c-button pf-m-link kcCommonLogoIdP=pf-v5-c-login__main-footer-links-item @@ -79,3 +80,11 @@ kcLoginMainBody=pf-v5-c-login__main-body kcContentWrapperClass=pf-v5-u-mb-md-on-md kcWebAuthnDefaultIcon=pf-v5-c-icon pf-m-lg +kcMarginTopClass=pf-v5-u-mt-md-on-md + +kcLoginOTPListClass=pf-v5-c-tile +kcLoginOTPListItemHeaderClass=pf-v5-c-tile__header pf-m-stacked +kcLoginOTPListItemIconBodyClass=pf-v5-c-tile__icon +kcLoginOTPListItemIconClass=fa fa-mobile +kcLoginOTPListItemTitleClass=pf-v5-c-tile__title +kcLoginOTPListSelectedClass=pf-m-selected \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak.v2/login/user-profile-commons.ftl b/themes/src/main/resources/theme/keycloak.v2/login/user-profile-commons.ftl index 763a0df447..390474fbe5 100644 --- a/themes/src/main/resources/theme/keycloak.v2/login/user-profile-commons.ftl +++ b/themes/src/main/resources/theme/keycloak.v2/login/user-profile-commons.ftl @@ -127,28 +127,48 @@ <#macro selectTag attribute> - disabled + <#if attribute.annotations.inputType=='multiselect'>multiple + <#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}" + > + <#if attribute.annotations.inputType=='select'> + + - <#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??> - <#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options> - <#elseif attribute.validators.options?? && attribute.validators.options.options??> - <#assign options=attribute.validators.options.options> - <#else> - <#assign options=[]> - + <#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??> + <#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options> + <#elseif attribute.validators.options?? && attribute.validators.options.options??> + <#assign options=attribute.validators.options.options> + <#else> + <#assign options=[]> + - <#list options as option> - - - + <#list options as option> + + + + + + + + + <#macro inputTagSelects attribute> diff --git a/themes/src/main/resources/theme/keycloak.v2/login/webauthn-register.ftl b/themes/src/main/resources/theme/keycloak.v2/login/webauthn-register.ftl new file mode 100644 index 0000000000..7020045dd3 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak.v2/login/webauthn-register.ftl @@ -0,0 +1,61 @@ +<#import "template.ftl" as layout> +<#import "password-commons.ftl" as passwordCommons> +<#import "buttons.ftl" as buttons> + +<@layout.registrationLayout; section> + <#if section = "title"> + title + <#elseif section = "header"> + + ${kcSanitize(msg("webauthn-registration-title"))?no_esc} + <#elseif section = "form"> + +
+
+ + + + + + + <@passwordCommons.logoutOtherSessions/> +
+
+ + + +
+ <@buttons.actionGroup> + <@buttons.button id="registerWebAuthn" label="doRegisterSecurityKey" /> + <#if !isSetRetry?has_content && isAppInitiatedAction?has_content> +
+ <@buttons.button id="cancelWebAuthnAIA" name="cancel-aia" label="doCancel" class=["kcButtonSecondaryClass"]/> +
+ + +
+ +