[java-idp-plugin-webauthn] branch main updated: Add username collection step if passwordless enabled
Phil Smart
philip.smart at jisc.ac.uk
Mon Jan 22 09:56:51 UTC 2024
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-webauthn.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-webauthn.git;a=commit;h=a68b9bb0ffd7e968942579b1549feb7d882428b0
The following commit(s) were added to refs/heads/main by this push:
new a68b9bb Add username collection step if passwordless enabled
a68b9bb is described below
commit a68b9bb0ffd7e968942579b1549feb7d882428b0
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Jan 22 09:56:49 2024 +0000
Add username collection step if passwordless enabled
---
.../context/logic/UsernamelessFlowEnabled.java | 84 ++++++++++
.../webauthn/impl/ExtractUsernameFromForm.java | 169 +++++++++++++++++++++
.../PopulateWebAuthnAuthenticationContext.java | 60 +-------
.../idp/flows/authn/WebAuthn/webauthn-beans.xml | 11 +-
.../idp/flows/authn/WebAuthn/webauthn-flow.xml | 73 ++++++---
.../authn/webauthn/conf/authn/webauthn.properties | 15 +-
6 files changed, 330 insertions(+), 82 deletions(-)
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/UsernamelessFlowEnabled.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/UsernamelessFlowEnabled.java
new file mode 100644
index 0000000..0f8ab01
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/UsernamelessFlowEnabled.java
@@ -0,0 +1,84 @@
+/*
+ * 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 net.shibboleth.idp.plugin.authn.webauthn.context.logic;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Predicate to determine if the user is required to enter their username or not. True implies a usernameless flow
+ * whilst false implies a passwordless flow.
+ */
+public class UsernamelessFlowEnabled extends AbstractInitializableComponent
+ implements Predicate<ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(IsDiscoverableCredentialRequired.class);
+
+ /**
+ * Determines if we want a usernameless flow (true), or a passwordless flow (false).
+ * Default is false (passwordless).
+ */
+ @Nonnull private Predicate<ProfileRequestContext> enabled;
+
+ /** Constructor.*/
+ public UsernamelessFlowEnabled() {
+ enabled = PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set a predicate that determines if we want a usernameless flow (true), or a passwordless flow (false).
+ *
+ * @param predicate the predicate
+ */
+ public void setEnabled(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+ checkSetterPreconditions();
+ enabled = Constraint.isNotNull(predicate, "Enabled predicate can not be null");
+ }
+
+ /**
+ * Set a flag that determines if we want a usernameless flow (true), or a passwordless flow (false).
+ *
+ * @param flag the flag to set
+ */
+ public void setEnabled(final boolean flag) {
+ checkSetterPreconditions();
+ enabled = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ if (input == null) {
+ log.trace("Profile context was null, assuming username is required");
+ return false;
+ }
+ final boolean usernamelessEnabled = enabled.test(input);
+ log.debug("{}", usernamelessEnabled ? "Usernameless authentication flow initiated" :
+ "Passwordless authentication flow initiated");
+ return usernamelessEnabled;
+ }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractUsernameFromForm.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractUsernameFromForm.java
new file mode 100644
index 0000000..9d1afa0
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractUsernameFromForm.java
@@ -0,0 +1,169 @@
+/*
+ * 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 net.shibboleth.idp.plugin.authn.webauthn.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.authn.AbstractExtractionAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * An action to populate a username into the {@link WebAuthnAuthenticationContext}.
+ *
+ * TODO FINISH. Really maybe should be similar to CheckPasswordlessEnrollment from Duo
+ */
+public class ExtractUsernameFromForm extends AbstractExtractionAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractUsernameFromForm.class);
+
+ /** Strategy used to locate the {@link WebAuthnAuthenticationContext} to operate on. */
+ @Nonnull private Function<ProfileRequestContext,WebAuthnAuthenticationContext> webAuthnContextLookupStrategy;
+
+ /** Form parameter name to carry username. */
+ @Nonnull @NotEmpty private String usernameFieldName;
+
+ /** Parameter name for SSO bypass. */
+ @Nonnull @NotEmpty private String ssoBypassFieldName;
+
+ /** Context to operate on. */
+ @NonnullBeforeExec private WebAuthnAuthenticationContext webAuthnContext;
+
+ /** Constructor.*/
+ public ExtractUsernameFromForm() {
+ usernameFieldName = "j_username";
+ ssoBypassFieldName = "donotcache";
+
+ webAuthnContextLookupStrategy =
+ new ChildContextLookup<>(WebAuthnAuthenticationContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class));
+ }
+
+ /**
+ * Set the strategy used to locate the {@link WebAuthnAuthenticationContext} to operate on.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setWebAuthnContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,WebAuthnAuthenticationContext> strategy) {
+ checkSetterPreconditions();
+
+ webAuthnContextLookupStrategy =
+ Constraint.isNotNull(strategy, "WebAuthnAuthenticationContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the name of the form field to carry the username.
+ *
+ * @param name field name
+ */
+ public void setUsernameFieldName(@Nonnull final String name) {
+ checkSetterPreconditions();
+
+ usernameFieldName = Constraint.isNotNull(StringSupport.trimOrNull(name) ,
+ "Username form field name cannot be null or empty");
+ }
+
+ /**
+ * Set the SSO bypass parameter name.
+ *
+ * @param fieldName the SSO bypass parameter name
+ */
+ public void setSSOBypassFieldName(@Nonnull @NotEmpty final String fieldName) {
+ checkSetterPreconditions();
+
+ ssoBypassFieldName = Constraint.isNotNull(
+ StringSupport.trimOrNull(fieldName), "SSO Bypass field name cannot be null or empty.");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+ return false;
+ }
+
+ webAuthnContext = webAuthnContextLookupStrategy.apply(profileRequestContext);
+ if (webAuthnContext == null) {
+ log.debug("{} No WebAuthnContext found, nothing to do", getLogPrefix());
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final String username = getUsernameFromForm(profileRequestContext, authenticationContext);
+ if (username == null) {
+ log.warn("{} Unable to find username in HTTP request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.UNKNOWN_USERNAME);
+ return;
+ }
+ log.trace("{} Populating username '{}' from form",getLogPrefix(), username);
+ webAuthnContext.setUsername(username);
+
+ }
+
+ /**
+ * Gets the username from a form submission.
+ *
+ * <p>Also processes do-not-cache instruction.</p>
+ *
+ * @param profileRequestContext profile request context
+ * @param authenticationContext authentication context
+ *
+ * @return submitted username, after applying any configured transforms
+ */
+ @Nullable private String getUsernameFromForm(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final HttpServletRequest request = getHttpServletRequest();
+ if (request != null) {
+ // FIXME This has not been thought out here.
+ final String donotcache = request.getParameter(ssoBypassFieldName);
+ if (donotcache != null && "1".equals(donotcache)) {
+ log.debug("{} Recording do-not-cache instruction in authentication context", getLogPrefix());
+ authenticationContext.setResultCacheable(false);
+ } else {
+ authenticationContext.setResultCacheable(true);
+ }
+ return applyTransforms(request.getParameter(usernameFieldName));
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
index 2089ede..0544080 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
@@ -16,7 +16,6 @@
package net.shibboleth.idp.plugin.authn.webauthn.impl;
import java.util.function.Function;
-import java.util.function.Predicate;
import javax.annotation.Nonnull;
@@ -29,9 +28,6 @@ import org.slf4j.Logger;
import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
-import net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.PredicateSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
/**
@@ -53,12 +49,6 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
@Nonnull
private final Function<ProfileRequestContext,WebAuthnAuthenticationContext> webauthnAuthContextCreationStrategy;
- /** Lookup strategy for username to extract. */
- @Nonnull private Function<ProfileRequestContext, String> usernameLookupStrategy;
-
- /** Is the username required?*/
- private Predicate<ProfileRequestContext> usernameRequiredPredicate;
-
/** Constructor.*/
public PopulateWebAuthnAuthenticationContext() {
@@ -66,66 +56,22 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
webauthnAuthContextCreationStrategy =
new ChildContextLookup<>(WebAuthnAuthenticationContext.class, true).
compose(new ChildContextLookup<>(AuthenticationContext.class));
-
- usernameLookupStrategy = new CanonicalUsernameLookupStrategy();
- usernameRequiredPredicate = PredicateSupport.alwaysTrue();
- }
-
- /**
- * @param flag The usernameRequired to set.
- */
- public void setUsernameRequired(final boolean flag) {
- checkSetterPreconditions();
- usernameRequiredPredicate = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
- }
-
- /**
- * @param usernameRequiredPredicate The usernameRequiredPredicate to set.
- */
- public void setUsernameRequiredPredicate(@Nonnull final Predicate<ProfileRequestContext> predicate){
- checkSetterPreconditions();
- usernameRequiredPredicate = Constraint.isNotNull(predicate, "Username required predicate can not be null");
- }
-
- /**
- * Set the lookup strategy to use for the username to use if we are not using a discoverable credential.
- *
- * @param strategy lookup strategy
- */
- public void setUsernameLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, String> strategy) {
- checkSetterPreconditions();
- usernameLookupStrategy = Constraint.isNotNull(strategy, "Username lookup strategy cannot be null");
}
-
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final AuthenticationContext authenticationContext) {
- final WebAuthnAuthenticationContext context = webauthnAuthContextCreationStrategy.apply(profileRequestContext);
+ final WebAuthnAuthenticationContext context =
+ webauthnAuthContextCreationStrategy.apply(profileRequestContext);
if (context == null) {
log.error("{} Error creating WebauthnAuthenticationContext", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return;
}
-
- //TODO this should not go in this action? Unless we always assume username input step before webauthn
- final String username = usernameLookupStrategy.apply(profileRequestContext);
- //username = "philsmart";
- if (username == null && usernameRequiredPredicate.test(profileRequestContext)) {
- log.error("{} Error creating WebauthnAuthenticationContext, no username found", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return;
- }
- if (username != null) {
- context.setUsername(username);
- log.debug("Created Webauthn authentication context for user '{}'", username);
- return;
- }
- log.debug("Created Webauthn authentication context, no previous username provided'");
+ log.debug("Created Webauthn authentication context");
}
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
index 66d44e9..f849186 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
@@ -16,8 +16,7 @@
<bean id="PopulateWebAuthnAuthenticationContext" scope="prototype"
- class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext"
- p:usernameRequiredPredicate="false">
+ class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext">
</bean>
<bean id="IsSecondFactor" scope="prototype"
@@ -26,6 +25,10 @@
getObject('shibboleth.authn.webauthn.SecondFactorOverride') : %{idp.authn.webauthn.2fa.forceSecondFactorFlow:false}}"
p:allowedPreviousFactors="%{idp.authn.webauthn.2fa.allowedPreviousFactors}"
p:enabled="%{idp.authn.webauthn.2fa.enabled:false}"/>
+
+ <bean id="IsUsernamelessFlow" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.logic.UsernamelessFlowEnabled"
+ p:enabled="%{idp.authn.webauthn.usernameless.enabled:false}"/>
<bean id="IsDiscoverableCredentialRequired" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.context.logic.IsDiscoverableCredentialRequired" />
@@ -33,6 +36,10 @@
<bean id="shibboleth.ChildLookup.WebAuthnAuthenticationContext"
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext) }" />
+
+ <bean id="ExtractUsernameFromForm" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.impl.ExtractUsernameFromForm"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
<bean id="EnsureAllowedCredentialsIsEmpty" parent="AbstractWebAuthnAuthenticationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.EnsureAllowedCredentialsIsEmpty"/>
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
index 7f8cf74..aa308e8 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
@@ -1,45 +1,84 @@
<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
parent="authn.abstract, authn/conditions">
-
- <action-state id="PopulateWebauthnContext">
- <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="DetermineSecondFactorLogin" />
- </action-state>
- <!-- Test if we are operating as a 2FA -->
+ <!-- Test if we are operating as a 2FA -->
<decision-state id="DetermineSecondFactorLogin">
<if test="IsSecondFactor.test(opensamlProfileRequestContext)"
then="SecondFactorLogin"
- else="DetermineUsernamelessOrPasswordlessLogin" />
+ else="DetermineUsernamelessFlow" />
</decision-state>
- <!-- Test if we are operating as a first (and possibly only) factor -->
- <decision-state id="DetermineUsernamelessOrPasswordlessLogin">
- <if test="IsDiscoverableCredentialRequired.test(opensamlProfileRequestContext)"
+ <!-- if usernameless flow, assume username input is not required. Otherwise prompt for username -->
+ <decision-state id="DetermineUsernamelessFlow">
+ <if test="IsUsernamelessFlow.test(opensamlProfileRequestContext)"
then="UsernamelessLogin"
else="PasswordlessLogin" />
</decision-state>
- <!-- If Usernameless: require ResidentKey, UV, UP, and provide no previous credentials-->
- <action-state id="UsernamelessLogin">
- <evaluate expression="AddUserVerificationRequired"/>
- <evaluate expression="EnsureAllowedCredentialsIsEmpty"/>
+ <!--
+ Passwordless login
+ -->
+ <action-state id="PasswordlessLogin">
+ <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
<evaluate expression="'proceed'" />
- <transition on="proceed" to="GenerateAuthenticationCeremonyOptions" />
+ <transition on="proceed" to="CollectUsernameView" />
</action-state>
+
+ <view-state id="CollectUsernameView" view="webauthn/webauthn-username-entry">
+ <on-render>
+ <evaluate expression="environment" result="viewScope.environment" />
+ <evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="viewScope.authenticationContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext))" result="viewScope.webauthnContext" />
+ <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.ui.context.RelyingPartyUIContext))" result="viewScope.rpUIContext" />
+ <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationErrorContext))" result="viewScope.authenticationErrorContext" />
+ <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationWarningContext))" result="viewScope.authenticationWarningContext" />
+ <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="viewScope.encoder" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
+ </on-render>
+ <transition on="proceed" to="PasswordlessLoginProceed" />
+
+ </view-state>
<!-- If Passwordless: do not require ResidentKey, require UV, UP, and provide previous credentials based on username -->
- <action-state id="PasswordlessLogin">
+ <action-state id="PasswordlessLoginProceed">
+ <evaluate expression="ExtractUsernameFromForm"/>
+ <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
<evaluate expression="LookupRegisteredCredentials"/>
<evaluate expression="AddUserVerificationRequired"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="GenerateAuthenticationCeremonyOptions" />
+ </action-state>
+
+ <!--
+ Usernameless login
+ -->
+
+ <!-- If Usernameless: require ResidentKey, UV, UP, and provide no previous credentials-->
+ <action-state id="UsernamelessLogin">
+ <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
+ <evaluate expression="AddUserVerificationRequired"/>
+ <evaluate expression="EnsureAllowedCredentialsIsEmpty"/>
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="GenerateAuthenticationCeremonyOptions" />
</action-state>
+ <!-- Test if we are operating as a first (and possibly only) factor -->
+ <!-- <decision-state id="DetermineUsernamelessOrPasswordlessLogin">
+ <if test="IsDiscoverableCredentialRequired.test(opensamlProfileRequestContext)"
+ then="UsernamelessLogin"
+ else="PasswordlessLogin" />
+ </decision-state> -->
+
+ <!--
+ Second Factor login
+ -->
+
<!-- If we are running after a first factor, perform 2FA only. Needs existing username -->
<action-state id="SecondFactorLogin">
+ <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
<evaluate expression="LookupRegisteredCredentials"/>
<evaluate expression="AddUserVerificationNotRequired"/>
<evaluate expression="'proceed'" />
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
index 6a08a1f..0f5c994 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
@@ -1,16 +1,16 @@
-## Thre relying party ID. Must be a valid domain string.
-## A public key credential is only registered and valid for a single relying party ID.
+## WebAuthn relying party setup
+### The relying party ID. Must be a valid domain string.
+### A public key credential is only registered and valid for a single relying party ID.
idp.authn.webauthn.relyingPartyId = localhost
idp.authn.webauthn.relyingPartyName = Shibboleth
## Allow any port on that origin
idp.authn.webauthn.allowOriginPort = true
idp.authn.webauthn.allowOriginSubdomain = false
-## Display debug information about the registration and authentication ceremony on their respective views?
-#idp.authn.webauthn.ui.debug = false
+## Which type of flow is supported? Usernameless or passwordless
+# idp.authn.webauthn.usernameless.enabled = false
## Registration properties.
-
### Require a residentKey to be created when registering a credential. One-of 'discouraged', 'preferred', 'required'
# idp.authn.webauthn.registration.residentKey = preferred
### The authenticatorAttachment requirement. One-of 'any', 'cross-platform', or 'platform'.
@@ -24,4 +24,7 @@ idp.authn.webauthn.2fa.allowedPreviousFactors = authn/Password
#idp.authn.webauthn.2fa.forceSecondFactorFlow = false
### Deny second factor irrespective of the value of forceSecondFactorFlow and if an acceptable previous factor is found
### Effectively turning off its ability to act as a second factor only
-#idp.authn.webauthn.2fa.enabled = false
\ No newline at end of file
+#idp.authn.webauthn.2fa.enabled = false
+
+## Display debug information about the registration and authentication ceremony on their respective views?
+#idp.authn.webauthn.ui.debug = false
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list