KEYCLOAK-19487 Test cases for managing 2FA authenticators in account console

This commit is contained in:
Martin Bartoš 2021-10-12 13:05:31 +02:00 committed by Marek Posolda
parent 38174212f9
commit faefeccbee
33 changed files with 750 additions and 176 deletions

View file

@ -96,6 +96,16 @@
<artifactId>arquillian-drone-appium-extension</artifactId>
<version>${arquillian-drone.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<profiles>

View file

@ -0,0 +1,102 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.ui.account2.page.utils;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.testsuite.ui.account2.page.AbstractLoggedInPage;
import org.keycloak.testsuite.ui.account2.page.SigningInPage;
import java.time.LocalDateTime;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
/**
* Helper class for SigningIn page
*/
public class SigningInPageUtils {
public static void testModalDialog(AbstractLoggedInPage accountPage, Runnable triggerModal, Runnable onCancel) {
triggerModal.run();
accountPage.modal().assertIsDisplayed();
accountPage.modal().clickCancel();
accountPage.modal().assertIsNotDisplayed();
onCancel.run();
triggerModal.run();
accountPage.modal().clickConfirm();
accountPage.modal().assertIsNotDisplayed();
}
public static void assertUserCredential(String expectedUserLabel, boolean removable, SigningInPage.UserCredential userCredential) {
assertThat(userCredential.getUserLabel(), is(expectedUserLabel));
// we expect the credential was created/edited no longer that 2 minutes ago (1 minute might not be enough in some corner cases)
LocalDateTime beforeNow = LocalDateTime.now().minusMinutes(2);
LocalDateTime now = LocalDateTime.now();
// createdAt doesn't specify seconds so it should be something like 12:47:00
LocalDateTime createdAt = userCredential.getCreatedAt();
assertThat("Creation time should be after time before update", createdAt.isAfter(beforeNow), is(true));
assertThat("Creation time should be before now", createdAt.isBefore(now), is(true));
assertThat("Remove button visible", userCredential.isRemoveBtnDisplayed(), is(removable));
assertThat("Update button visible", userCredential.isUpdateBtnDisplayed(), is(not(removable)));
}
public static void testSetUpLink(RealmResource realmResource, SigningInPage.CredentialType credentialType, String requiredActionProviderId) {
assertThat("Set up link for \"" + credentialType.getType() + "\" is not visible", credentialType.isSetUpLinkVisible(), is(true));
RequiredActionProviderRepresentation requiredAction = new RequiredActionProviderRepresentation();
requiredAction.setEnabled(false);
realmResource.flows().updateRequiredAction(requiredActionProviderId, requiredAction);
refreshPageAndWaitForLoad();
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isSetUpLinkVisible(), is(false));
assertThat("Title for \"" + credentialType.getType() + "\" is visible", credentialType.isTitleVisible(), is(false));
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isNotSetUpLabelVisible(), is(false));
}
public static void testRemoveCredential(AbstractLoggedInPage accountPage, SigningInPage.UserCredential userCredential) {
int countBeforeRemove = userCredential.getCredentialType().getUserCredentialsCount();
testModalDialog(accountPage, userCredential::clickRemoveBtn, () -> {
assertThat(userCredential.isPresent(), is(true));
assertThat(userCredential.getCredentialType().getUserCredentialsCount(), is(countBeforeRemove));
});
accountPage.alert().assertSuccess();
assertThat(userCredential.isPresent(), is(false));
assertThat(userCredential.getCredentialType().getUserCredentialsCount(), is(countBeforeRemove - 1));
}
public static SigningInPage.UserCredential getNewestUserCredential(UserResource userResource, SigningInPage.CredentialType credentialType) {
List<CredentialRepresentation> credentials = userResource.credentials();
SigningInPage.UserCredential userCredential =
credentialType.getUserCredential(credentials.get(credentials.size() - 1).getId());
assertThat(userCredential.isPresent(), is(true));
return userCredential;
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.ui.account2;
import org.junit.Test;
import org.keycloak.testsuite.ui.account2.page.AbstractLoggedInPage;
import org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils;
import static org.junit.Assert.assertTrue;
@ -50,13 +51,6 @@ public abstract class BaseAccountPageTest extends AbstractAccountTest {
}
protected void testModalDialog(Runnable triggerModal, Runnable onCancel) {
triggerModal.run();
getAccountPage().modal().assertIsDisplayed();
getAccountPage().modal().clickCancel();
getAccountPage().modal().assertIsNotDisplayed();
onCancel.run();
triggerModal.run();
getAccountPage().modal().clickConfirm();
getAccountPage().modal().assertIsNotDisplayed();
SigningInPageUtils.testModalDialog(getAccountPage(), triggerModal, onCancel);
}
}

View file

