parent
cc88fb2daa
commit
47208b7a20
3 changed files with 86 additions and 11 deletions
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue