diff --git a/testsuite/integration-arquillian/tests/other/console/pom.xml b/testsuite/integration-arquillian/tests/other/console/pom.xml index 8343e722ca..45068356db 100644 --- a/testsuite/integration-arquillian/tests/other/console/pom.xml +++ b/testsuite/integration-arquillian/tests/other/console/pom.xml @@ -77,6 +77,17 @@ + + maven-jar-plugin + 2.2 + + + + test-jar + + + + diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/mappers/MultivaluedStringProperty.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/mappers/MultivaluedStringProperty.java index d524c75ca4..3d37097417 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/mappers/MultivaluedStringProperty.java +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/idp/mappers/MultivaluedStringProperty.java @@ -41,16 +41,24 @@ public class MultivaluedStringProperty { @FindBy(xpath = "//button[@data-ng-click='addValueToMultivalued(option.name)']") private WebElement plusButton; + protected List getMinusButtons() { + return minusButtons; + } + + protected WebElement getPlusButton() { + return plusButton; + } + public boolean isPresent() { try { - return plusButton.isDisplayed() && items != null && !items.isEmpty(); + return getPlusButton().isDisplayed() && getItems() != null && !getItems().isEmpty(); } catch (NoSuchElementException e) { return false; } } public void clickAddItem() { - plusButton.click(); + getPlusButton().click(); } public List getItems() { @@ -71,7 +79,10 @@ public class MultivaluedStringProperty { clickAddItem(); final List items = getItems(); - WebElement webElement = items.get(items.size() - 1); + final int index = items.size() - 1; + + validateIndex(index); + WebElement webElement = items.get(index); setTextInputValue(webElement, item); } @@ -80,11 +91,12 @@ public class MultivaluedStringProperty { if (index == getItems().size() - 1) { editItem(index, ""); } else { - minusButtons.get(index).click(); + getMinusButtons().get(index).click(); } } private void validateIndex(int index) { - if (index >= getItems().size()) throw new AssertionError("Input with index: " + index + " does not exist."); + if (index < 0 || index >= getItems().size()) + throw new AssertionError("Input with index: " + index + " does not exist."); } } diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml index 0cf1792f7d..2e011b125e 100644 --- a/testsuite/integration-arquillian/tests/other/pom.xml +++ b/testsuite/integration-arquillian/tests/other/pom.xml @@ -164,6 +164,7 @@ webauthn + console webauthn diff --git a/testsuite/integration-arquillian/tests/other/webauthn/pom.xml b/testsuite/integration-arquillian/tests/other/webauthn/pom.xml index 139af1cbaa..d5c9f1814a 100644 --- a/testsuite/integration-arquillian/tests/other/webauthn/pom.xml +++ b/testsuite/integration-arquillian/tests/other/webauthn/pom.xml @@ -20,6 +20,17 @@ + + org.keycloak.testsuite + integration-arquillian-tests-console + ${project.version} + + + org.keycloak.testsuite + integration-arquillian-tests-console + ${project.version} + test-jar + org.jboss.arquillian.extension arquillian-drone-bom diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnPolicyPage.java b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnPolicyPage.java new file mode 100644 index 0000000000..725c5f75ca --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnPolicyPage.java @@ -0,0 +1,297 @@ +/* + * 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.pages; + +import com.webauthn4j.data.AttestationConveyancePreference; +import com.webauthn4j.data.AuthenticatorAttachment; +import com.webauthn4j.data.UserVerificationRequirement; +import org.jboss.arquillian.graphene.elements.GrapheneSelect; +import org.jboss.arquillian.graphene.page.Page; +import org.keycloak.testsuite.console.page.authentication.Authentication; +import org.keycloak.testsuite.console.page.fragment.OnOffSwitch; +import org.keycloak.testsuite.console.page.idp.mappers.MultivaluedStringProperty; +import org.keycloak.testsuite.webauthn.utils.PropertyRequirement; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.ISelect; + +import java.util.List; +import java.util.function.Supplier; + +import static org.keycloak.testsuite.util.UIUtils.getTextInputValue; +import static org.keycloak.testsuite.util.UIUtils.setTextInputValue; +import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; + +/** + * Helper class for WebAuthnPolicy Page + * + * @author Martin Bartos + */ +public class WebAuthnPolicyPage extends Authentication { + + @FindBy(id = "name") + private WebElement rpEntityName; + + @FindBy(xpath = "//select[@id='sigalg']") + private GrapheneSelect signatureAlgorithms; + + @FindBy(id = "rpid") + private WebElement rpEntityId; + + @FindBy(id = "attpref") + private GrapheneSelect attestationConveyancePreference; + + @FindBy(id = "authnatt") + private GrapheneSelect authenticatorAttachment; + + @FindBy(id = "reqresident") + private GrapheneSelect requireResidentKey; + + @FindBy(id = "usrverify") + private GrapheneSelect userVerification; + + @FindBy(id = "timeout") + private WebElement timeout; + + @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='avoidsame']]") + private OnOffSwitch avoidSameAuthenticatorRegister; + + @Page + private MultivaluedAcceptableAaguid acceptableAaguid; + + @FindBy(xpath = "//button[text()='Save']") + private WebElement saveButton; + + @FindBy(xpath = "//button[text()='Cancel']") + private WebElement cancelButton; + + @Override + public String getUriFragment() { + return getAuthenticationUriFragment() + "/webauthn-policy"; + } + + public String getAuthenticationUriFragment() { + return super.getUriFragment(); + } + + /* Relaying Party Entity Name */ + + public String getRpEntityName() { + waitUntilElement(checkElement(() -> rpEntityName)).is().present(); + return getTextInputValue(rpEntityName); + } + + public void setRpEntityName(String entityName) { + waitUntilElement(checkElement(() -> rpEntityName)).is().present(); + setTextInputValue(rpEntityName, entityName); + } + + /* Signature Algorithms */ + + public ISelect getSignatureAlgorithms() { + GrapheneSelect select = checkElement(() -> signatureAlgorithms); + select.setIsMulti(true); + return select; + } + + /* Relaying Party Entity ID */ + + public String getRpEntityId() { + waitUntilElement(checkElement(() -> rpEntityId)).is().present(); + return getTextInputValue(rpEntityId); + } + + public void setRpEntityId(String id) { + waitUntilElement(checkElement(() -> rpEntityId)).is().present(); + setTextInputValue(rpEntityId, id); + } + + /* Attestation Conveyance Preference */ + + public int getAttestationConveyancePreferenceItemsCount() { + return checkElement(() -> attestationConveyancePreference.getOptions().size()); + } + + public AttestationConveyancePreference getAttestationConveyancePreference() { + return getRequirementOrNull(() -> + AttestationConveyancePreference.create(checkElement(() -> attestationConveyancePreference + .getFirstSelectedOption() + .getText())) + ); + } + + public void setAttestationConveyancePreference(AttestationConveyancePreference attestation) { + checkElement(() -> attestationConveyancePreference) + .selectByValue(attestation.getValue()); + } + + /* Authenticator Attachment */ + + public int getAuthenticatorAttachmentItemsCount() { + return checkElement(() -> authenticatorAttachment.getOptions().size()); + } + + public AuthenticatorAttachment getAuthenticatorAttachment() { + return getRequirementOrNull(() -> + AuthenticatorAttachment.create(checkElement(() -> authenticatorAttachment + .getFirstSelectedOption() + .getText())) + ); + } + + public void setAuthenticatorAttachment(AuthenticatorAttachment attachment) { + checkElement(() -> authenticatorAttachment).selectByValue(attachment.getValue()); + } + + /* Require Resident Key */ + // If returns null, the requirement for resident key is not set up + public PropertyRequirement requireResidentKey() { + final int size = checkElement(() -> requireResidentKey).getAllSelectedOptions().size(); + if (size == 0) return null; + + final String value = requireResidentKey.getFirstSelectedOption().getText(); + return PropertyRequirement.fromValue(value); + } + + // If parameter state is null, the requirement is considered as not set up + public void requireResidentKey(PropertyRequirement requiresProperty) { + if (requiresProperty == null) return; + GrapheneSelect select = checkElement(() -> requireResidentKey); + select.selectByVisibleText(requiresProperty.getValue()); + } + + /* User Verification Requirement */ + + public int getUserVerificationItemsCount() { + return checkElement(() -> userVerification).getOptions().size(); + } + + public UserVerificationRequirement getUserVerification() { + return getRequirementOrNull(() -> + UserVerificationRequirement.create(checkElement(() -> userVerification + .getFirstSelectedOption() + .getText())) + ); + } + + public void setUserVerification(UserVerificationRequirement verification) { + checkElement(() -> userVerification).selectByValue(verification.getValue()); + } + + /* Timeout */ + public int getTimeout() { + final String value = getTextInputValue(checkElement(() -> timeout)); + return checkElement(() -> value == null || value.isEmpty() ? 0 : Integer.parseInt(value)); + } + + public void setTimeout(Integer time) { + waitUntilElement(checkElement(() -> timeout)).is().present(); + setTextInputValue(timeout, time == null ? "0" : String.valueOf(time)); + } + + /* Avoid Same Authenticator Registration */ + public boolean avoidSameAuthenticatorRegistration() { + return checkElement(() -> avoidSameAuthenticatorRegister.isOn()); + } + + public void avoidSameAuthenticatorRegister(boolean state) { + if (avoidSameAuthenticatorRegistration() != state) { + checkElement(() -> avoidSameAuthenticatorRegister).setOn(state); + } + } + + public MultivaluedAcceptableAaguid getAcceptableAaguid() { + return acceptableAaguid; + } + + /* Buttons */ + public void clickSaveButton() { + waitUntilElement(checkElement(() -> saveButton)).is().clickable(); + saveButton.click(); + } + + public void clickCancelButton() { + waitUntilElement(checkElement(() -> cancelButton)).is().clickable(); + cancelButton.click(); + } + + public boolean isSaveButtonEnabled() { + waitUntilElement(checkElement(() -> saveButton)).is().present(); + return saveButton.isEnabled(); + } + + public boolean isCancelButtonEnabled() { + waitUntilElement(checkElement(() -> cancelButton)).is().present(); + return cancelButton.isEnabled(); + } + + private T checkElement(Supplier supplier) { + try { + return supplier.get(); + } catch (NoSuchElementException e) { + throw new RuntimeException("Cannot find required element in WebAuthn Policy"); + } catch (NumberFormatException e) { + throw new RuntimeException("Cannot convert element value to number in WebAuthn Policy"); + } + } + + private T getRequirementOrNull(Supplier supplier) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + return null; + } + } + + public class MultivaluedAcceptableAaguid extends MultivaluedStringProperty { + + @FindBy(className = "webauthn-acceptable-aaguid") + private List aaguids; + + @FindBy(id = "newAcceptableAaguid") + private WebElement newAaguid; + + @FindBy(xpath = "//button[@data-ng-click='deleteAcceptableAaguid($index)']") + private List minusButtons; + + @FindBy(xpath = "//button[@data-ng-click='newAcceptableAaguid.length > 0 && addAcceptableAaguid()']") + private WebElement plusButton; + + @Override + public List getItems() { + return checkElement(() -> aaguids); + } + + @Override + public void addItem(String item) { + setTextInputValue(checkElement(() -> newAaguid), item); + clickAddItem(); + } + + @Override + protected List getMinusButtons() { + return minusButtons; + } + + @Override + protected WebElement getPlusButton() { + return plusButton; + } + } +} diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnPolicyPasswordlessPage.java b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnPolicyPasswordlessPage.java new file mode 100644 index 0000000000..2cc54f487f --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnPolicyPasswordlessPage.java @@ -0,0 +1,31 @@ +/* + * 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.pages; + +/** + * Helper class for WebAuthnPolicy Passwordless Page + * + * @author Martin Bartos + */ +public class WebAuthnPolicyPasswordlessPage extends WebAuthnPolicyPage { + + @Override + public String getUriFragment() { + return super.getAuthenticationUriFragment() + "/webauthn-policy-passwordless"; + } +} diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/utils/PropertyRequirement.java b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/utils/PropertyRequirement.java new file mode 100644 index 0000000000..7d0c488ba1 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/utils/PropertyRequirement.java @@ -0,0 +1,28 @@ +package org.keycloak.testsuite.webauthn.utils; + +import org.keycloak.WebAuthnConstants; + +import java.util.Arrays; + +public enum PropertyRequirement { + NOT_SPECIFIED(WebAuthnConstants.OPTION_NOT_SPECIFIED), + YES("Yes"), + NO("No"); + + private final String value; + + PropertyRequirement(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static PropertyRequirement fromValue(String value) { + return Arrays.stream(PropertyRequirement.values()) + .filter(f -> f.getValue().equals(value)) + .findFirst() + .orElse(NOT_SPECIFIED); + } +} diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/AbstractWebAuthnPolicySettingsTest.java b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/AbstractWebAuthnPolicySettingsTest.java new file mode 100644 index 0000000000..f5f45fe90e --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/AbstractWebAuthnPolicySettingsTest.java @@ -0,0 +1,416 @@ +/* + * 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.admin; + +import com.webauthn4j.data.AttestationConveyancePreference; +import com.webauthn4j.data.AuthenticatorAttachment; +import com.webauthn4j.data.UserVerificationRequirement; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.keycloak.models.Constants; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.console.AbstractConsoleTest; +import org.keycloak.testsuite.util.UIUtils; +import org.keycloak.testsuite.webauthn.pages.WebAuthnPolicyPage; +import org.keycloak.testsuite.webauthn.updaters.AbstractWebAuthnRealmUpdater; +import org.keycloak.testsuite.webauthn.utils.PropertyRequirement; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ISelect; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.keycloak.testsuite.util.WaitUtils.pause; +import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad; +import static org.keycloak.testsuite.webauthn.utils.PropertyRequirement.NO; +import static org.keycloak.testsuite.webauthn.utils.PropertyRequirement.YES; + +/** + * @author Martin Bartos + */ +public abstract class AbstractWebAuthnPolicySettingsTest extends AbstractConsoleTest { + + protected static final String ALL_ZERO_AAGUID = "00000000-0000-0000-0000-000000000000"; + protected static final String ALL_ONE_AAGUID = "11111111-1111-1111-1111-111111111111"; + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Before + public void navigateToPolicy() { + getPolicyPage().navigateTo(); + waitForPageToLoad(); + getPolicyPage().assertCurrent(); + } + + protected abstract WebAuthnPolicyPage getPolicyPage(); + + protected abstract AbstractWebAuthnRealmUpdater getWebAuthnRealmUpdater(); + + protected AbstractWebAuthnRealmUpdater updateWebAuthnPolicy( + String rpName, + List algorithms, + String attestationPreference, + String authenticatorAttachment, + String requireResidentKey, + String rpId, + String userVerification, + List acceptableAaguids) { + + AbstractWebAuthnRealmUpdater updater = getWebAuthnRealmUpdater().setWebAuthnPolicyRpEntityName(rpName); + + checkAndSet(algorithms, updater::setWebAuthnPolicySignatureAlgorithms); + checkAndSet(attestationPreference, updater::setWebAuthnPolicyAttestationConveyancePreference); + checkAndSet(authenticatorAttachment, updater::setWebAuthnPolicyAuthenticatorAttachment); + checkAndSet(requireResidentKey, updater::setWebAuthnPolicyRequireResidentKey); + checkAndSet(rpId, updater::setWebAuthnPolicyRpId); + checkAndSet(userVerification, updater::setWebAuthnPolicyUserVerificationRequirement); + checkAndSet(acceptableAaguids, updater::setWebAuthnPolicyAcceptableAaguids); + + return (AbstractWebAuthnRealmUpdater) updater.update(); + } + + private void checkAndSet(T value, Consumer consumer) { + if (value != null) { + consumer.accept(value); + } + } + + protected void checkRpEntityValues() { + String rpEntityName = getPolicyPage().getRpEntityName(); + assertThat(rpEntityName, notNullValue()); + assertThat(rpEntityName, is(Constants.DEFAULT_WEBAUTHN_POLICY_RP_ENTITY_NAME)); + + getPolicyPage().setRpEntityName("newEntityName"); + getPolicyPage().clickSaveButton(); + + rpEntityName = getPolicyPage().getRpEntityName(); + assertThat(rpEntityName, notNullValue()); + assertThat(rpEntityName, is("newEntityName")); + + getPolicyPage().setRpEntityName(""); + getPolicyPage().clickSaveButton(); + + rpEntityName = getPolicyPage().getRpEntityName(); + assertThat(rpEntityName, notNullValue()); + assertThat(rpEntityName, is(Constants.DEFAULT_WEBAUTHN_POLICY_RP_ENTITY_NAME)); + + String rpEntityId = getPolicyPage().getRpEntityId(); + assertThat(rpEntityId, notNullValue()); + assertThat(rpEntityId, is("")); + + getPolicyPage().setRpEntityId("rpId123"); + getPolicyPage().clickSaveButton(); + + rpEntityId = getPolicyPage().getRpEntityId(); + assertThat(rpEntityId, notNullValue()); + assertThat(rpEntityId, is("rpId123")); + } + + protected void checkWrongSignatureAlgorithm() throws IOException { + try (AbstractWebAuthnRealmUpdater rau = (AbstractWebAuthnRealmUpdater) getWebAuthnRealmUpdater() + .setWebAuthnPolicySignatureAlgorithms(Collections.singletonList("something-bad")) + .update()) { + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + final List signatureAlgorithms = realm.getWebAuthnPolicySignatureAlgorithms(); + assertThat(signatureAlgorithms, notNullValue()); + assertThat(signatureAlgorithms.size(), is(1)); + + getPolicyPage().navigateTo(); + waitForPageToLoad(); + + ISelect selectedAlg = getPolicyPage().getSignatureAlgorithms(); + assertThat(selectedAlg, notNullValue()); + + try { + // should throw an exception + selectedAlg.getFirstSelectedOption(); + } catch (NoSuchElementException e) { + assertThat(e.getMessage(), containsString("No options are selected")); + } + } + } + + protected void checkSignatureAlgorithms() { + getPolicyPage().assertCurrent(); + + final ISelect algorithms = getPolicyPage().getSignatureAlgorithms(); + assertThat(algorithms, notNullValue()); + + algorithms.selectByValue("ES256"); + algorithms.selectByValue("ES384"); + algorithms.selectByValue("RS1"); + + final List selectedAlgs = algorithms.getAllSelectedOptions() + .stream() + .map(WebElement::getText) + .collect(Collectors.toList()); + + assertThat(selectedAlgs, notNullValue()); + assertThat(selectedAlgs, hasSize(3)); + + try { + algorithms.selectByValue("something-bad"); + } catch (NoSuchElementException e) { + assertThat(e.getMessage(), containsString("Cannot locate option with value: something-bad")); + } + + assertThat(getPolicyPage().isSaveButtonEnabled(), is(true)); + assertThat(getPolicyPage().isCancelButtonEnabled(), is(true)); + + getPolicyPage().clickSaveButton(); + + assertThat(getPolicyPage().isSaveButtonEnabled(), is(false)); + assertThat(getPolicyPage().isCancelButtonEnabled(), is(false)); + } + + public void checkAttestationConveyancePreference() { + // default not specified + AttestationConveyancePreference attestation = getPolicyPage().getAttestationConveyancePreference(); + assertThat(attestation, nullValue()); + + // Direct + getPolicyPage().setAttestationConveyancePreference(AttestationConveyancePreference.DIRECT); + getPolicyPage().clickSaveButton(); + + attestation = getPolicyPage().getAttestationConveyancePreference(); + assertThat(attestation, notNullValue()); + assertThat(attestation, is(AttestationConveyancePreference.DIRECT)); + + // Indirect + getPolicyPage().setAttestationConveyancePreference(AttestationConveyancePreference.INDIRECT); + getPolicyPage().clickSaveButton(); + + attestation = getPolicyPage().getAttestationConveyancePreference(); + assertThat(attestation, notNullValue()); + assertThat(attestation, is(AttestationConveyancePreference.INDIRECT)); + + // None + getPolicyPage().setAttestationConveyancePreference(AttestationConveyancePreference.NONE); + getPolicyPage().clickSaveButton(); + + attestation = getPolicyPage().getAttestationConveyancePreference(); + assertThat(attestation, notNullValue()); + assertThat(attestation, is(AttestationConveyancePreference.NONE)); + + try { + getPolicyPage().setAttestationConveyancePreference(AttestationConveyancePreference.ENTERPRISE); + Assert.fail("We don't support 'Enterprise' mode at this moment"); + } catch (NoSuchElementException e) { + } + } + + protected void checkAuthenticatorAttachment() { + AuthenticatorAttachment attachment = getPolicyPage().getAuthenticatorAttachment(); + assertThat(attachment, nullValue()); + + // Cross-platform + getPolicyPage().setAuthenticatorAttachment(AuthenticatorAttachment.CROSS_PLATFORM); + getPolicyPage().clickSaveButton(); + + attachment = getPolicyPage().getAuthenticatorAttachment(); + assertThat(attachment, notNullValue()); + assertThat(attachment, is(AuthenticatorAttachment.CROSS_PLATFORM)); + + // Platform + getPolicyPage().setAuthenticatorAttachment(AuthenticatorAttachment.PLATFORM); + getPolicyPage().clickSaveButton(); + + attachment = getPolicyPage().getAuthenticatorAttachment(); + assertThat(attachment, notNullValue()); + assertThat(attachment, is(AuthenticatorAttachment.PLATFORM)); + } + + protected void checkResidentKey() { + PropertyRequirement requireResidentKey = getPolicyPage().requireResidentKey(); + assertThat(requireResidentKey, notNullValue()); + assertThat(requireResidentKey, is(PropertyRequirement.NOT_SPECIFIED)); + + getPolicyPage().requireResidentKey(YES); + getPolicyPage().clickSaveButton(); + + // Yes + requireResidentKey = getPolicyPage().requireResidentKey(); + assertThat(requireResidentKey, notNullValue()); + assertThat(requireResidentKey, is(YES)); + + getPolicyPage().requireResidentKey(NO); + getPolicyPage().clickSaveButton(); + + // Null + getPolicyPage().requireResidentKey(null); + assertThat(getPolicyPage().isSaveButtonEnabled(), is(false)); + + // Not specified + getPolicyPage().requireResidentKey(PropertyRequirement.NOT_SPECIFIED); + assertThat(getPolicyPage().isSaveButtonEnabled(), is(true)); + getPolicyPage().clickSaveButton(); + + // No + getPolicyPage().requireResidentKey(NO); + getPolicyPage().clickSaveButton(); + + requireResidentKey = getPolicyPage().requireResidentKey(); + assertThat(requireResidentKey, notNullValue()); + assertThat(requireResidentKey, is(NO)); + } + + protected void checkUserVerification() { + UserVerificationRequirement userVerification = getPolicyPage().getUserVerification(); + assertThat(userVerification, nullValue()); + + // Preferred + getPolicyPage().setUserVerification(UserVerificationRequirement.PREFERRED); + getPolicyPage().clickSaveButton(); + + userVerification = getPolicyPage().getUserVerification(); + assertThat(userVerification, notNullValue()); + assertThat(userVerification, is(UserVerificationRequirement.PREFERRED)); + + // Required + getPolicyPage().setUserVerification(UserVerificationRequirement.REQUIRED); + getPolicyPage().clickSaveButton(); + + userVerification = getPolicyPage().getUserVerification(); + assertThat(userVerification, notNullValue()); + assertThat(userVerification, is(UserVerificationRequirement.REQUIRED)); + + // Discouraged + getPolicyPage().setUserVerification(UserVerificationRequirement.DISCOURAGED); + getPolicyPage().clickSaveButton(); + + userVerification = getPolicyPage().getUserVerification(); + assertThat(userVerification, notNullValue()); + assertThat(userVerification, is(UserVerificationRequirement.DISCOURAGED)); + } + + protected void checkTimeout() { + int timeout = getPolicyPage().getTimeout(); + assertThat(timeout, is(0)); + + getPolicyPage().setTimeout(10); + getPolicyPage().clickSaveButton(); + + timeout = getPolicyPage().getTimeout(); + assertThat(timeout, is(10)); + + getPolicyPage().setTimeout(-10); + getPolicyPage().clickSaveButton(); + assertAlertDanger(); + + timeout = getPolicyPage().getTimeout(); + assertThat(timeout, is(-10)); + + getPolicyPage().navigateTo(); + waitForPageToLoad(); + + timeout = getPolicyPage().getTimeout(); + assertThat(timeout, is(10)); + + getPolicyPage().setTimeout(1000000); + getPolicyPage().clickSaveButton(); + assertAlertDanger(); + + getPolicyPage().setTimeout(500); + getPolicyPage().clickSaveButton(); + + timeout = getPolicyPage().getTimeout(); + assertThat(timeout, is(500)); + } + + protected void checkAvoidSameAuthenticatorRegistration() { + boolean avoidSameAuthenticatorRegistration = getPolicyPage().avoidSameAuthenticatorRegistration(); + assertThat(avoidSameAuthenticatorRegistration, is(false)); + + getPolicyPage().avoidSameAuthenticatorRegister(true); + assertThat(getPolicyPage().isSaveButtonEnabled(), is(true)); + getPolicyPage().clickSaveButton(); + + avoidSameAuthenticatorRegistration = getPolicyPage().avoidSameAuthenticatorRegistration(); + assertThat(avoidSameAuthenticatorRegistration, is(true)); + + getPolicyPage().avoidSameAuthenticatorRegister(false); + getPolicyPage().clickSaveButton(); + + avoidSameAuthenticatorRegistration = getPolicyPage().avoidSameAuthenticatorRegistration(); + assertThat(avoidSameAuthenticatorRegistration, is(false)); + } + + protected void checkAcceptableAaguid() { + WebAuthnPolicyPage.MultivaluedAcceptableAaguid acceptableAaguid = getPolicyPage().getAcceptableAaguid(); + assertThat(acceptableAaguid, notNullValue()); + + List items = getAcceptableAaguid(getPolicyPage().getAcceptableAaguid()); + assertThat(items, notNullValue()); + + acceptableAaguid.addItem(ALL_ONE_AAGUID); + getPolicyPage().clickSaveButton(); + + items = getAcceptableAaguid(getPolicyPage().getAcceptableAaguid()); + + assertThat(items, notNullValue()); + assertThat(items.isEmpty(), is(false)); + assertThat(items.contains(ALL_ONE_AAGUID), is(true)); + + final String YUBIKEY_5_AAGUID = "cb69481e-8ff7-4039-93ec-0a2729a154a8"; + final String YUBICO_AAGUID = "f8a011f3-8c0a-4d15-8006-17111f9edc7d"; + + acceptableAaguid.addItem(YUBIKEY_5_AAGUID); + acceptableAaguid.addItem(YUBICO_AAGUID); + items = getAcceptableAaguid(getPolicyPage().getAcceptableAaguid()); + + assertThat(items, notNullValue()); + assertThat(items, hasSize(3)); + + getPolicyPage().clickSaveButton(); + acceptableAaguid.removeItem(0); + items = getAcceptableAaguid(getPolicyPage().getAcceptableAaguid()); + + assertThat(items, notNullValue()); + assertThat(items, hasSize(2)); + assertThat(items.contains(YUBICO_AAGUID), is(true)); + assertThat(items.contains(YUBIKEY_5_AAGUID), is(true)); + assertThat(items.contains(ALL_ONE_AAGUID), is(false)); + + assertThat(getPolicyPage().isSaveButtonEnabled(), is(true)); + getPolicyPage().clickSaveButton(); + pause(100); + } + + protected List getAcceptableAaguid(WebAuthnPolicyPage.MultivaluedAcceptableAaguid acceptableAaguid) { + return acceptableAaguid.getItems() + .stream() + .map(UIUtils::getTextInputValue) + .collect(Collectors.toList()); + } +} diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/WebAuthnPolicyPasswordlessSettingsTest.java b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/WebAuthnPolicyPasswordlessSettingsTest.java new file mode 100644 index 0000000000..267a684bb6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/WebAuthnPolicyPasswordlessSettingsTest.java @@ -0,0 +1,219 @@ +/* + * 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.admin; + +import com.webauthn4j.data.AttestationConveyancePreference; +import com.webauthn4j.data.AuthenticatorAttachment; +import com.webauthn4j.data.UserVerificationRequirement; +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Test; +import org.keycloak.models.Constants; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.updaters.RealmAttributeUpdater; +import org.keycloak.testsuite.webauthn.pages.WebAuthnPolicyPage; +import org.keycloak.testsuite.webauthn.pages.WebAuthnPolicyPasswordlessPage; +import org.keycloak.testsuite.webauthn.updaters.AbstractWebAuthnRealmUpdater; +import org.keycloak.testsuite.webauthn.updaters.PasswordLessRealmAttributeUpdater; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static com.webauthn4j.data.AttestationConveyancePreference.DIRECT; +import static com.webauthn4j.data.AuthenticatorAttachment.PLATFORM; +import static com.webauthn4j.data.UserVerificationRequirement.PREFERRED; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; +import static org.keycloak.models.Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; + +/** + * @author Martin Bartos + */ +public class WebAuthnPolicyPasswordlessSettingsTest extends AbstractWebAuthnPolicySettingsTest { + + @Page + WebAuthnPolicyPasswordlessPage webAuthnPolicyPasswordlessPage; + + @Override + protected WebAuthnPolicyPage getPolicyPage() { + return webAuthnPolicyPasswordlessPage; + } + + @Override + protected AbstractWebAuthnRealmUpdater getWebAuthnRealmUpdater() { + return new PasswordLessRealmAttributeUpdater(testRealmResource()); + } + + @Test + public void policySettingsWithExternalProperties() throws IOException { + try (RealmAttributeUpdater rau = updateWebAuthnPolicy( + "rpNamePasswordless", + Collections.singletonList("RS256"), + DIRECT.getValue(), + PLATFORM.getValue(), + "Yes", + "1234", + PREFERRED.getValue(), + Collections.singletonList(ALL_ZERO_AAGUID)) + ) { + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessSignatureAlgorithms(), hasItems("RS256")); + assertThat(realm.getWebAuthnPolicyPasswordlessAttestationConveyancePreference(), is(DIRECT.getValue())); + assertThat(realm.getWebAuthnPolicyPasswordlessAuthenticatorAttachment(), is(PLATFORM.getValue())); + assertThat(realm.getWebAuthnPolicyPasswordlessRequireResidentKey(), is("Yes")); + assertThat(realm.getWebAuthnPolicyPasswordlessRpId(), is("1234")); + assertThat(realm.getWebAuthnPolicyPasswordlessUserVerificationRequirement(), is(PREFERRED.getValue())); + assertThat(realm.getWebAuthnPolicyPasswordlessAcceptableAaguids(), hasItems(ALL_ZERO_AAGUID)); + } + } + + @Test + public void wrongSignatureAlgorithm() throws IOException { + checkWrongSignatureAlgorithm(); + } + + @Test + public void algorithmsValuesSetUpInAdminConsole() { + checkSignatureAlgorithms(); + + final RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + final List realmSignatureAlgs = realm.getWebAuthnPolicyPasswordlessSignatureAlgorithms(); + assertThat(realmSignatureAlgs, notNullValue()); + assertThat(realmSignatureAlgs, hasSize(3)); + assertThat(realmSignatureAlgs, contains("ES256", "ES384", "RS1")); + } + + @Test + public void rpValuesSetUpInAdminConsole() { + checkRpEntityValues(); + + final RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessRpEntityName(), is(Constants.DEFAULT_WEBAUTHN_POLICY_RP_ENTITY_NAME)); + assertThat(realm.getWebAuthnPolicyPasswordlessRpId(), is("rpId123")); + } + + @Test + public void attestationConveyancePreferenceSettings() { + checkAttestationConveyancePreference(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessAttestationConveyancePreference(), is(AttestationConveyancePreference.NONE.getValue())); + + realm.setWebAuthnPolicyPasswordlessAttestationConveyancePreference(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessAttestationConveyancePreference(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void authenticatorAttachmentSettings() { + checkAuthenticatorAttachment(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessAuthenticatorAttachment(), is(AuthenticatorAttachment.PLATFORM.getValue())); + + realm.setWebAuthnPolicyPasswordlessAuthenticatorAttachment(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessAuthenticatorAttachment(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void requireResidentKeySettings() { + checkResidentKey(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessRequireResidentKey(), is("No")); + + realm.setWebAuthnPolicyPasswordlessRequireResidentKey(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessRequireResidentKey(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void userVerificationRequirementSettings() { + checkUserVerification(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessUserVerificationRequirement(), is(UserVerificationRequirement.DISCOURAGED.getValue())); + + realm.setWebAuthnPolicyPasswordlessUserVerificationRequirement(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessUserVerificationRequirement(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void timeoutSettings() { + checkTimeout(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyPasswordlessCreateTimeout(), is(500)); + } + + @Test + public void avoidSameAuthenticatorRegistrationSettings() { + checkAvoidSameAuthenticatorRegistration(); + + final RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + assertThat(realm.isWebAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister(), is(false)); + } + + @Test + public void acceptableAaguidSettings() { + checkAcceptableAaguid(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + assertThat(realm.getWebAuthnPolicyPasswordlessAcceptableAaguids(), is(getAcceptableAaguid(getPolicyPage().getAcceptableAaguid()))); + } +} diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/WebAuthnPolicySettingsTest.java b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/WebAuthnPolicySettingsTest.java new file mode 100644 index 0000000000..6a1e66dc6d --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/admin/WebAuthnPolicySettingsTest.java @@ -0,0 +1,223 @@ +/* + * 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.admin; + +import com.webauthn4j.data.AttestationConveyancePreference; +import com.webauthn4j.data.AuthenticatorAttachment; +import com.webauthn4j.data.UserVerificationRequirement; +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Test; +import org.keycloak.models.Constants; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.updaters.RealmAttributeUpdater; +import org.keycloak.testsuite.webauthn.pages.WebAuthnPolicyPage; +import org.keycloak.testsuite.webauthn.updaters.AbstractWebAuthnRealmUpdater; +import org.keycloak.testsuite.webauthn.updaters.WebAuthnRealmAttributeUpdater; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static com.webauthn4j.data.AttestationConveyancePreference.INDIRECT; +import static com.webauthn4j.data.AuthenticatorAttachment.CROSS_PLATFORM; +import static com.webauthn4j.data.UserVerificationRequirement.PREFERRED; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; +import static org.keycloak.models.Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; + +/** + * @author Martin Bartos + */ +public class WebAuthnPolicySettingsTest extends AbstractWebAuthnPolicySettingsTest { + + @Page + WebAuthnPolicyPage webAuthnPolicyPage; + + @Override + protected WebAuthnPolicyPage getPolicyPage() { + return webAuthnPolicyPage; + } + + @Override + protected AbstractWebAuthnRealmUpdater getWebAuthnRealmUpdater() { + return new WebAuthnRealmAttributeUpdater(testRealmResource()); + } + + @Test + public void policySettingsWithExternalProperties() throws IOException { + try (RealmAttributeUpdater rau = updateWebAuthnPolicy( + "rpName", + Collections.singletonList("ES256"), + INDIRECT.getValue(), + CROSS_PLATFORM.getValue(), + "No", + null, + PREFERRED.getValue(), + Collections.singletonList(ALL_ZERO_AAGUID)) + ) { + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicySignatureAlgorithms(), hasItems("ES256")); + assertThat(realm.getWebAuthnPolicyAttestationConveyancePreference(), is(INDIRECT.getValue())); + assertThat(realm.getWebAuthnPolicyAuthenticatorAttachment(), is(CROSS_PLATFORM.getValue())); + assertThat(realm.getWebAuthnPolicyRequireResidentKey(), is("No")); + assertThat(realm.getWebAuthnPolicyRpId(), is("")); + assertThat(realm.getWebAuthnPolicyUserVerificationRequirement(), is(PREFERRED.getValue())); + assertThat(realm.getWebAuthnPolicyAcceptableAaguids(), hasItems(ALL_ZERO_AAGUID)); + } + } + + @Test + public void wrongSignatureAlgorithm() throws IOException { + checkWrongSignatureAlgorithm(); + } + + @Test + public void algorithmsValuesSetUpInAdminConsole() { + checkSignatureAlgorithms(); + + final RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + final List realmSignatureAlgs = realm.getWebAuthnPolicySignatureAlgorithms(); + assertThat(realmSignatureAlgs, notNullValue()); + assertThat(realmSignatureAlgs, hasSize(3)); + assertThat(realmSignatureAlgs, contains("ES256", "ES384", "RS1")); + } + + @Test + public void rpValuesSetUpInAdminConsole() { + checkRpEntityValues(); + + final RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyRpEntityName(), is(Constants.DEFAULT_WEBAUTHN_POLICY_RP_ENTITY_NAME)); + assertThat(realm.getWebAuthnPolicyRpId(), is("rpId123")); + } + + @Test + public void attestationConveyancePreferenceSettings() { + checkAttestationConveyancePreference(); + + // Realm + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyAttestationConveyancePreference(), is(AttestationConveyancePreference.NONE.getValue())); + + realm.setWebAuthnPolicyAttestationConveyancePreference(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyAttestationConveyancePreference(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void authenticatorAttachmentSettings() { + checkAuthenticatorAttachment(); + + // Realm + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyAuthenticatorAttachment(), is(AuthenticatorAttachment.PLATFORM.getValue())); + + realm.setWebAuthnPolicyAuthenticatorAttachment(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyAuthenticatorAttachment(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void requireResidentKeySettings() { + checkResidentKey(); + + // Realm + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyRequireResidentKey(), is("No")); + + realm.setWebAuthnPolicyRequireResidentKey(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyRequireResidentKey(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void userVerificationRequirementSettings() { + checkUserVerification(); + + // Realm + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyUserVerificationRequirement(), is(UserVerificationRequirement.DISCOURAGED.getValue())); + + realm.setWebAuthnPolicyUserVerificationRequirement(null); + testRealmResource().update(realm); + + realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyUserVerificationRequirement(), is(DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED)); + } + + @Test + public void timeoutSettings() { + checkTimeout(); + + // Realm + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + + assertThat(realm.getWebAuthnPolicyCreateTimeout(), is(500)); + } + + @Test + public void avoidSameAuthenticatorRegistrationSettings() { + checkAvoidSameAuthenticatorRegistration(); + + final RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + assertThat(realm.isWebAuthnPolicyAvoidSameAuthenticatorRegister(), is(false)); + } + + @Test + public void acceptableAaguidSettings() { + checkAcceptableAaguid(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + assertThat(realm, notNullValue()); + assertThat(realm.getWebAuthnPolicyAcceptableAaguids(), is(getAcceptableAaguid(getPolicyPage().getAcceptableAaguid()))); + } +} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/webauthn-policy-passwordless.html b/themes/src/main/resources/theme/base/admin/resources/partials/webauthn-policy-passwordless.html index dbb1e0e83b..b15dfec6e4 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/webauthn-policy-passwordless.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/webauthn-policy-passwordless.html @@ -144,7 +144,7 @@
- +