@ -20,51 +20,37 @@ package org.keycloak.testsuite.ui.account2;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAuthenticatorFactory;
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
import org.keycloak.common.Profile;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.credential.WebAuthnCredentialModel;
import org.keycloak.models.utils.Base32;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
import org.keycloak.testsuite.admin.Users;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.auth.page.login.OTPSetup;
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
import org.keycloak.testsuite.ui.account2.page.AbstractLoggedInPage;
import org.keycloak.testsuite.ui.account2.page.SigningInPage;
import org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils;
import java.time.LocalDateTime;
import java.util.List;
import static java.util.Collections.emptyList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.models.UserModel.RequiredAction.CONFIGURE_TOTP;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
import static org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils.assertUserCredential;
import static org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils.testSetUpLink;
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
public class SigningInTest extends BaseAccountPageTest {
public static final String PASSWORD_LABEL = "My Password";
public static final String WEBAUTHN_FLOW_ID = "75e2390e-f296-49e6-acf8-6d21071d7e10";
@Page
private SigningInPage signingInPage;
@ -75,12 +61,8 @@ public class SigningInTest extends BaseAccountPageTest {
@Page
private OTPSetup otpSetupPage;
private SigningInPage.CredentialType passwordCredentialType;
private SigningInPage.CredentialType otpCredentialType;
private SigningInPage.CredentialType webAuthnCredentialType;
private SigningInPage.CredentialType webAuthnPwdlessCredentialType;
private TimeBasedOTP otpGenerator;
@Override
@ -88,42 +70,6 @@ public class SigningInTest extends BaseAccountPageTest {
return signingInPage;
}
@Override
protected void afterAbstractKeycloakTestRealmImport() {
super.afterAbstractKeycloakTestRealmImport();
// configure WebAuthn
// we can't do this during the realm import because we'd need to specify all built-in flows as well
AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation();
flow.setId(WEBAUTHN_FLOW_ID);
flow.setAlias("webauthn flow");
flow.setProviderId("basic-flow");
flow.setBuiltIn(false);
flow.setTopLevel(true);
testRealmResource().flows().createFlow(flow);
AuthenticationExecutionRepresentation execution = new AuthenticationExecutionRepresentation();
execution.setAuthenticator(WebAuthnAuthenticatorFactory.PROVIDER_ID);
execution.setPriority(10);
execution.setRequirement(REQUIRED.toString());
execution.setParentFlow(WEBAUTHN_FLOW_ID);
testRealmResource().flows().addExecution(execution);
execution.setAuthenticator(WebAuthnPasswordlessAuthenticatorFactory.PROVIDER_ID);
testRealmResource().flows().addExecution(execution);
RequiredActionProviderSimpleRepresentation requiredAction = new RequiredActionProviderSimpleRepresentation();
requiredAction.setProviderId(WebAuthnRegisterFactory.PROVIDER_ID);
requiredAction.setName("blahblah");
testRealmResource().flows().registerRequiredAction(requiredAction);
requiredAction.setProviderId(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
testRealmResource().flows().registerRequiredAction(requiredAction);
// no need to actually configure the authentication, in Account Console tests we just verify the registration
}
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
@ -135,8 +81,6 @@ public class SigningInTest extends BaseAccountPageTest {
public void beforeSigningInTest() {
passwordCredentialType = signingInPage.getCredentialType(PasswordCredentialModel.TYPE);
otpCredentialType = signingInPage.getCredentialType(OTPCredentialModel.TYPE);
webAuthnCredentialType = signingInPage.getCredentialType(WebAuthnCredentialModel.TYPE_TWOFACTOR);
webAuthnPwdlessCredentialType = signingInPage.getCredentialType(WebAuthnCredentialModel.TYPE_PASSWORDLESS);
RealmRepresentation realm = testRealmResource().toRepresentation();
otpGenerator = new TimeBasedOTP(realm.getOtpPolicyAlgorithm(), realm.getOtpPolicyDigits(), realm.getOtpPolicyPeriod(), 0);
@ -146,25 +90,21 @@ public class SigningInTest extends BaseAccountPageTest {
public void categoriesTest() {
testContext.setTestRealmReps(emptyList()); // reimport realm after this test
assertEquals(3, signingInPage.getCategoriesCount());
assertEquals("Basic Authentication", signingInPage.getCategoryTitle("basic-authentication"));
assertEquals("Two-Factor Authentication", signingInPage.getCategoryTitle("two-factor"));
assertEquals("Passwordless", signingInPage.getCategoryTitle("passwordless"));
// Delete WebAuthn flow ==> Passwordless category should disappear
testRealmResource().flows().deleteFlow(WEBAUTHN_FLOW_ID);
refreshPageAndWaitForLoad();
assertEquals(2, signingInPage.getCategoriesCount());
assertThat(signingInPage.getCategoriesCount(), is(1));
assertThat(signingInPage.getCategoryTitle("basic-authentication"), is("Basic Authentication"));
}
@Test
public void updatePasswordTest() {
SigningInPage.UserCredential passwordCred =
passwordCredentialType.getUserCredential(testUserResource().credentials().get(0).getId());
SigningInPage.UserCredential passwordCred = passwordCredentialType.getUserCredential(
testUserResource()
.credentials()
.get(0)
.getId()
);
assertFalse(passwordCredentialType.isSetUpLinkVisible());
assertTrue(passwordCredentialType.isSetUp());
assertThat(passwordCredentialType.isSetUpLinkVisible(), is(false));
assertThat(passwordCredentialType.isSetUp(), is(true));
assertUserCredential(PASSWORD_LABEL, false, passwordCred);
LocalDateTime previousCreatedAt = passwordCred.getCreatedAt();
@ -178,95 +118,79 @@ public class SigningInTest extends BaseAccountPageTest {
signingInPage.assertCurrent();
assertUserCredential(PASSWORD_LABEL, false, passwordCred);
assertNotEquals(previousCreatedAt, passwordCred.getCreatedAt());
assertThat(passwordCred.getCreatedAt(), is(not(previousCreatedAt)));
}
@Test
public void updatePasswordTestForUserWithoutPassword() {
// Remove password from the user through admin REST API
String passwordId = testUserResource().credentials().get(0).getId();
testUserResource().removeCredential(passwordId);
// Remove password from the user through admin REST API
String passwordId = testUserResource().credentials().get(0).getId();
testUserResource().removeCredential(passwordId);
// Refresh the page
refreshPageAndWaitForLoad();
// Refresh the page
refreshPageAndWaitForLoad();
// Test user doesn't have password set
assertTrue(passwordCredentialType.isSetUpLinkVisible());
assertFalse(passwordCredentialType.isSetUp());
// Test user doesn't have password set
assertThat(passwordCredentialType.isSetUpLinkVisible(), is(true));
assertThat(passwordCredentialType.isSetUp(), is(false));
// Set password
passwordCredentialType.clickSetUpLink();
updatePasswordPage.assertCurrent();
String originalPassword = Users.getPasswordOf(testUser);
updatePasswordPage.updatePasswords(originalPassword, originalPassword);
signingInPage.assertCurrent();
// Set password
passwordCredentialType.clickSetUpLink();
updatePasswordPage.assertCurrent();
String originalPassword = Users.getPasswordOf(testUser);
updatePasswordPage.updatePasswords(originalPassword, originalPassword);
signingInPage.assertCurrent();
// Credential set-up now
assertFalse(passwordCredentialType.isSetUpLinkVisible());
assertTrue(passwordCredentialType.isSetUp());
SigningInPage.UserCredential passwordCred =
passwordCredentialType.getUserCredential(testUserResource().credentials().get(0).getId());
assertUserCredential(PASSWORD_LABEL, false, passwordCred);
// Credential set-up now
assertThat(passwordCredentialType.isSetUpLinkVisible(), is(false));
assertThat(passwordCredentialType.isSetUp(), is(true));
SigningInPage.UserCredential passwordCred =
passwordCredentialType.getUserCredential(testUserResource().credentials().get(0).getId());
assertUserCredential(PASSWORD_LABEL, false, passwordCred);
}
@Test
public void otpTest() {
testContext.setTestRealmReps(emptyList());
assertFalse(otpCredentialType.isSetUp());
assertThat(otpCredentialType.isSetUp(), is(false));
otpCredentialType.clickSetUpLink();
otpSetupPage.cancel();
signingInPage.assertCurrent();
assertFalse(otpCredentialType.isSetUp());
assertEquals("Authenticator Application", otpCredentialType.getTitle());
signingInPage.assertCurrent();
assertThat(otpCredentialType.isSetUp(), is(false));
assertThat(otpCredentialType.getTitle(), is("Authenticator Application"));
final String label1 = "OTP is secure";
final String label2 = "OTP is inconvenient";
SigningInPage.UserCredential otp1 = addOtpCredential(label1);
assertTrue(otpCredentialType.isSetUp());
assertEquals(1, otpCredentialType.getUserCredentialsCount());
assertThat(otpCredentialType.isSetUp(), is(true));
assertThat(otpCredentialType.getUserCredentialsCount(), is(1));
assertUserCredential(label1, true, otp1);
SigningInPage.UserCredential otp2 = addOtpCredential(label2);
assertEquals(2, otpCredentialType.getUserCredentialsCount());
assertThat(otpCredentialType.getUserCredentialsCount(), is(2));
assertUserCredential(label2, true, otp2);
assertTrue("Set up link is not visible", otpCredentialType.isSetUpLinkVisible());
assertThat("Set up link is not visible", otpCredentialType.isSetUpLinkVisible(), is(true));
RequiredActionProviderRepresentation requiredAction = new RequiredActionProviderRepresentation();
requiredAction.setEnabled(false);
testRealmResource().flows().updateRequiredAction(CONFIGURE_TOTP.name(), requiredAction);
refreshPageAndWaitForLoad();
assertFalse("Set up link for \"otp\" is visible", otpCredentialType.isSetUpLinkVisible());
assertFalse("Not set up link for \"otp\" is visible", otpCredentialType.isNotSetUpLabelVisible());
assertTrue("Title for \"otp\" is not visible", otpCredentialType.isTitleVisible());
assertEquals(2, otpCredentialType.getUserCredentialsCount());
assertThat("Set up link for \"otp\" is visible", otpCredentialType.isSetUpLinkVisible(), is(false));
assertThat("Not set up link for \"otp\" is visible", otpCredentialType.isNotSetUpLabelVisible(), is(false));
assertThat("Title for \"otp\" is not visible", otpCredentialType.isTitleVisible(), is(true));
assertThat(otpCredentialType.getUserCredentialsCount(), is(2));
testRemoveCredential(otp1);
}
@Test
public void setUpLinksTest() {
testSetUpLink(otpCredentialType, CONFIGURE_TOTP.name());
testSetUpLink(webAuthnCredentialType, WebAuthnRegisterFactory.PROVIDER_ID);
testSetUpLink(webAuthnPwdlessCredentialType, WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
}
private void testSetUpLink(SigningInPage.CredentialType credentialType, String requiredActionProviderId) {
assertTrue("Set up link for \"" + credentialType.getType() + "\" is not visible", credentialType.isSetUpLinkVisible());
RequiredActionProviderRepresentation requiredAction = new RequiredActionProviderRepresentation();
requiredAction.setEnabled(false);
testRealmResource().flows().updateRequiredAction(requiredActionProviderId, requiredAction);
refreshPageAndWaitForLoad();
assertFalse("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isSetUpLinkVisible());
assertFalse("Title for \"" + credentialType.getType() + "\" is visible", credentialType.isTitleVisible());
assertFalse("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isNotSetUpLabelVisible());
testSetUpLink(testRealmResource(), otpCredentialType, CONFIGURE_TOTP.name());
}
private SigningInPage.UserCredential addOtpCredential(String label) {
@ -285,39 +209,10 @@ public class SigningInTest extends BaseAccountPageTest {
}
private SigningInPage.UserCredential getNewestUserCredential(SigningInPage.CredentialType credentialType) {
List<CredentialRepresentation> credentials = testUserResource().credentials();
SigningInPage.UserCredential userCredential =
credentialType.getUserCredential(credentials.get(credentials.size() - 1).getId());
assertTrue(userCredential.isPresent());
return userCredential;
return SigningInPageUtils.getNewestUserCredential(testUserResource(), credentialType);
}
private void testRemoveCredential(SigningInPage.UserCredential userCredential) {
int countBeforeRemove = userCredential.getCredentialType().getUserCredentialsCount();
testModalDialog(userCredential::clickRemoveBtn, () -> {
assertTrue(userCredential.isPresent());
assertEquals(countBeforeRemove, userCredential.getCredentialType().getUserCredentialsCount());
});
signingInPage.alert().assertSuccess();
assertFalse(userCredential.isPresent());
assertEquals(countBeforeRemove - 1, userCredential.getCredentialType().getUserCredentialsCount());
}
private void assertUserCredential(String expectedUserLabel, boolean removable, SigningInPage.UserCredential userCredential) {
assertEquals(expectedUserLabel, userCredential.getUserLabel());
// we expect the credential was created/edited no longer that 2 minutes ago (1 minute might not be enough in some corner cases)
LocalDateTime beforeNow = LocalDateTime.now().minusMinutes(2);
LocalDateTime now = LocalDateTime.now();
// createdAt doesn't specify seconds so it should be something like 12:47:00
LocalDateTime createdAt = userCredential.getCreatedAt();
assertTrue("Creation time should be after time before update", createdAt.isAfter(beforeNow));
assertTrue("Creation time should be before now", createdAt.isBefore(now));
assertEquals("Remove button visible", removable, userCredential.isRemoveBtnDisplayed());
assertEquals("Update button visible", !removable, userCredential.isUpdateBtnDisplayed());
SigningInPageUtils.testRemoveCredential(getAccountPage(), userCredential);
}
}

View file

@ -32,7 +32,7 @@
<packaging>pom</packaging>
<name>Other Tests Modules</name>
<description>Test modules that depend on the Base TestSuite.
This POM contains common configuration for submodules.</description>
@ -172,6 +172,7 @@
<id>webauthn</id>
<modules>
<module>console</module>
<module>base-ui</module>
<module>webauthn</module>
</modules>
</profile>

View file

@ -31,6 +31,11 @@
<version>${project.version}</version>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-base-ui</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-bom</artifactId>

View file

@ -2,12 +2,18 @@ package org.keycloak.testsuite.webauthn.pages;
import org.junit.Assert;
import org.keycloak.testsuite.pages.LanguageComboboxAwarePage;
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;
import org.openqa.selenium.support.FindBy;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
*/
@ -20,6 +26,12 @@ public class WebAuthnErrorPage extends LanguageComboboxAwarePage {
@FindBy(id = "cancelWebAuthnAIA")
private WebElement cancelRegistrationAIA;
@FindBy(className = "alert-error")
private WebElement errorMessage;
@FindBy(id = "kc-webauthn-authenticator")
private List<WebElement> authenticators;
public void clickTryAgain() {
WaitUtils.waitUntilElement(tryAgainButton).is().clickable();
tryAgainButton.click();
@ -34,6 +46,33 @@ public class WebAuthnErrorPage extends LanguageComboboxAwarePage {
}
}
public String getError() {
try {
return UIUtils.getTextFromElement(errorMessage);
} catch (NoSuchElementException e) {
return null;
}
}
public int getAuthenticatorsCount() {
try {
return authenticators.size();
} catch (NoSuchElementException e) {
return 0;
}
}
public List<String> getAuthenticators() {
try {
return authenticators.stream()
.filter(Objects::nonNull)
.map(UIUtils::getTextFromElement)
.collect(Collectors.toList());
} catch (NoSuchElementException e) {
return Collections.emptyList();
}
}
@Override
public boolean isCurrent() {
try {

View file

@ -46,6 +46,9 @@ public class WebAuthnRegisterPage extends AbstractPage {
@FindBy(id = "cancelWebAuthnAIA")
private WebElement cancelAIAButton;
@FindBy(id = "kc-page-title")
private WebElement formTitle;
public void clickRegister() {
WaitUtils.waitUntilElement(registerButton).is().clickable();
registerButton.click();
@ -58,7 +61,7 @@ public class WebAuthnRegisterPage extends AbstractPage {
}
public void registerWebAuthnCredential(String authenticatorLabel) {
// label edit after registering authenicator by .create()
// label edit after registering authenticator by .create()
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(60));
Alert promptDialog = wait.until(ExpectedConditions.alertIsPresent());
@ -68,9 +71,8 @@ public class WebAuthnRegisterPage extends AbstractPage {
promptDialog.accept();
}
private boolean isAIA() {
public boolean isAIA() {
try {
registerButton.getText();
cancelAIAButton.getText();
return true;
} catch (NoSuchElementException e) {
@ -78,15 +80,22 @@ public class WebAuthnRegisterPage extends AbstractPage {
}
}
public boolean isCurrent() {
public String getFormTitle() {
try {
registerButton.getText();
return driver.getPageSource().contains("navigator.credentials.create");
WaitUtils.waitUntilElement(formTitle).is().present();
return formTitle.getText();
} catch (NoSuchElementException e) {
return false;
return null;
}
}
@Override
public boolean isCurrent() {
final String formTitle = getFormTitle();
return formTitle != null && formTitle.equals("Security Key Registration") &&
driver.getPageSource().contains("navigator.credentials.create");
}
@Override
public void open() {
throw new UnsupportedOperationException();

View file

@ -45,6 +45,7 @@ import static org.keycloak.common.Profile.Feature.WEB_AUTHN;
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.ALTERNATIVE;
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
/**
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
@ -125,6 +126,8 @@ public class AppInitiatedActionWebAuthnTest extends AbstractAppInitiatedActionTe
webAuthnRegisterPage.assertCurrent();
webAuthnRegisterPage.cancelAIA();
waitForPageToLoad();
assertKcActionStatus("cancelled");
}
@ -137,6 +140,9 @@ public class AppInitiatedActionWebAuthnTest extends AbstractAppInitiatedActionTe
webAuthnRegisterPage.assertCurrent();
webAuthnRegisterPage.clickRegister();
webAuthnRegisterPage.registerWebAuthnCredential("authenticator1");
waitForPageToLoad();
assertKcActionStatus("success");
}

View file

@ -0,0 +1,168 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.webauthn.account;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.After;
import org.junit.Before;
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAuthenticatorFactory;
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
import org.keycloak.common.Profile;
import org.keycloak.models.credential.WebAuthnCredentialModel;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
import org.keycloak.testsuite.AbstractAuthTest;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.page.AbstractPatternFlyAlert;
import org.keycloak.testsuite.ui.account2.page.SigningInPage;
import org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils;
import org.keycloak.testsuite.webauthn.AbstractWebAuthnVirtualTest;
import org.keycloak.testsuite.webauthn.authenticators.DefaultVirtualAuthOptions;
import org.keycloak.testsuite.webauthn.authenticators.UseVirtualAuthenticators;
import org.keycloak.testsuite.webauthn.authenticators.VirtualAuthenticatorManager;
import org.keycloak.testsuite.webauthn.pages.WebAuthnRegisterPage;
import org.openqa.selenium.virtualauthenticator.VirtualAuthenticatorOptions;
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
@EnableFeature(value = Profile.Feature.WEB_AUTHN, skipRestart = true, onlyForProduct = true)
public abstract class AbstractWebAuthnAccountTest extends AbstractAuthTest implements UseVirtualAuthenticators {
@Page
protected SigningInPage signingInPage;
@Page
protected WebAuthnRegisterPage webAuthnRegisterPage;
private VirtualAuthenticatorManager webAuthnManager;
protected SigningInPage.CredentialType webAuthnCredentialType;
protected SigningInPage.CredentialType webAuthnPwdlessCredentialType;
protected static final String WEBAUTHN_FLOW_ID = "75e2390e-f296-49e6-acf8-6d21071d7e10";
@Override
@Before
public void setUpVirtualAuthenticator() {
webAuthnManager = AbstractWebAuthnVirtualTest.createDefaultVirtualManager(driver, getDefaultOptions());
}
@Override
@After
public void removeVirtualAuthenticator() {
webAuthnManager.removeAuthenticator();
}
@Before
public void navigateBeforeTest() {
driver.manage().window().maximize();
webAuthnCredentialType = signingInPage.getCredentialType(WebAuthnCredentialModel.TYPE_TWOFACTOR);
webAuthnPwdlessCredentialType = signingInPage.getCredentialType(WebAuthnCredentialModel.TYPE_PASSWORDLESS);
createTestUserWithAdminClient(false);
signingInPage.navigateTo();
loginToAccount();
signingInPage.assertCurrent();
}
@Override
protected void afterAbstractKeycloakTestRealmImport() {
super.afterAbstractKeycloakTestRealmImport();
// configure WebAuthn
// we can't do this during the realm import because we'd need to specify all built-in flows as well
AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation();
flow.setId(WEBAUTHN_FLOW_ID);
flow.setAlias("webauthn flow");
flow.setProviderId("basic-flow");
flow.setBuiltIn(false);
flow.setTopLevel(true);
testRealmResource().flows().createFlow(flow);
AuthenticationExecutionRepresentation execution = new AuthenticationExecutionRepresentation();
execution.setAuthenticator(WebAuthnAuthenticatorFactory.PROVIDER_ID);
execution.setPriority(10);
execution.setRequirement(REQUIRED.toString());
execution.setParentFlow(WEBAUTHN_FLOW_ID);
testRealmResource().flows().addExecution(execution);
execution.setAuthenticator(WebAuthnPasswordlessAuthenticatorFactory.PROVIDER_ID);
testRealmResource().flows().addExecution(execution);
RequiredActionProviderSimpleRepresentation requiredAction = new RequiredActionProviderSimpleRepresentation();
requiredAction.setProviderId(WebAuthnRegisterFactory.PROVIDER_ID);
requiredAction.setName("blahblah");
testRealmResource().flows().registerRequiredAction(requiredAction);
requiredAction.setProviderId(WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
testRealmResource().flows().registerRequiredAction(requiredAction);
}
protected VirtualAuthenticatorManager getWebAuthnManager() {
return webAuthnManager;
}
protected VirtualAuthenticatorOptions getDefaultOptions() {
return DefaultVirtualAuthOptions.DEFAULT.getOptions();
}
protected void loginToAccount() {
loginPage.assertCurrent();
loginPage.form().login(testUser);
waitForPageToLoad();
}
protected void logout() {
signingInPage.navigateTo();
signingInPage.assertCurrent();
signingInPage.header().clickLogoutBtn();
waitForPageToLoad();
}
protected SigningInPage.UserCredential addWebAuthnCredential(String label) {
return addWebAuthnCredential(label, false);
}
protected SigningInPage.UserCredential addWebAuthnCredential(String label, boolean passwordless) {
SigningInPage.CredentialType credentialType = passwordless ? webAuthnPwdlessCredentialType : webAuthnCredentialType;
AbstractPatternFlyAlert.waitUntilHidden();
credentialType.clickSetUpLink();
webAuthnRegisterPage.assertCurrent();
webAuthnRegisterPage.clickRegister();
webAuthnRegisterPage.registerWebAuthnCredential(label);
waitForPageToLoad();
signingInPage.assertCurrent();
return getNewestUserCredential(credentialType);
}
protected void testRemoveCredential(SigningInPage.UserCredential userCredential) {
AbstractPatternFlyAlert.waitUntilHidden();
SigningInPageUtils.testRemoveCredential(signingInPage, userCredential);
}
protected SigningInPage.UserCredential getNewestUserCredential(SigningInPage.CredentialType credentialType) {
return SigningInPageUtils.getNewestUserCredential(testUserResource(), credentialType);
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.webauthn.account;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.FlowUtil;
import org.keycloak.testsuite.util.WaitUtils;
import org.keycloak.testsuite.webauthn.pages.WebAuthnErrorPage;
import org.keycloak.testsuite.webauthn.pages.WebAuthnLoginPage;
import org.keycloak.testsuite.webauthn.updaters.WebAuthnRealmAttributeUpdater;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
public class WebAuthnErrorTest extends AbstractWebAuthnAccountTest {
@Page
protected WebAuthnLoginPage webAuthnLoginPage;
@Page
protected WebAuthnErrorPage webAuthnErrorPage;
@Test
public void errorPageWithPossibleAuthenticators() throws IOException {
final int timeoutSec = 3;
addWebAuthnCredential("authenticator#1");
addWebAuthnCredential("authenticator#2");
try (RealmAttributeUpdater u = new WebAuthnRealmAttributeUpdater(testRealmResource())
.setWebAuthnPolicyCreateTimeout(timeoutSec)
.update()) {
RealmRepresentation realm = testRealmResource().toRepresentation();
assertThat(realm, notNullValue());
assertThat(realm.getWebAuthnPolicyCreateTimeout(), is(timeoutSec));
final int webAuthnCount = webAuthnCredentialType.getUserCredentialsCount();
assertThat(webAuthnCount, is(2));
getWebAuthnManager().getActualAuthenticator().getAuthenticator().removeAllCredentials();
setUpWebAuthnFlow("webAuthnFlow");
logout();
signingInPage.navigateTo();
loginToAccount();
webAuthnLoginPage.assertCurrent();
webAuthnLoginPage.clickAuthenticate();
//Should fail after this time
WaitUtils.pause((timeoutSec + 1) * 1000);
webAuthnErrorPage.assertCurrent();
assertThat(webAuthnErrorPage.getError(), containsString("Failed to authenticate by the Security key."));
assertThat(webAuthnErrorPage.getAuthenticatorsCount(), is(2));
assertThat(webAuthnErrorPage.getAuthenticators(), Matchers.contains("authenticator#1", "authenticator#2"));
}
}
private void setUpWebAuthnFlow(String newFlowAlias) {
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias));
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session)
.selectFlow(newFlowAlias)
.inForms(forms -> forms
.clear()
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, UsernamePasswordFormFactory.PROVIDER_ID)
.addAuthenticatorExecution(AuthenticationExecutionModel.Requirement.REQUIRED, WebAuthnAuthenticatorFactory.PROVIDER_ID)
)
.defineAsBrowserFlow() // Activate this new flow
);
}
}

View file

@ -0,0 +1,242 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.webauthn.account;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAuthenticatorFactory;
import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
import org.keycloak.testsuite.ui.account2.page.SigningInPage;
import org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils;
import org.keycloak.testsuite.webauthn.authenticators.UseVirtualAuthenticators;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED;
import static org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils.assertUserCredential;
import static org.keycloak.testsuite.ui.account2.page.utils.SigningInPageUtils.testSetUpLink;
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
public class WebAuthnSigningInTest extends AbstractWebAuthnAccountTest implements UseVirtualAuthenticators {
@Test
public void categoriesTest() {
testContext.setTestRealmReps(emptyList()); // reimport realm after this test
assertThat(signingInPage.getCategoriesCount(), is(3));
assertThat(signingInPage.getCategoryTitle("basic-authentication"), is("Basic Authentication"));
assertThat(signingInPage.getCategoryTitle("two-factor"), is("Two-Factor Authentication"));
assertThat(signingInPage.getCategoryTitle("passwordless"), is("Passwordless"));
// Delete WebAuthn flow ==> Passwordless category should disappear
testRealmResource().flows().deleteFlow(WEBAUTHN_FLOW_ID);
refreshPageAndWaitForLoad();
assertThat(signingInPage.getCategoriesCount(), is(2));
}
@Test
public void twoFactorWebAuthnTest() {
testWebAuthn(false);
}
@Test
public void passwordlessWebAuthnTest() {
testWebAuthn(true);
}
@Test
public void testCreateWebAuthnSameUserLabel() {
final String SAME_LABEL = "key123";
// Do we really allow to have several authenticators with the same user label??
SigningInPage.UserCredential webAuthn = addWebAuthnCredential(SAME_LABEL, false);
assertThat(webAuthn, notNullValue());
SigningInPage.UserCredential passwordless = addWebAuthnCredential(SAME_LABEL, true);
assertThat(passwordless, notNullValue());
assertThat(webAuthnCredentialType.getUserCredentialsCount(), is(1));
webAuthn = webAuthnCredentialType.getUserCredential(webAuthn.getId());
assertThat(webAuthn, notNullValue());
assertThat(webAuthn.getUserLabel(), is(SAME_LABEL));
assertThat(webAuthnPwdlessCredentialType.getUserCredentialsCount(), is(1));
passwordless = webAuthnPwdlessCredentialType.getUserCredential(passwordless.getId());
assertThat(passwordless, notNullValue());
assertThat(passwordless.getUserLabel(), is(SAME_LABEL));
SigningInPage.UserCredential webAuthn2 = addWebAuthnCredential(SAME_LABEL, false);
assertThat(webAuthn2, notNullValue());
assertThat(webAuthn2.getUserLabel(), is(SAME_LABEL));
assertThat(webAuthnCredentialType.getUserCredentialsCount(), is(2));
SigningInPage.UserCredential passwordless2 = addWebAuthnCredential(SAME_LABEL, true);
assertThat(passwordless2, notNullValue());
assertThat(passwordless2.getUserLabel(), is(SAME_LABEL));
assertThat(webAuthnPwdlessCredentialType.getUserCredentialsCount(), is(2));
}
@Test
public void testMultipleSecurityKeys() {
final String LABEL = "SecurityKey#";
List<SigningInPage.UserCredential> createdCredentials = new ArrayList<>();
for (int i = 0; i < 10; i++) {
SigningInPage.UserCredential key = addWebAuthnCredential(LABEL + i, i % 2 == 0);
assertThat(key, notNullValue());
createdCredentials.add(key);
}
final int webAuthnCount = webAuthnCredentialType.getUserCredentialsCount();
assertThat(webAuthnCount, is(5));
final int passwordlessCount = webAuthnPwdlessCredentialType.getUserCredentialsCount();
assertThat(passwordlessCount, is(5));
UserResource userResource = testRealmResource().users().get(testUser.getId());
assertThat(userResource, notNullValue());
List<CredentialRepresentation> list = userResource.credentials();
assertThat(list, notNullValue());
assertThat(list, not(empty()));
final List<String> credentialsLabels = list.stream()
.map(CredentialRepresentation::getUserLabel)
.collect(Collectors.toList());
assertThat(credentialsLabels, notNullValue());
final List<String> createdCredentialsLabels = createdCredentials.stream()
.map(SigningInPage.UserCredential::getUserLabel)
.collect(Collectors.toList());
assertThat(credentialsLabels.containsAll(createdCredentialsLabels), is(true));
final List<SigningInPage.UserCredential> credentials = createdCredentials.stream()
.filter(key -> key.getUserLabel().equals(LABEL + 0)
|| key.getUserLabel().equals(LABEL + 1))
.collect(Collectors.toList());
assertThat(credentials, hasSize(2));
testRemoveCredential(credentials.get(0));
testRemoveCredential(credentials.get(1));
assertThat(webAuthnCredentialType.getUserCredentialsCount(), is(4));
assertThat(webAuthnPwdlessCredentialType.getUserCredentialsCount(), is(4));
}
@Test
public void setUpLinksTest() {
testSetUpLink(testRealmResource(), webAuthnCredentialType, WebAuthnRegisterFactory.PROVIDER_ID);
testSetUpLink(testRealmResource(), webAuthnPwdlessCredentialType, WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
}
@Test
public void testCancelRegistration() {
cancelRegistration(false);
}
@Test
public void testCancelPasswordlessRegistration() {
cancelRegistration(true);
}
private void cancelRegistration(boolean passwordless) {
SigningInPage.CredentialType credentialType = passwordless ? webAuthnPwdlessCredentialType : webAuthnCredentialType;
credentialType.clickSetUpLink();
waitForPageToLoad();
webAuthnRegisterPage.assertCurrent();
assertThat(webAuthnRegisterPage.isAIA(), is(true));
webAuthnRegisterPage.cancelAIA();
waitForPageToLoad();
signingInPage.assertCurrent();
}
private void testWebAuthn(boolean passwordless) {
testContext.setTestRealmReps(emptyList());
SigningInPage.CredentialType credentialType;
final String expectedHelpText;
final String providerId;
if (passwordless) {
credentialType = webAuthnPwdlessCredentialType;
expectedHelpText = "Use your security key for passwordless sign in.";
providerId = WebAuthnPasswordlessRegisterFactory.PROVIDER_ID;
} else {
credentialType = webAuthnCredentialType;
expectedHelpText = "Use your security key to sign in.";
providerId = WebAuthnRegisterFactory.PROVIDER_ID;
}
assertThat(credentialType.isSetUp(), is(false));
// no way to simulate registration cancellation
assertThat("Set up link for \"" + credentialType.getType() + "\" is not visible", credentialType.isSetUpLinkVisible(), is(true));
assertThat(credentialType.getTitle(), is("Security Key"));
assertThat(credentialType.getHelpText(), is(expectedHelpText));
final String label1 = "WebAuthn is convenient";
final String label2 = "but not yet widely adopted";
SigningInPage.UserCredential webAuthn1 = addWebAuthnCredential(label1, passwordless);
assertThat(credentialType.isSetUp(), is(true));
assertThat(credentialType.getUserCredentialsCount(), is(1));
assertUserCredential(label1, true, webAuthn1);
SigningInPage.UserCredential webAuthn2 = addWebAuthnCredential(label2, passwordless);
assertThat(credentialType.getUserCredentialsCount(), is(2));
assertUserCredential(label2, true, webAuthn2);
RequiredActionProviderRepresentation requiredAction = new RequiredActionProviderRepresentation();
requiredAction.setEnabled(false);
testRealmResource().flows().updateRequiredAction(providerId, requiredAction);
refreshPageAndWaitForLoad();
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isSetUpLinkVisible(), is(false));
assertThat("Not set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isNotSetUpLabelVisible(), is(false));
assertThat("Title for \"" + credentialType.getType() + "\" is not visible", credentialType.isTitleVisible(), is(true));
assertThat(credentialType.getUserCredentialsCount(), is(2));
testRemoveCredential(webAuthn1);
}
}

View file

@ -86,6 +86,9 @@
challenge: base64url.decode(challenge, { loose: true })
};
let createTimeout = ${createTimeout};
if (createTimeout !== 0) publicKey.timeout = createTimeout * 1000;
if (allowCredentials.length) {
publicKey.allowCredentials = allowCredentials;
}

View file

@ -86,7 +86,7 @@
if (isAuthenticatorSelectionSpecified) publicKey.authenticatorSelection = authenticatorSelection;
let createTimeout = ${createTimeout};
if (createTimeout != 0) publicKey.timeout = createTimeout * 1000;
if (createTimeout !== 0) publicKey.timeout = createTimeout * 1000;
let excludeCredentialIds = "${excludeCredentialIds}";
let excludeCredentials = getExcludeCredentials(excludeCredentialIds);

View file

@ -90,9 +90,9 @@ password=My Password
otp-display-name=Authenticator Application
otp-help-text=Enter a verification code from authenticator application.
webauthn-display-name=Security Key
webauthn-help-text=Use your security key to log in.
webauthn-help-text=Use your security key to sign in.
webauthn-passwordless-display-name=Security Key
webauthn-passwordless-help-text=Use your security key for passwordless log in.
webauthn-passwordless-help-text=Use your security key for passwordless sign in.
basic-authentication=Basic Authentication
invalidRequestMessage=Invalid Request