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 <erikjan.dewit@gmail.com> * fixed grant screen Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * test fixes Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix for code.ftl Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * test fixes Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
125124c2d9
commit
9aad6f650d
28 changed files with 438 additions and 158 deletions
|
@ -12,7 +12,7 @@ public class DeleteAccountActionConfirmPage extends RequiredActions {
|
||||||
@FindBy(css = "button[name='cancel-aia']")
|
@FindBy(css = "button[name='cancel-aia']")
|
||||||
WebElement cancelActionButton;
|
WebElement cancelActionButton;
|
||||||
|
|
||||||
@FindBy(css = "input[type='submit']")
|
@FindBy(css = "button[type='submit']")
|
||||||
WebElement confirmActionButton;
|
WebElement confirmActionButton;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -36,10 +36,10 @@ public class LoginActions extends LoginBase {
|
||||||
.path("login-actions");
|
.path("login-actions");
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(css = "input[type='submit']")
|
@FindBy(css = "button[type='submit']")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
@FindBy(css = "button[type='submit']")
|
@FindBy(css = "button[name='cancel-aia']")
|
||||||
private WebElement cancelButton;
|
private WebElement cancelButton;
|
||||||
|
|
||||||
public void submit() {
|
public void submit() {
|
||||||
|
|
|
@ -34,10 +34,10 @@ import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||||
*/
|
*/
|
||||||
public class OAuthGrant extends RequiredActions {
|
public class OAuthGrant extends RequiredActions {
|
||||||
@FindBy(css = "input[name=\"accept\"]")
|
@FindBy(css = "button[name=\"accept\"]")
|
||||||
private WebElement acceptButton;
|
private WebElement acceptButton;
|
||||||
|
|
||||||
@FindBy(css = "input[name=\"cancel\"]")
|
@FindBy(css = "button[name=\"cancel\"]")
|
||||||
private WebElement cancelButton;
|
private WebElement cancelButton;
|
||||||
|
|
||||||
@FindBy(xpath = "//div[@id='kc-oauth']/ul/li/span")
|
@FindBy(xpath = "//div[@id='kc-oauth']/ul/li/span")
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class OneTimeCode extends Authenticate {
|
||||||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
||||||
private WebElement loginErrorMessage;
|
private WebElement loginErrorMessage;
|
||||||
|
|
||||||
@FindBy(id = "input-error-otp-code")
|
@FindBy(id = "input-error-otp")
|
||||||
private WebElement totpInputCodeError;
|
private WebElement totpInputCodeError;
|
||||||
|
|
||||||
public String getOtpLabel() {
|
public String getOtpLabel() {
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class LoginPasswordUpdatePage {
|
||||||
@FindBy(id = "password-confirm")
|
@FindBy(id = "password-confirm")
|
||||||
private WebElement passwordConfirmInput;
|
private WebElement passwordConfirmInput;
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
@FindBy(css = "button[type=\"submit\"]")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class LoginPasswordResetPage extends LanguageComboboxAwarePage {
|
||||||
@FindBy(id = "input-error-username")
|
@FindBy(id = "input-error-username")
|
||||||
private WebElement usernameError;
|
private WebElement usernameError;
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
@FindBy(css = "button[type=\"submit\"]")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
@FindBy(className = "pf-v5-c-success")
|
@FindBy(className = "pf-v5-c-success")
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class LoginPasswordUpdatePage extends LogoutSessionsPage {
|
||||||
@FindBy(id = "password-confirm")
|
@FindBy(id = "password-confirm")
|
||||||
private WebElement passwordConfirmInput;
|
private WebElement passwordConfirmInput;
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
@FindBy(css = "button[type=\"submit\"]")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
@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")
|
@FindBy(className = "kc-feedback-text")
|
||||||
private WebElement feedbackMessage;
|
private WebElement feedbackMessage;
|
||||||
|
|
||||||
@FindBy(name = "cancel-aia")
|
@FindBy(name = "cancel-aia")
|
||||||
private WebElement cancelAIAButton;
|
private WebElement cancelAIAButton;
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public class LoginPasswordUpdatePage extends LogoutSessionsPage {
|
||||||
|
|
||||||
submitButton.click();
|
submitButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
cancelAIAButton.click();
|
cancelAIAButton.click();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.keycloak.common.util.Retry;
|
import org.keycloak.common.util.Retry;
|
||||||
import org.keycloak.testsuite.util.UIUtils;
|
import org.keycloak.testsuite.util.UIUtils;
|
||||||
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.NoSuchElementException;
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
|
@ -38,13 +39,13 @@ public class LoginTotpPage extends LanguageComboboxAwarePage {
|
||||||
@FindBy(id = "password-token")
|
@FindBy(id = "password-token")
|
||||||
private WebElement passwordToken;
|
private WebElement passwordToken;
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
@FindBy(css = "button[type=\"submit\"]")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
||||||
private WebElement loginErrorMessage;
|
private WebElement loginErrorMessage;
|
||||||
|
|
||||||
@FindBy(id = "input-error-otp-code")
|
@FindBy(id = "input-error-otp")
|
||||||
private WebElement totpInputCodeError;
|
private WebElement totpInputCodeError;
|
||||||
|
|
||||||
public void login(String totp) {
|
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
|
// 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) {
|
public void assertOtpCredentialSelectorAvailability(boolean expectedAvailability) {
|
||||||
try {
|
try {
|
||||||
driver.findElement(By.className("pf-c-tile"));
|
driver.findElement(By.className("pf-v5-c-tile"));
|
||||||
Assert.assertTrue(expectedAvailability);
|
Assert.assertTrue(expectedAvailability);
|
||||||
} catch (NoSuchElementException nse) {
|
} catch (NoSuchElementException nse) {
|
||||||
Assert.assertFalse(expectedAvailability);
|
Assert.assertFalse(expectedAvailability);
|
||||||
|
@ -113,21 +114,19 @@ public class LoginTotpPage extends LanguageComboboxAwarePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
private By getXPathForLookupAllCards() {
|
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() {
|
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) {
|
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) {
|
public void selectOtpCredential(String credentialName) {
|
||||||
waitForElement(getCssSelectorForLookupActiveCard());
|
|
||||||
|
|
||||||
WebElement webElement = driver.findElement(
|
WebElement webElement = driver.findElement(
|
||||||
getXPathForLookupCardWithName(credentialName));
|
getXPathForLookupCardWithName(credentialName));
|
||||||
UIUtils.clickLink(webElement);
|
UIUtils.clickLink(webElement);
|
||||||
|
@ -143,4 +142,4 @@ public class LoginTotpPage extends LanguageComboboxAwarePage {
|
||||||
}, 10, 10);
|
}, 10, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,9 @@ public class OAuthGrantPage extends LanguageComboboxAwarePage {
|
||||||
public static final String OFFLINE_ACCESS_CONSENT_TEXT = "Offline Access";
|
public static final String OFFLINE_ACCESS_CONSENT_TEXT = "Offline Access";
|
||||||
public static final String ROLES_CONSENT_TEXT = "User roles";
|
public static final String ROLES_CONSENT_TEXT = "User roles";
|
||||||
|
|
||||||
@FindBy(css = "input[name=\"accept\"]")
|
@FindBy(css = "button[name=\"accept\"]")
|
||||||
private WebElement acceptButton;
|
private WebElement acceptButton;
|
||||||
@FindBy(css = "input[name=\"cancel\"]")
|
@FindBy(css = "button[name=\"cancel\"]")
|
||||||
private WebElement cancelButton;
|
private WebElement cancelButton;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||||
* and other contributors as indicated by the @author tags.
|
* and other contributors as indicated by the @author tags.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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.SamlClient.Step;
|
||||||
import org.keycloak.testsuite.util.SamlClientBuilder;
|
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.UriBuilder;
|
import jakarta.ws.rs.core.UriBuilder;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
|
@ -37,12 +39,12 @@ import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class RequiredConsentBuilder implements Step {
|
public class RequiredConsentBuilder implements Step {
|
||||||
|
@ -88,18 +90,18 @@ public class RequiredConsentBuilder implements Step {
|
||||||
for (Element form : theLoginPage.getElementsByTag("form")) {
|
for (Element form : theLoginPage.getElementsByTag("form")) {
|
||||||
String method = form.attr("method");
|
String method = form.attr("method");
|
||||||
String action = form.attr("action");
|
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")) {
|
for (Element input : form.getElementsByTag("input")) {
|
||||||
if (Objects.equals(input.id(), "kc-login")) {
|
parameters.add(new BasicNameValuePair(input.attr("name"), input.val()));
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPost) {
|
if (isPost) {
|
||||||
|
|
|
@ -181,6 +181,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent());
|
Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent());
|
||||||
loginTotpPage.assertCurrent();
|
loginTotpPage.assertCurrent();
|
||||||
loginTotpPage.assertOtpCredentialSelectorAvailability(true);
|
loginTotpPage.assertOtpCredentialSelectorAvailability(true);
|
||||||
|
loginTotpPage.selectOtpCredential("first");
|
||||||
|
|
||||||
// Check that selected credential is "first"
|
// Check that selected credential is "first"
|
||||||
Assert.assertEquals("first", loginTotpPage.getSelectedOtpCredential());
|
Assert.assertEquals("first", loginTotpPage.getSelectedOtpCredential());
|
||||||
|
@ -207,6 +208,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent());
|
Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent());
|
||||||
loginTotpPage.assertCurrent();
|
loginTotpPage.assertCurrent();
|
||||||
loginTotpPage.assertOtpCredentialSelectorAvailability(true);
|
loginTotpPage.assertOtpCredentialSelectorAvailability(true);
|
||||||
|
loginTotpPage.selectOtpCredential(orderedCredentials.get(0));
|
||||||
|
|
||||||
// Check that preferred credential is selected
|
// Check that preferred credential is selected
|
||||||
Assert.assertEquals(orderedCredentials.get(0), loginTotpPage.getSelectedOtpCredential());
|
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
|
// A conditional flow without conditional authenticator should automatically be disabled
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testFlowDisabledWhenConditionalAuthenticatorIsMissing() {
|
public void testFlowDisabledWhenConditionalAuthenticatorIsMissing() {
|
||||||
try {
|
try {
|
||||||
configureBrowserFlowWithConditionalSubFlowHavingConditionalAuthenticator("browser - non missing conditional authenticator", true);
|
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
|
// A conditional flow with disabled conditional authenticator should automatically be disabled
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testFlowDisabledWhenConditionalAuthenticatorIsDisabled() {
|
public void testFlowDisabledWhenConditionalAuthenticatorIsDisabled() {
|
||||||
try {
|
try {
|
||||||
configureBrowserFlowWithConditionalSubFlowHavingDisabledConditionalAuthenticator("browser - disabled conditional authenticator");
|
configureBrowserFlowWithConditionalSubFlowHavingDisabledConditionalAuthenticator("browser - disabled conditional authenticator");
|
||||||
|
@ -321,7 +323,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
// Configure a conditional authenticator in a non-conditional sub-flow
|
// 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
|
// In such case, the flow is evaluated and the conditional authenticator is considered as disabled
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testConditionalAuthenticatorInNonConditionalFlow() {
|
public void testConditionalAuthenticatorInNonConditionalFlow() {
|
||||||
try {
|
try {
|
||||||
configureBrowserFlowWithConditionalAuthenticatorInNonConditionalFlow();
|
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-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
|
// user-with-one-configured-otp does not have the role. He should not be asked for an OTP code
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testConditionalRoleAuthenticator() {
|
public void testConditionalRoleAuthenticator() {
|
||||||
String requiredRole = "user";
|
String requiredRole = "user";
|
||||||
// A browser flow is configured with an OTPForm for users having the role "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-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
|
// user-with-one-configured-otp does not have the role. He should not be asked for an OTP code
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testConditionalRoleAuthenticatorWithRealmRoleIncludedInCompositeRealmRole() {
|
public void testConditionalRoleAuthenticatorWithRealmRoleIncludedInCompositeRealmRole() {
|
||||||
|
|
||||||
// Create composite-realm-role-1
|
// 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-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
|
// user-with-one-configured-otp does not have the role. He should not be asked for an OTP code
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testConditionalRoleAuthenticatorWithClientRoleIncludedInCompositeClientRole() {
|
public void testConditionalRoleAuthenticatorWithClientRoleIncludedInCompositeClientRole() {
|
||||||
|
|
||||||
String clientName = "test-app";
|
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
|
// 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
|
// In such case, all the required authenticator inside the subflow should be evaluated even if the condition has changed
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testConditionalAuthenticatorWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation() {
|
public void testConditionalAuthenticatorWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation() {
|
||||||
try {
|
try {
|
||||||
configureBrowserFlowWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation();
|
configureBrowserFlowWithConditionalSubFlowWithChangingConditionWhileFlowEvaluation();
|
||||||
|
@ -595,7 +597,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testSwitchExecutionNotAllowedWithRequiredPasswordAndAlternativeOTP() {
|
public void testSwitchExecutionNotAllowedWithRequiredPasswordAndAlternativeOTP() {
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithRequiredPasswordFormAndAlternativeOTP(newFlowAlias);
|
configureBrowserFlowWithRequiredPasswordFormAndAlternativeOTP(newFlowAlias);
|
||||||
|
@ -631,7 +633,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testSocialProvidersPresentOnLoginUsernameOnlyPageIfConfigured() {
|
public void testSocialProvidersPresentOnLoginUsernameOnlyPageIfConfigured() {
|
||||||
String testRealm = "test";
|
String testRealm = "test";
|
||||||
// Test setup - Configure the testing Keycloak instance with UsernameForm & PasswordForm (both REQUIRED) and OTPFormAuthenticator (ALTERNATIVE)
|
// 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
|
@Test
|
||||||
|
|
||||||
public void testConditionalFlowWithConditionalAuthenticatorEvaluatingToFalseActsAsDisabled(){
|
public void testConditionalFlowWithConditionalAuthenticatorEvaluatingToFalseActsAsDisabled(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithConditionalFlowWithOTP(newFlowAlias);
|
configureBrowserFlowWithConditionalFlowWithOTP(newFlowAlias);
|
||||||
|
@ -707,7 +709,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testConditionalFlowWithConditionalAuthenticatorEvaluatingToTrueActsAsRequired(){
|
public void testConditionalFlowWithConditionalAuthenticatorEvaluatingToTrueActsAsRequired(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithConditionalFlowWithOTP(newFlowAlias);
|
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
|
* In this test the user is expected to have to log in with OTP
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testConditionalFlowWithMultipleConditionalAuthenticatorsWithUserWithRoleAndOTP() {
|
public void testConditionalFlowWithMultipleConditionalAuthenticatorsWithUserWithRoleAndOTP() {
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithConditionalFlowWithMultipleConditionalAuthenticators(newFlowAlias);
|
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
|
* 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
|
@Test
|
||||||
|
|
||||||
public void testConditionalFlowWithMultipleConditionalAuthenticatorsWithUserWithRoleButNotOTP() {
|
public void testConditionalFlowWithMultipleConditionalAuthenticatorsWithUserWithRoleButNotOTP() {
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithConditionalFlowWithMultipleConditionalAuthenticators(newFlowAlias);
|
configureBrowserFlowWithConditionalFlowWithMultipleConditionalAuthenticators(newFlowAlias);
|
||||||
|
@ -866,7 +868,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
* and will instead raise an credential setup required error.
|
* and will instead raise an credential setup required error.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testLoginWithWithNoOTPCredentialAndNoRequiredActionProviderRegistered(){
|
public void testLoginWithWithNoOTPCredentialAndNoRequiredActionProviderRegistered(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithRequiredOTP(newFlowAlias);
|
configureBrowserFlowWithRequiredOTP(newFlowAlias);
|
||||||
|
@ -893,7 +895,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
* and will instead raise an credential setup required error.
|
* and will instead raise an credential setup required error.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testLoginWithWithNoOTPCredentialAndRequiredActionProviderDisabled(){
|
public void testLoginWithWithNoOTPCredentialAndRequiredActionProviderDisabled(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithRequiredOTP(newFlowAlias);
|
configureBrowserFlowWithRequiredOTP(newFlowAlias);
|
||||||
|
@ -918,7 +920,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
* has its requiredActionProvider enabled, than it will login and show the otpSetup page.
|
* has its requiredActionProvider enabled, than it will login and show the otpSetup page.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testLoginWithWithNoOTPCredential(){
|
public void testLoginWithWithNoOTPCredential(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithRequiredOTP(newFlowAlias);;
|
configureBrowserFlowWithRequiredOTP(newFlowAlias);;
|
||||||
|
@ -961,7 +963,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
* and will instead raise an credential setup required error.
|
* and will instead raise an credential setup required error.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testLoginWithWithNoWebAuthnCredentialAndRequiredActionProviderDisabled(){
|
public void testLoginWithWithNoWebAuthnCredentialAndRequiredActionProviderDisabled(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithRequiredWebAuthn(newFlowAlias);
|
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.
|
* has its requiredActionProvider enabled, then it will login and show the WebAuthn registration page.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testLoginWithWithNoWebAuthnCredential(){
|
public void testLoginWithWithNoWebAuthnCredential(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithRequiredWebAuthn(newFlowAlias);
|
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)
|
* 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
|
@Test
|
||||||
|
|
||||||
public void testLoginWithWithNoOTPCredentialAndAlternativeActionProvider(){
|
public void testLoginWithWithNoOTPCredentialAndAlternativeActionProvider(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithAlternativeOTPAndPassword(newFlowAlias);
|
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.
|
* This test checks the error messages, when the credentials are invalid and UsernameForm and PasswordForm are separated.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testLoginMultiFactorWithWrongCredentialsMessage() {
|
public void testLoginMultiFactorWithWrongCredentialsMessage() {
|
||||||
UserRepresentation user = testRealm().users().search("test-user@localhost").get(0);
|
UserRepresentation user = testRealm().users().search("test-user@localhost").get(0);
|
||||||
Assert.assertNotNull(user);
|
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
|
* then it will not try to create the required action, and will instead move to the next alternative
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
||||||
public void testLoginWithWithNoWebAuthnCredentialAndAlternativeActionProvider(){
|
public void testLoginWithWithNoWebAuthnCredentialAndAlternativeActionProvider(){
|
||||||
String newFlowAlias = "browser - copy 1";
|
String newFlowAlias = "browser - copy 1";
|
||||||
configureBrowserFlowWithAlternativeWebAuthnAndPassword(newFlowAlias);
|
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
|
* 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
|
@Test
|
||||||
|
|
||||||
public void testLoginWithAlternativeOTPAndConditionalPassword(){
|
public void testLoginWithAlternativeOTPAndConditionalPassword(){
|
||||||
String newFlowAlias = "browser - copy 2";
|
String newFlowAlias = "browser - copy 2";
|
||||||
configureBrowserFlowWithAlternativeOTPAndConditionalPassword(newFlowAlias);
|
configureBrowserFlowWithAlternativeOTPAndConditionalPassword(newFlowAlias);
|
||||||
|
|
|
@ -1370,7 +1370,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
||||||
newPassword.sendKeys("resetPassword");
|
newPassword.sendKeys("resetPassword");
|
||||||
final WebElement confirmPassword = driver.findElement(By.id("password-confirm"));
|
final WebElement confirmPassword = driver.findElement(By.id("password-confirm"));
|
||||||
confirmPassword.sendKeys("resetPassword");
|
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();
|
submit.click();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,17 @@
|
||||||
<div class="${properties.kcFormActionGroupClass}">
|
<div class="${properties.kcFormActionGroupClass}">
|
||||||
<#nested>
|
<#nested>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
<#macro button id name label class=["kcButtonPrimaryClass"]>
|
<#macro button label id="" name="" class=["kcButtonPrimaryClass"]>
|
||||||
<button class="<#list class as c>${properties[c]} </#list>" name="${name}" id="${id}" type="submit">${msg(label)}</button>
|
<button class="<#list class as c>${properties[c]} </#list>" name="${name}" id="${id}" type="submit">${msg(label)}</button>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
|
<#macro buttonLink href label id="" class=["kcButtonSecondaryClass"]>
|
||||||
|
<a id="${id}" href="${href}" class="<#list class as c>${properties[c]} </#list>">${kcSanitize(msg(label))?no_esc}</a>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
<#macro loginButton>
|
<#macro loginButton>
|
||||||
<@buttons.actionGroup>
|
<@buttons.actionGroup>
|
||||||
<@buttons.button id="kc-login" name="login" label="doLogIn" class=["kcButtonPrimaryClass", "kcButtonBlockClass"] />
|
<@buttons.button id="kc-login" name="login" label="doLogIn" class=["kcButtonPrimaryClass", "kcButtonBlockClass"] />
|
||||||
|
|
20
themes/src/main/resources/theme/keycloak.v2/login/code.ftl
Executable file
20
themes/src/main/resources/theme/keycloak.v2/login/code.ftl
Executable file
|
@ -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))}
|
||||||
|
</#if>
|
||||||
|
<#elseif section = "form">
|
||||||
|
<div id="kc-code">
|
||||||
|
<#if code.success>
|
||||||
|
<p>${msg("copyCodeInstruction")}</p>
|
||||||
|
<@field.input name="code" label="" value=code.code />
|
||||||
|
<#else>
|
||||||
|
<p id="error">${kcSanitize(code.error)}</p>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<#import "buttons.ftl" as buttons>
|
||||||
|
|
||||||
|
<@layout.registrationLayout; section>
|
||||||
|
<!-- template: delete-account-confirm.ftl -->
|
||||||
|
|
||||||
|
<#if section = "header">
|
||||||
|
${msg("deleteAccountConfirm")}
|
||||||
|
|
||||||
|
<#elseif section = "form">
|
||||||
|
|
||||||
|
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-deleteaccount-form" method="post">
|
||||||
|
|
||||||
|
<div class="${properties.kcAlertClass!} pf-m-warning">
|
||||||
|
<div class="${properties.kcAlertIconClass!}">
|
||||||
|
<i class="${properties.kcFeedbackWarningIcon!}" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
<span class="${properties.kcAlertTitleClass!}">
|
||||||
|
${msg("irreversibleAction")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>${msg("deletingImplies")}</p>
|
||||||
|
<ul class="pf-v5-c-list" role="list">
|
||||||
|
<li>${msg("loggingOutImmediately")}</li>
|
||||||
|
<li>${msg("errasingData")}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p class="delete-account-text">${msg("finalDeletionConfirmation")}</p>
|
||||||
|
|
||||||
|
<@buttons.actionGroup>
|
||||||
|
<@buttons.button label="doConfirmDelete" class=["kcButtonPrimaryClass"]/>
|
||||||
|
<#if triggered_from_aia>
|
||||||
|
<@buttons.button name="cancel-aia" label="doCancel" class=["kcButtonSecondaryClass"]/>
|
||||||
|
</#if>
|
||||||
|
</@buttons.actionGroup>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
</@layout.registrationLayout>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<#import "buttons.ftl" as buttons>
|
||||||
|
|
||||||
|
<@layout.registrationLayout displayMessage=false; section>
|
||||||
|
<!-- template: delete-credential.ftl -->
|
||||||
|
|
||||||
|
<#if section = "header">
|
||||||
|
${msg("deleteCredentialTitle", credentialLabel)}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<div id="kc-delete-text" class="${properties.kcContentWrapperClass!}">
|
||||||
|
${msg("deleteCredentialMessage", credentialLabel)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="${properties.kcFormClass!}" action="${url.loginAction}" method="POST">
|
||||||
|
<@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"]/>
|
||||||
|
</@buttons.actionGroup>
|
||||||
|
</form
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -41,19 +41,19 @@
|
||||||
</#if>
|
</#if>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
<#macro input name label value="" required=false>
|
<#macro input name label value="" required=false autocomplete="off" fieldName=name>
|
||||||
<#assign error=kcSanitize(messagesPerField.get(name))?no_esc>
|
<#assign error=kcSanitize(messagesPerField.get(fieldName))?no_esc>
|
||||||
<@field.group name=name label=label error=error required=required>
|
<@field.group name=name label=label error=error required=required>
|
||||||
<span class="${properties.kcInputClass} <#if error?has_content>${properties.kcError}</#if>">
|
<span class="${properties.kcInputClass} <#if error?has_content>${properties.kcError}</#if>">
|
||||||
<input id="${name}" name="${name}" value="${value}" type="text" autocomplete="off"
|
<input id="${name}" name="${name}" value="${value}" type="text" autocomplete="${autocomplete}"
|
||||||
aria-invalid="<#if error?has_content>true</#if>"/>
|
aria-invalid="<#if error?has_content>true</#if>"/>
|
||||||
<@errorIcon error=error/>
|
<@errorIcon error=error/>
|
||||||
</span>
|
</span>
|
||||||
</@field.group>
|
</@field.group>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
<#macro password name label value="" required=false forgotPassword=false>
|
<#macro password name label value="" required=false forgotPassword=false fieldName=name>
|
||||||
<#assign error=kcSanitize(messagesPerField.get(name))?no_esc>
|
<#assign error=kcSanitize(messagesPerField.get(fieldName))?no_esc>
|
||||||
<@field.group name=name label=label error=error required=required>
|
<@field.group name=name label=label error=error required=required>
|
||||||
<div class="${properties.kcInputGroup}">
|
<div class="${properties.kcInputGroup}">
|
||||||
<div class="${properties.kcInputGroupItemClass} ${properties.kcFill}">
|
<div class="${properties.kcInputGroupItemClass} ${properties.kcFill}">
|
||||||
|
|
59
themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl
Executable file
59
themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl
Executable file
|
@ -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??>
|
||||||
|
<img src="${client.attributes.logoUri}"/>
|
||||||
|
</#if>
|
||||||
|
<p>
|
||||||
|
<#if client.name?has_content>
|
||||||
|
${msg("oauthGrantTitle",advancedMsg(client.name))}
|
||||||
|
<#else>
|
||||||
|
${msg("oauthGrantTitle",client.clientId)}
|
||||||
|
</#if>
|
||||||
|
</p>
|
||||||
|
<#elseif section = "form">
|
||||||
|
<div id="kc-oauth" class="content-area">
|
||||||
|
<h3>${msg("oauthGrantRequest")}</h3>
|
||||||
|
<ul class="${properties.kcListClass!}">
|
||||||
|
<#if oauth.clientScopesRequested??>
|
||||||
|
<#list oauth.clientScopesRequested as clientScope>
|
||||||
|
<li>
|
||||||
|
<span><#if !clientScope.dynamicScopeParameter??>
|
||||||
|
${advancedMsg(clientScope.consentScreenText)}
|
||||||
|
<#else>
|
||||||
|
${advancedMsg(clientScope.consentScreenText)}: <b>${clientScope.dynamicScopeParameter}</b>
|
||||||
|
</#if>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
|
</ul>
|
||||||
|
<#if client.attributes.policyUri?? || client.attributes.tosUri??>
|
||||||
|
<h3>
|
||||||
|
<#if client.name?has_content>
|
||||||
|
${msg("oauthGrantInformation",advancedMsg(client.name))}
|
||||||
|
<#else>
|
||||||
|
${msg("oauthGrantInformation",client.clientId)}
|
||||||
|
</#if>
|
||||||
|
<#if client.attributes.tosUri??>
|
||||||
|
${msg("oauthGrantReview")}
|
||||||
|
<a href="${client.attributes.tosUri}" target="_blank">${msg("oauthGrantTos")}</a>
|
||||||
|
</#if>
|
||||||
|
<#if client.attributes.policyUri??>
|
||||||
|
${msg("oauthGrantReview")}
|
||||||
|
<a href="${client.attributes.policyUri}" target="_blank">${msg("oauthGrantPolicy")}</a>
|
||||||
|
</#if>
|
||||||
|
</h3>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<form class="${properties.kcFormClass} ${properties.kcMarginTopClass!}" action="${url.oauthAction}" method="POST">
|
||||||
|
<input type="hidden" name="code" value="${oauth.code}">
|
||||||
|
<@buttons.actionGroup>
|
||||||
|
<@buttons.button id="kc-login" name="accept" label="doYes"/>
|
||||||
|
<@buttons.button id="kc-cancel" name="cancel" label="doNo" class=["kcButtonSecondaryClass"]/>
|
||||||
|
</@buttons.actionGroup>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
45
themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl
Executable file
45
themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl
Executable file
|
@ -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>
|
||||||
|
<!-- template: login-otp.ftl -->
|
||||||
|
|
||||||
|
<#if section="header">
|
||||||
|
${msg("doLogIn")}
|
||||||
|
<#elseif section="form">
|
||||||
|
<form id="kc-otp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||||
|
<input id="selectedCredentialId" type="hidden" name="selectedCredentialId" value="${otpLogin.selectedCredentialId!''}">
|
||||||
|
<#if otpLogin.userOtpCredentials?size gt 1>
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<#list otpLogin.userOtpCredentials as otpCredential>
|
||||||
|
<div id="kc-otp-credential-${otpCredential?index}" class="${properties.kcLoginOTPListClass!}"
|
||||||
|
onclick="toggleOTP(${otpCredential?index}, '${otpCredential.id}')">
|
||||||
|
<span class="${properties.kcLoginOTPListItemHeaderClass!}">
|
||||||
|
<span class="${properties.kcLoginOTPListItemIconBodyClass!}">
|
||||||
|
<i class="${properties.kcLoginOTPListItemIconClass!}" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
<span class="${properties.kcLoginOTPListItemTitleClass!}">${otpCredential.userLabel}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</#list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<@field.input name="otp" label=msg("loginOtpOneTime") autocomplete="one-time-code" fieldName="totp" />
|
||||||
|
|
||||||
|
<@buttons.loginButton />
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
function toggleOTP(index, value) {
|
||||||
|
// select the clicked OTP credential
|
||||||
|
document.getElementById("selectedCredentialId").value = value;
|
||||||
|
// remove selected class from all OTP credentials
|
||||||
|
Array.from(document.getElementsByClassName("${properties.kcLoginOTPListSelectedClass!}")).map(i => i.classList.remove("${properties.kcLoginOTPListSelectedClass!}"));
|
||||||
|
// add selected class to the clicked OTP credential
|
||||||
|
document.getElementById("kc-otp-credential-" + index).classList.add("${properties.kcLoginOTPListSelectedClass!}");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -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">
|
||||||
|
<form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||||
|
<#assign label>
|
||||||
|
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
||||||
|
</#assign>
|
||||||
|
<@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"]/>
|
||||||
|
</@buttons.actionGroup>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
<#elseif section = "info" >
|
||||||
|
<#if realm.duplicateEmailsAllowed>
|
||||||
|
${msg("emailInstructionUsername")}
|
||||||
|
<#else>
|
||||||
|
${msg("emailInstruction")}
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -1,79 +1,28 @@
|
||||||
<#import "template.ftl" as layout>
|
<#import "template.ftl" as layout>
|
||||||
<#import "password-commons.ftl" as passwordCommons>
|
<#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>
|
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section>
|
||||||
|
<!-- template: login-update-password.ftl -->
|
||||||
<#if section = "header">
|
<#if section = "header">
|
||||||
${msg("updatePasswordTitle")}
|
${msg("updatePasswordTitle")}
|
||||||
<#elseif section = "form">
|
<#elseif section = "form">
|
||||||
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post" novalidate="novalidate">
|
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post" novalidate="novalidate">
|
||||||
<div class="${properties.kcFormGroupClass!}">
|
<@field.password name="password-new" label=msg("passwordNew") fieldName="password" />
|
||||||
<label for="password-new" class="${properties.kcLabelClass!}">
|
<@field.password name="password-confirm" label=msg("passwordConfirm") />
|
||||||
<span class="pf-v5-c-form__label-text">
|
|
||||||
${msg("passwordNew")}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="${properties.kcInputGroup!}">
|
|
||||||
<span class="${properties.kcInputClass!}" dir="ltr">
|
|
||||||
<input type="password" id="password-new" name="password-new" autofocus autocomplete="new-password"
|
|
||||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
|
||||||
aria-controls="password-new" data-password-toggle
|
|
||||||
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
|
||||||
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
|
||||||
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<#if messagesPerField.existsError('password')>
|
|
||||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
|
||||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
|
||||||
</span>
|
|
||||||
</#if>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="${properties.kcFormGroupClass!}">
|
|
||||||
<label for="password-confirm" class="${properties.kcLabelClass!}">
|
|
||||||
<span class="pf-v5-c-form__label-text">
|
|
||||||
${msg("passwordConfirm")}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="${properties.kcInputGroup!}">
|
|
||||||
<span class="${properties.kcInputClass!}" dir="ltr">
|
|
||||||
<input type="password" id="password-confirm" name="password-confirm"
|
|
||||||
autocomplete="new-password"
|
|
||||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
|
||||||
aria-controls="password-confirm" data-password-toggle
|
|
||||||
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
|
||||||
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
|
||||||
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<#if messagesPerField.existsError('password-confirm')>
|
|
||||||
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
|
||||||
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
|
||||||
</span>
|
|
||||||
</#if>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="${properties.kcFormGroupClass!}">
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
<@passwordCommons.logoutOtherSessions/>
|
<@passwordCommons.logoutOtherSessions/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!} pf-m-action">
|
<@buttons.actionGroup>
|
||||||
<div class="pf-v5-c-form__actions">
|
<#if isAppInitiatedAction??>
|
||||||
<#if isAppInitiatedAction??>
|
<@buttons.button label="doSubmit" class=["kcButtonPrimaryClass"]/>
|
||||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
<@buttons.button label="doCancel" name="cancel-aia" class=["kcButtonSecondaryClass"]/>
|
||||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button>
|
<#else>
|
||||||
<#else>
|
<@buttons.button label="doSubmit" class=["kcButtonPrimaryClass", "kcButtonBlockClass"]/>
|
||||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
</#if>
|
||||||
</#if>
|
</@buttons.actionGroup>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</#if>
|
</#if>
|
||||||
</@layout.registrationLayout>
|
</@layout.registrationLayout>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<#assign label>
|
<#assign label>
|
||||||
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
||||||
</#assign>
|
</#assign>
|
||||||
<@field.input name="username" label=label value="${(login.username!'')}" />
|
<@field.input name="username" label=label value=login.username!'' />
|
||||||
|
|
||||||
<#if messagesPerField.existsError('username')>
|
<#if messagesPerField.existsError('username')>
|
||||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<#assign label>
|
<#assign label>
|
||||||
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
||||||
</#assign>
|
</#assign>
|
||||||
<@field.input name="username" label=label value="${(login.username!'')}" />
|
<@field.input name="username" label=label value=login.username!'' />
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<@field.password name="password" label=msg("password") forgotPassword=realm.resetPasswordAllowed/>
|
<@field.password name="password" label=msg("password") forgotPassword=realm.resetPasswordAllowed/>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
<#elseif section = "socialProviders" >
|
<#elseif section = "socialProviders" >
|
||||||
<#if realm.password && social?? && social.providers?has_content>
|
<#if realm.password && social.providers?? && social.providers?has_content>
|
||||||
<div class="pf-v5-c-login__main-footer-band">
|
<div class="pf-v5-c-login__main-footer-band">
|
||||||
<p class="pf-v5-c-login__main-footer-band-item">
|
<p class="pf-v5-c-login__main-footer-band-item">
|
||||||
${msg("identity-provider-login-label")}
|
${msg("identity-provider-login-label")}
|
||||||
|
|
|
@ -173,7 +173,7 @@
|
||||||
<#if message.type = 'error'><span class="${properties.kcFeedbackErrorIcon!}"></span></#if>
|
<#if message.type = 'error'><span class="${properties.kcFeedbackErrorIcon!}"></span></#if>
|
||||||
<#if message.type = 'info'><span class="${properties.kcFeedbackInfoIcon!}"></span></#if>
|
<#if message.type = 'info'><span class="${properties.kcFeedbackInfoIcon!}"></span></#if>
|
||||||
</div>
|
</div>
|
||||||
<span class="${properties.kcAlertTitleClass!} kc-feedback-text">${kcSanitize(message.summary)?no_esc}</span>
|
<span class="${properties.kcAlertTitleClass!} kc-feedback-text">${kcSanitize(message.summary)?no_esc}</span>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
|
@ -181,11 +181,11 @@
|
||||||
|
|
||||||
<#if auth?has_content && auth.showTryAnotherWayLink()>
|
<#if auth?has_content && auth.showTryAnotherWayLink()>
|
||||||
<form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post" novalidate="novalidate">
|
<form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post" novalidate="novalidate">
|
||||||
<div class="${properties.kcFormGroupClass!}">
|
<input type="hidden" name="tryAnotherWay" value="on"/>
|
||||||
<input type="hidden" name="tryAnotherWay" value="on"/>
|
<a id="try-another-way" href="javascript:document.forms['kc-select-try-another-way-form'].submit()"
|
||||||
<a href="#" id="try-another-way"
|
class="${properties.kcButtonSecondaryClass} ${properties.kcButtonBlockClass} ${properties.kcMarginTopClass}">
|
||||||
onclick="document.forms['kc-select-try-another-way-form'].submit();return false;">${msg("doTryAnotherWay")}</a>
|
${kcSanitize(msg("doTryAnotherWay"))?no_esc}
|
||||||
</div>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
|
|
21
themes/src/main/resources/theme/keycloak.v2/login/terms.ftl
Executable file
21
themes/src/main/resources/theme/keycloak.v2/login/terms.ftl
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<#import "buttons.ftl" as buttons>
|
||||||
|
|
||||||
|
<@layout.registrationLayout displayMessage=false; section>
|
||||||
|
<!-- template: terms.ftl -->
|
||||||
|
|
||||||
|
<#if section = "header">
|
||||||
|
${msg("termsTitle")}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<div class="${properties.kcContentWrapperClass}">
|
||||||
|
${kcSanitize(msg("termsText"))?no_esc}
|
||||||
|
</div>
|
||||||
|
<form class="${properties.kcFormClass!}" action="${url.loginAction}" method="POST">
|
||||||
|
<@buttons.actionGroup>
|
||||||
|
<@buttons.button name="accept" id="kc-accept" label="doAccept" class=["kcButtonPrimaryClass"]/>
|
||||||
|
<@buttons.button name="cancel" id="kc-decline" label="doDecline" class=["kcButtonSecondaryClass"]/>
|
||||||
|
</@buttons.actionGroup>
|
||||||
|
</form>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -44,6 +44,7 @@ kcListClass=pf-v5-c-list
|
||||||
|
|
||||||
kcButtonClass=pf-v5-c-button
|
kcButtonClass=pf-v5-c-button
|
||||||
kcButtonPrimaryClass=pf-v5-c-button pf-m-primary
|
kcButtonPrimaryClass=pf-v5-c-button pf-m-primary
|
||||||
|
kcButtonSecondaryClass=pf-v5-c-button pf-m-secondary
|
||||||
kcButtonBlockClass=pf-m-block
|
kcButtonBlockClass=pf-m-block
|
||||||
kcButtonLinkClass=pf-v5-c-button pf-m-link
|
kcButtonLinkClass=pf-v5-c-button pf-m-link
|
||||||
kcCommonLogoIdP=pf-v5-c-login__main-footer-links-item
|
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
|
kcContentWrapperClass=pf-v5-u-mb-md-on-md
|
||||||
kcWebAuthnDefaultIcon=pf-v5-c-icon pf-m-lg
|
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
|
|
@ -127,28 +127,48 @@
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
<#macro selectTag attribute>
|
<#macro selectTag attribute>
|
||||||
<select id="${attribute.name}" name="${attribute.name}" class="${properties.kcInputClass!}"
|
<div class="${properties.kcInputClass!}">
|
||||||
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
<select id="${attribute.name}" name="${attribute.name}"
|
||||||
<#if attribute.readOnly>disabled</#if>
|
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||||
<#if attribute.annotations.inputType=='multiselect'>multiple</#if>
|
<#if attribute.readOnly>disabled</#if>
|
||||||
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
<#if attribute.annotations.inputType=='multiselect'>multiple</#if>
|
||||||
>
|
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||||
<#if attribute.annotations.inputType=='select'>
|
>
|
||||||
<option value=""></option>
|
<#if attribute.annotations.inputType=='select'>
|
||||||
</#if>
|
<option value=""></option>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
||||||
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
||||||
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
||||||
<#assign options=attribute.validators.options.options>
|
<#assign options=attribute.validators.options.options>
|
||||||
<#else>
|
<#else>
|
||||||
<#assign options=[]>
|
<#assign options=[]>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#list options as option>
|
<#list options as option>
|
||||||
<option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option>
|
<option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option>
|
||||||
</#list>
|
</#list>
|
||||||
</select>
|
</select>
|
||||||
|
<span class="${properties.kcFormControlUtilClass}">
|
||||||
|
<span class="${properties.kcFormControlToggleIcon!}">
|
||||||
|
<svg
|
||||||
|
class="pf-v5-svg"
|
||||||
|
viewBox="0 0 320 512"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
||||||
|
>
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|
||||||
<#macro inputTagSelects attribute>
|
<#macro inputTagSelects attribute>
|
||||||
|
|
|
@ -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">
|
||||||
|
<span class="${properties.kcWebAuthnKeyIcon!}"></span>
|
||||||
|
${kcSanitize(msg("webauthn-registration-title"))?no_esc}
|
||||||
|
<#elseif section = "form">
|
||||||
|
|
||||||
|
<form id="register" action="${url.loginAction}" method="post">
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<input type="hidden" id="clientDataJSON" name="clientDataJSON"/>
|
||||||
|
<input type="hidden" id="attestationObject" name="attestationObject"/>
|
||||||
|
<input type="hidden" id="publicKeyCredentialId" name="publicKeyCredentialId"/>
|
||||||
|
<input type="hidden" id="authenticatorLabel" name="authenticatorLabel"/>
|
||||||
|
<input type="hidden" id="transports" name="transports"/>
|
||||||
|
<input type="hidden" id="error" name="error"/>
|
||||||
|
<@passwordCommons.logoutOtherSessions/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { registerByWebAuthn } from "${url.resourcesPath}/js/webauthnRegister.js";
|
||||||
|
const registerButton = document.getElementById('registerWebAuthn');
|
||||||
|
registerButton.addEventListener("click", function() {
|
||||||
|
const input = {
|
||||||
|
challenge : '${challenge}',
|
||||||
|
userid : '${userid}',
|
||||||
|
username : '${username}',
|
||||||
|
signatureAlgorithms : [<#list signatureAlgorithms as sigAlg>${sigAlg?c},</#list>],
|
||||||
|
rpEntityName : '${rpEntityName}',
|
||||||
|
rpId : '${rpId}',
|
||||||
|
attestationConveyancePreference : '${attestationConveyancePreference}',
|
||||||
|
authenticatorAttachment : '${authenticatorAttachment}',
|
||||||
|
requireResidentKey : '${requireResidentKey}',
|
||||||
|
userVerificationRequirement : '${userVerificationRequirement}',
|
||||||
|
createTimeout : ${createTimeout},
|
||||||
|
excludeCredentialIds : '${excludeCredentialIds}',
|
||||||
|
initLabel : "${msg("webauthn-registration-init-label")?no_esc}",
|
||||||
|
initLabelPrompt : "${msg("webauthn-registration-init-label-prompt")?no_esc}",
|
||||||
|
errmsg : "${msg("webauthn-unsupported-browser-text")?no_esc}"
|
||||||
|
};
|
||||||
|
registerByWebAuthn(input);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="pf-v5-u-py-lg ${properties.kcFormClass!}">
|
||||||
|
<@buttons.actionGroup>
|
||||||
|
<@buttons.button id="registerWebAuthn" label="doRegisterSecurityKey" />
|
||||||
|
<#if !isSetRetry?has_content && isAppInitiatedAction?has_content>
|
||||||
|
<form action="${url.loginAction}" id="kc-webauthn-settings-form" method="post">
|
||||||
|
<@buttons.button id="cancelWebAuthnAIA" name="cancel-aia" label="doCancel" class=["kcButtonSecondaryClass"]/>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</@buttons.actionGroup>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
Loading…
Reference in a new issue