[java-idp-plugin-webauthn] branch main updated: Add initial username lookup to registration flow, pre-authentication
Phil Smart
philip.smart at jisc.ac.uk
Fri Feb 16 11:41:58 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=83cba90d98debd852cfb108a3285f16f10b903ed
The following commit(s) were added to refs/heads/main by this push:
new 83cba90 Add initial username lookup to registration flow, pre-authentication
83cba90 is described below
commit 83cba90d98debd852cfb108a3285f16f10b903ed
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Feb 16 11:41:55 2024 +0000
Add initial username lookup to registration flow, pre-authentication
- Allows the MFA flow to determine which authentication flow to follow
depending on whether the user already has pre-registered credentials.
---
webauthn-api/pom.xml | 5 ++
.../webauthn/context/BaseWebAuthnContext.java | 9 ++
.../logic/IsUsernameCollectionRequired.java | 64 ++++++++++++++
.../navigate/UsernameLookupFromHttpRequest.java | 97 ++++++++++++++++++++++
.../UsernameLookupFromRegistrationContext.java | 56 +++++++++++++
.../webauthn/impl/ExtractUsernameFromForm.java | 2 +-
.../PopulateWebAuthnAuthenticationContext.java | 19 +++--
.../webauthn-registration-beans.xml | 8 +-
.../webauthn-registration-flow.xml | 42 +++++++---
.../idp/flows/authn/WebAuthn/webauthn-beans.xml | 15 +++-
.../idp/flows/authn/WebAuthn/webauthn-flow.xml | 49 +++++++----
11 files changed, 320 insertions(+), 46 deletions(-)
diff --git a/webauthn-api/pom.xml b/webauthn-api/pom.xml
index a4d0850..8eac6bb 100644
--- a/webauthn-api/pom.xml
+++ b/webauthn-api/pom.xml
@@ -25,6 +25,11 @@
distribution. -->
<!-- Provided dependencies -->
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>${idp.groupId}</groupId>
<artifactId>idp-authn-api</artifactId>
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
index a85b26f..a297505 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
@@ -58,6 +58,15 @@ public class BaseWebAuthnContext extends BaseContext {
/** Does the authentication/registration require user verification.*/
@Nullable private UserVerificationRequirement userVerificationRequirement;
+ /**
+ * Are credentials available to use for WebAuthn authentication.
+ *
+ * @return true iff credentials are available, false otherwise.
+ */
+ public boolean isWebAuthnAvailable() {
+ return (existingCredentials != null && !existingCredentials.isEmpty());
+ }
+
/**
* Gets the username.
*
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsUsernameCollectionRequired.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsUsernameCollectionRequired.java
new file mode 100644
index 0000000..1c2ae70
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsUsernameCollectionRequired.java
@@ -0,0 +1,64 @@
+/*
+ * 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.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A predicate that determines if a username already exists in the authentication context.
+ */
+public class IsUsernameCollectionRequired extends AbstractInitializableComponent implements Predicate<ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(IsUsernameCollectionRequired.class);
+
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ if (input == null) {
+ log.trace("Profile context was null, can not determine if a username already exists, "
+ + "assume collection required");
+ return true;
+ }
+ final AuthenticationContext authnContext = input.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ log.trace("Authentication context was null, can not determine if a username already exists, "
+ + "assume collection required");
+ return true;
+ }
+ final WebAuthnAuthenticationContext webauthnContext =
+ authnContext.getSubcontext(WebAuthnAuthenticationContext.class);
+ if (webauthnContext == null) {
+ log.trace("WebAuthn authentication context was null, can not determine if a username already exists, "
+ + "assume collection required");
+ return true;
+ }
+ final boolean usernameExists = webauthnContext.getUsername() != null;
+ log.debug("{}", usernameExists ? "Username exists, skipping collection" :
+ "Username does not exist, collecting");
+ return !usernameExists;
+
+ }
+}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/UsernameLookupFromHttpRequest.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/UsernameLookupFromHttpRequest.java
new file mode 100644
index 0000000..5ab2b74
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/UsernameLookupFromHttpRequest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Extract a username from the HTTP request, returning {@code null} if not found.
+ */
+ at ThreadSafeAfterInit
+public class UsernameLookupFromHttpRequest extends AbstractIdentifiableInitializableComponent
+ implements Function<ProfileRequestContext, String> {
+
+ /** Form parameter name to carry username. */
+ @Nonnull @NotEmpty private String usernameFieldName;
+
+ /** Supplier for the Current HTTP request, if available. */
+ @Nullable private NonnullSupplier<HttpServletRequest> httpServletRequestSupplier;
+
+ /** Constructor.*/
+ public UsernameLookupFromHttpRequest() {
+ usernameFieldName = "j_username";
+ }
+
+ /**
+ * Get the current HTTP request if available.
+ *
+ * @return current HTTP request
+ */
+ @Nullable public HttpServletRequest getHttpServletRequest() {
+ checkComponentActive();
+ if (httpServletRequestSupplier != null) {
+ return httpServletRequestSupplier.get();
+ }
+
+ return null;
+ }
+
+ /**
+ * Set the current HTTP request Supplier.
+ *
+ * @param requestSupplier Supplier for the current HTTP request
+ */
+ public void setHttpServletRequestSupplier(@Nullable final NonnullSupplier<HttpServletRequest> requestSupplier) {
+ checkSetterPreconditions();
+ httpServletRequestSupplier = requestSupplier;
+ }
+
+ /**
+ * 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");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String apply(@Nullable final ProfileRequestContext prc) {
+ checkComponentActive();
+ final HttpServletRequest request = getHttpServletRequest();
+ if (request != null) {
+ return request.getParameter(usernameFieldName);
+ }
+ return null;
+ }
+
+}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/UsernameLookupFromRegistrationContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/UsernameLookupFromRegistrationContext.java
new file mode 100644
index 0000000..64f3991
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/UsernameLookupFromRegistrationContext.java
@@ -0,0 +1,56 @@
+/*
+ * 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.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Pull out a username from a WebAuthn Registration Context if it exists. Useful when operating inside a WebAuthn
+ * registration flow.
+ */
+public class UsernameLookupFromRegistrationContext implements Function<ProfileRequestContext, String> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(UsernameLookupFromRegistrationContext.class);
+
+ /** {@inheritDoc} */
+ @Override
+ public String apply(@Nullable final ProfileRequestContext input) {
+ if (input == null) {
+ log.trace("Profile context was null, can not find existing username");
+ return null;
+ }
+ final WebAuthnRegistrationContext registrationContext =
+ input.getSubcontext(WebAuthnRegistrationContext.class);
+ if (registrationContext == null) {
+ log.trace("WebAuthn registration context was null, can not find existing username");
+ return null;
+ }
+ final String username = registrationContext.getUsername();
+ log.debug("{}", username != null ? "Found existing username" :
+ "Did not find existing username");
+ return username;
+ }
+
+}
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
index 9d1afa0..10f42d1 100644
--- 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
@@ -153,7 +153,7 @@ public class ExtractUsernameFromForm extends AbstractExtractionAction {
final HttpServletRequest request = getHttpServletRequest();
if (request != null) {
- // FIXME This has not been thought out here.
+ // FIXME this will not work atm?
final String donotcache = request.getParameter(ssoBypassFieldName);
if (donotcache != null && "1".equals(donotcache)) {
log.debug("{} Recording do-not-cache instruction in authentication context", getLogPrefix());
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 de11d69..95225cc 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
@@ -112,18 +112,19 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return;
}
-
- if (usernameRequiredPredicate.test(profileRequestContext)) {
- final String username = usernameLookupStrategy.apply(profileRequestContext);
- if (username == null && usernameRequiredPredicate.test(profileRequestContext)) {
- log.error("{} Error creating WebauthnAuthenticationContext, no username found", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return;
- }
+ // Username can be null, but if required record exception if it is
+ final String username = usernameLookupStrategy.apply(profileRequestContext);
+ if (usernameRequiredPredicate.test(profileRequestContext) && username == null) {
+ 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");
+ log.debug("Created WebAuthn authentication context {}", username != null ? "for user '"+username+"'" :
+ "without existing username");
}
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
index 3b92309..dde01ff 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
@@ -18,12 +18,12 @@
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext) }" />
- <!-- TODO Should this been populating an authentication context for an admin flow? -->
<bean id="PopulateWebAuthnRegistrationContext" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PopulateWebAuthnRegistrationContext">
- <property name="usernameLookupStrategy">
- <bean id="UsernameFromAuthenticationContextLookupStrategy"
- class="net.shibboleth.idp.plugin.authn.webauthn.impl.UsernameFromAuthenticationContextLookupStrategy" />
+ <property name="usernameLookupStrategy">
+ <bean id="usernameFromHttpRequest" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.UsernameLookupFromHttpRequest"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
</property>
</bean>
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
index af69ac8..b7c4519 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
@@ -8,6 +8,30 @@
<action-state id="InitializeProfileRequestContext">
<evaluate expression="InitializeProfileRequestContext" />
<evaluate expression="FlowStartPopulateAuditContext" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="CollectUsernameView" />
+ </action-state>
+
+
+ <view-state id="CollectUsernameView" view="webauthn/webauthn-username-register">
+ <on-render>
+ <evaluate expression="environment" result="viewScope.environment" />
+ <evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPDigester')" result="requestScope.cspDigester" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPNonce')" result="requestScope.cspNonce" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="viewScope.webauthnRegContext" />
+ <evaluate expression="T(net.shibboleth.shared.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="ExtractUsernameAndPopulateContext" />
+
+ </view-state>
+
+ <action-state id="ExtractUsernameAndPopulateContext">
+ <evaluate expression="PopulateWebAuthnRegistrationContext"/>
+ <evaluate expression="LookupRegisteredCredentials"/>
<evaluate expression="'proceed'" />
<!-- Branch to determine if authentication is required. -->
@@ -20,12 +44,10 @@
<evaluate expression="CheckAccess" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="PopulateWebAuthnContext" />
+ <transition on="proceed" to="GeneratePublicKeyCredentialCreationOptions" />
</action-state>
- <action-state id="PopulateWebAuthnContext">
- <evaluate expression="PopulateWebAuthnRegistrationContext"/>
- <evaluate expression="LookupRegisteredCredentials"/>
+ <action-state id="GeneratePublicKeyCredentialCreationOptions">
<evaluate expression="GenerateServerChallenge"/>
<evaluate expression="GenerateUserHandle"/>
<evaluate expression="AddResidentKeyRequirement"/>
@@ -41,11 +63,9 @@
<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.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="viewScope.webauthnRegContext" />
- <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="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPDigester')" result="requestScope.cspDigester" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPNonce')" result="requestScope.cspNonce" />
<evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
<evaluate expression="T(net.shibboleth.idp.plugin.authn.webauthn.impl.WebAuthnEncoder)" result="viewScope.webAuthnEncoder"/>
<evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
@@ -87,11 +107,9 @@
<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.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="viewScope.webauthnRegContext" />
- <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="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPDigester')" result="requestScope.cspDigester" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPNonce')" result="requestScope.cspNonce" />
<evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
<evaluate expression="T(net.shibboleth.idp.plugin.authn.webauthn.impl.WebAuthnEncoder)" result="viewScope.webAuthnEncoder"/>
<evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
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 fc33f7d..f4bc07e 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
@@ -15,14 +15,25 @@
c:g-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContext" />
- <bean id="PopulateWebAuthnAuthenticationContext" scope="prototype"
+ <bean id="PopulateWebAuthnAuthenticationContextPasswordless" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext">
- </bean>
+ <!-- In-case a username has been supplied by an outer WebAuthn registration context -->
+ <property name="usernameLookupStrategy">
+ <bean id="UsernameLookupFromRegistrationContext"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.UsernameLookupFromRegistrationContext"/>
+ </property>
+ </bean>
+
+ <bean id="PopulateWebAuthnAuthenticationContextUsernameless" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext"/>
<bean id="PopulateWebAuthnAuthenticationContextFor2FA" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext"
p:usernameRequired="true">
</bean>
+
+ <bean id="IsUsernameCollectionRequired" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.logic.IsUsernameCollectionRequired"/>
<bean id="IsSecondFactor" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.context.logic.IsSecondFactor"
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 7d5e59d..ea528f8 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
@@ -17,13 +17,23 @@
</decision-state>
<!--
- Passwordless login
+ Passwordless login. If Passwordless: do not require ResidentKey, require UV, UP, and provide previous credentials based on username.
-->
<action-state id="PasswordlessLogin">
- <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
+ <evaluate expression="PopulateWebAuthnAuthenticationContextPasswordless"/>
<evaluate expression="'proceed'" />
- <transition on="proceed" to="CollectUsernameView" />
+ <transition on="proceed" to="DetermineIfUsernameCollectionIsRequired" />
</action-state>
+
+ <!-- If the registration context already contains a username, e.g. from an outer registration flow, skip collection -->
+ <decision-state id="DetermineIfUsernameCollectionIsRequired">
+ <if test="IsUsernameCollectionRequired.test(opensamlProfileRequestContext)"
+ then="CollectUsernameView"
+ else="PasswordlessLoginProceed" />
+ </decision-state>
+
+
+ <!-- need decision state, we might have already collected username, so might not need it again e.g. when part of a registration flow -->
<view-state id="CollectUsernameView" view="webauthn/webauthn-username">
<on-render>
@@ -34,31 +44,34 @@
<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.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPDigester')" result="requestScope.cspDigester" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPNonce')" result="requestScope.cspNonce" />
+ <evaluate expression="T(net.shibboleth.shared.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" />
+ <transition on="proceed" to="PasswordlessLoginExtractUsername" />
</view-state>
- <!-- If Passwordless: do not require ResidentKey, require UV, UP, and provide previous credentials based on username -->
- <action-state id="PasswordlessLoginProceed">
+ <action-state id="PasswordlessLoginExtractUsername">
<evaluate expression="ExtractUsernameFromForm"/>
- <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="PasswordlessLoginProceed" />
+ </action-state>
+
+ <action-state id="PasswordlessLoginProceed">
<evaluate expression="LookupRegisteredCredentials"/>
<evaluate expression="AddUserVerificationRequired"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="GenerateAuthenticationCeremonyOptions" />
- </action-state>
+ </action-state>
<!--
- Usernameless login
- -->
-
- <!-- If Usernameless: require ResidentKey, UV, UP, and provide no previous credentials-->
+ Usernameless login. If Usernameless: require ResidentKey, UV, UP, and provide no previous credentials.
+ -->
<action-state id="UsernamelessLogin">
- <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
+ <evaluate expression="PopulateWebAuthnAuthenticationContextUsernameless"/>
<evaluate expression="AddUserVerificationRequired"/>
<evaluate expression="EnsureAllowedCredentialsIsEmpty"/>
<evaluate expression="'proceed'" />
@@ -73,10 +86,8 @@
</decision-state> -->
<!--
- Second Factor login
+ Second Factor login. If we are running after a first factor, perform 2FA only. Needs existing username.
-->
-
- <!-- If we are running after a first factor, perform 2FA only. Needs existing username -->
<action-state id="SecondFactorLogin">
<evaluate expression="PopulateWebAuthnAuthenticationContextFor2FA"/>
<evaluate expression="LookupRegisteredCredentials"/>
@@ -102,7 +113,9 @@
<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.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPDigester')" result="requestScope.cspDigester" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPNonce')" result="requestScope.cspNonce" />
+ <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
<evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
<evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
</on-render>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list