Extend and fix tests for Resident Keys for WebAuthn

Closes #9796
This commit is contained in:
Martin Bartoš 2022-01-26 15:04:39 +01:00 committed by Marek Posolda
parent cc88fb2daa
commit 47208b7a20
3 changed files with 86 additions and 11 deletions

View file

@ -22,11 +22,15 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.keycloak.admin.client.resource.UserResource; 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.WebAuthnPasswordlessRegisterFactory;
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory; import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.util.SecretGenerator; import org.keycloak.common.util.SecretGenerator;
import org.keycloak.models.credential.WebAuthnCredentialModel; import org.keycloak.models.credential.WebAuthnCredentialModel;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
@ -131,6 +135,7 @@ public abstract class AbstractWebAuthnVirtualTest extends AbstractTestRealmKeycl
if (isPasswordless()) { if (isPasswordless()) {
makePasswordlessRequiredActionDefault(realmRepresentation); makePasswordlessRequiredActionDefault(realmRepresentation);
switchExecutionInBrowserFormToPasswordless(realmRepresentation);
} }
testRealms.add(realmRepresentation); testRealms.add(realmRepresentation);
@ -330,6 +335,37 @@ public abstract class AbstractWebAuthnVirtualTest extends AbstractTestRealmKeycl
webAuthnPasswordlessProvider.setDefaultAction(true); webAuthnPasswordlessProvider.setDefaultAction(true);
} }
// Switch WebAuthn authenticator with Passwordless authenticator in browser flow
protected void switchExecutionInBrowserFormToPasswordless(RealmRepresentation realm) {
List<AuthenticationFlowRepresentation> flows = realm.getAuthenticationFlows();
assertThat(flows, notNullValue());
AuthenticationFlowRepresentation browserForm = flows.stream()
.filter(f -> f.getAlias().equals("browser-webauthn-forms"))
.findFirst()
.orElse(null);
assertThat("Cannot find 'browser-webauthn-forms' flow", browserForm, notNullValue());
flows.removeIf(f -> f.getAlias().equals(browserForm.getAlias()));
List<AuthenticationExecutionExportRepresentation> browserFormExecutions = browserForm.getAuthenticationExecutions();
assertThat("Flow 'browser-webauthn-forms' doesn't have any executions", browserForm, notNullValue());
AuthenticationExecutionExportRepresentation webAuthn = browserFormExecutions.stream()
.filter(f -> WebAuthnAuthenticatorFactory.PROVIDER_ID.equals(f.getAuthenticator()))
.findFirst()
.orElse(null);
assertThat("Cannot find WebAuthn execution in Browser flow", webAuthn, notNullValue());
browserFormExecutions.removeIf(f -> webAuthn.getAuthenticator().equals(f.getAuthenticator()));
webAuthn.setAuthenticator(WebAuthnPasswordlessAuthenticatorFactory.PROVIDER_ID);
browserFormExecutions.add(webAuthn);
browserForm.setAuthenticationExecutions(browserFormExecutions);
flows.add(browserForm);
realm.setAuthenticationFlows(flows);
}
protected void logout() { protected void logout() {
try { try {
appPage.open(); appPage.open();

View file

@ -17,8 +17,9 @@
package org.keycloak.testsuite.webauthn.registration; package org.keycloak.testsuite.webauthn.registration;
import org.junit.Ignore; import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.webauthn.AbstractWebAuthnVirtualTest; import org.keycloak.testsuite.webauthn.AbstractWebAuthnVirtualTest;
import org.keycloak.testsuite.webauthn.utils.PropertyRequirement; import org.keycloak.testsuite.webauthn.utils.PropertyRequirement;
import org.keycloak.testsuite.webauthn.utils.WebAuthnRealmData; import org.keycloak.testsuite.webauthn.utils.WebAuthnRealmData;
@ -26,10 +27,15 @@ import org.openqa.selenium.virtualauthenticator.Credential;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is; 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.MatcherAssert.assertThat;
import static org.keycloak.WebAuthnConstants.OPTION_REQUIRED;
import static org.keycloak.models.Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED;
import static org.keycloak.testsuite.webauthn.authenticators.DefaultVirtualAuthOptions.DEFAULT_RESIDENT_KEY;
/** /**
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a> * @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
@ -46,7 +52,6 @@ public class ResidentKeyRegisterTest extends AbstractWebAuthnVirtualTest {
assertResidentKey(true, PropertyRequirement.NO, true); assertResidentKey(true, PropertyRequirement.NO, true);
} }
@Ignore("Not working")
@Test @Test
public void residentKeyRequiredCorrect() { public void residentKeyRequiredCorrect() {
assertResidentKey(true, PropertyRequirement.YES, true); assertResidentKey(true, PropertyRequirement.YES, true);
@ -58,29 +63,51 @@ public class ResidentKeyRegisterTest extends AbstractWebAuthnVirtualTest {
} }
private void assertResidentKey(boolean shouldSuccess, PropertyRequirement requirement, boolean hasResidentKey) { private void assertResidentKey(boolean shouldSuccess, PropertyRequirement requirement, boolean hasResidentKey) {
Credential credential; final String userVerification;
getVirtualAuthManager().useAuthenticator(getDefaultAuthenticatorOptions().setHasResidentKey(hasResidentKey));
if (hasResidentKey) { if (hasResidentKey) {
credential = getDefaultResidentKeyCredential(); getVirtualAuthManager().useAuthenticator(DEFAULT_RESIDENT_KEY.getOptions());
userVerification = OPTION_REQUIRED;
} else { } else {
credential = getDefaultNonResidentKeyCredential(); userVerification = DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED;
} }
getVirtualAuthManager().getCurrent().getAuthenticator().addCredential(credential);
try (Closeable u = getWebAuthnRealmUpdater() try (Closeable u = getWebAuthnRealmUpdater()
.setWebAuthnPolicyRpEntityName("localhost")
.setWebAuthnPolicyRequireResidentKey(requirement.getValue()) .setWebAuthnPolicyRequireResidentKey(requirement.getValue())
.setWebAuthnPolicyUserVerificationRequirement(userVerification)
.update()) { .update()) {
WebAuthnRealmData realmData = new WebAuthnRealmData(testRealm().toRepresentation(), isPasswordless()); WebAuthnRealmData realmData = new WebAuthnRealmData(testRealm().toRepresentation(), isPasswordless());
assertThat(realmData.getRequireResidentKey(), containsString(requirement.getValue())); assertThat(realmData.getRpEntityName(), is("localhost"));
assertThat(realmData.getRequireResidentKey(), is(requirement.getValue()));
assertThat(realmData.getUserVerificationRequirement(), is(userVerification));
registerDefaultUser(shouldSuccess); registerDefaultUser(shouldSuccess);
displayErrorMessageIfPresent(); displayErrorMessageIfPresent();
assertThat(webAuthnErrorPage.isCurrent(), is(!shouldSuccess)); if (!shouldSuccess) {
assertThat(webAuthnErrorPage.isCurrent(), is(true));
return;
} else {
assertThat(webAuthnErrorPage.isCurrent(), is(false));
}
final List<Credential> credentials = getVirtualAuthManager().getCurrent().getAuthenticator().getCredentials();
assertThat(credentials, notNullValue());
assertThat(credentials, not(Matchers.empty()));
if (PropertyRequirement.YES.equals(requirement)) {
final String userId = ApiUtil.findUserByUsername(testRealm(), USERNAME).getId();
final Credential credential = credentials.get(0);
assertThat(credential.isResidentCredential(), is(hasResidentKey));
assertThat(new String(credential.getUserHandle()), is(userId));
}
logout();
authenticateDefaultUser();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e.getCause()); throw new RuntimeException(e.getCause());
} }

View file

@ -22,6 +22,18 @@
"webAuthnPolicyRpEntityName": "keycloak-webauthn-2FA", "webAuthnPolicyRpEntityName": "keycloak-webauthn-2FA",
"webAuthnPolicyCreateTimeout": 60, "webAuthnPolicyCreateTimeout": 60,
"webAuthnPolicyAvoidSameAuthenticatorRegister": true, "webAuthnPolicyAvoidSameAuthenticatorRegister": true,
"webAuthnPolicyPasswordlessSignatureAlgorithms": [
"ES256",
"RS256",
"RS1"
],
"webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
"webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
"webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
"webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
"webAuthnPolicyPasswordlessRpEntityName": "keycloak-webauthn-passwordless-2FA",
"webAuthnPolicyPasswordlessCreateTimeout": 60,
"webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": true,
"smtpServer": { "smtpServer": {
"from": "auto@keycloak.org", "from": "auto@keycloak.org",
"host": "localhost", "host": "localhost",