[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-6 - Signal an end-state from the authentication flow if user has no registered credentials
Phil Smart
philip.smart at jisc.ac.uk
Wed Apr 17 15:57:01 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=e2b697f71bb21538b364343ecdb30a1c69981eae
The following commit(s) were added to refs/heads/main by this push:
new e2b697f JWEBAUTHN-6 - Signal an end-state from the authentication flow if user has no registered credentials
e2b697f is described below
commit e2b697f71bb21538b364343ecdb30a1c69981eae
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Apr 17 16:56:57 2024 +0100
JWEBAUTHN-6 - Signal an end-state from the authentication flow if user
has no registered credentials
- Add the ability to configure custom events that should be built if no
credentials can be found for the given username or userhandle.
https://shibboleth.atlassian.net/browse/JWEBAUTHN-6
---
.../authn/WebAuthnAuthenticationEventIds.java | 4 +
.../context/logic/UsernamelessFlowEnabled.java | 2 +-
.../impl/AbstractWebAuthnRegistrationAction.java | 2 +-
.../authn/webauthn/admin/impl/AddUserId.java | 2 +-
.../impl/AbstractWebAuthnAuthenticationAction.java | 25 +++-
.../webauthn/impl/LookupRegisteredCredentials.java | 29 +++-
.../LookupRegisteredCredentialsFromUserHandle.java | 160 +++++++++++++++++++++
.../webauthn-registration-beans.xml | 16 +--
.../idp/flows/authn/WebAuthn/webauthn-beans.xml | 17 ++-
.../idp/flows/authn/WebAuthn/webauthn-flow.xml | 3 +
.../storage/impl/InMemoryRegistrationStorage.java | 2 +
11 files changed, 238 insertions(+), 24 deletions(-)
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/authn/WebAuthnAuthenticationEventIds.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/authn/WebAuthnAuthenticationEventIds.java
index 98fcfae..b9b26e6 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/authn/WebAuthnAuthenticationEventIds.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/authn/WebAuthnAuthenticationEventIds.java
@@ -34,5 +34,9 @@ public final class WebAuthnAuthenticationEventIds {
@Nonnull @NotEmpty
public static final String NO_REGISTERED_WEBAUTHN_CREDENTIALS = "NoRegisteredWebAuthnCredentials";
+ /** The user has no registered WebAuthn credentials for the user handle supplied. */
+ @Nonnull @NotEmpty
+ public static final String USER_HANDLE_NOT_REGISTERED = "UserHandleNotRegistered";
+
}
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
index 0f8ab01..fed84b3 100644
--- 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
@@ -35,7 +35,7 @@ public class UsernamelessFlowEnabled extends AbstractInitializableComponent
implements Predicate<ProfileRequestContext> {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(IsDiscoverableCredentialRequired.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(UsernamelessFlowEnabled.class);
/**
* Determines if we want a usernameless flow (true), or a passwordless flow (false).
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnRegistrationAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnRegistrationAction.java
index 7edcac6..3b0a83a 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnRegistrationAction.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnRegistrationAction.java
@@ -150,7 +150,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
*
* @return the credential repository.
*/
- @NonnullAfterInit public StorageServiceCredentialRepository getCredentialRepository() {
+ @NonnullAfterInit protected StorageServiceCredentialRepository getCredentialRepository() {
return credentialRepository;
}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java
index ec72a7b..4ed89c7 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java
@@ -120,7 +120,7 @@ public class AddUserId extends AbstractWebAuthnRegistrationAction {
}
/**
- * Default user.id generator that generates a 64 byte randomized UserHandle (must be at least 32 bytes long).
+ * Default user.id generator that generates a 64 byte randomized user.id (must be at least 32 bytes long).
* Returns {@code null} iff one can not be generated.
*
* <p>This could contain some form of state if required, but must not contain retrievable PII.</p>
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAuthenticationAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAuthenticationAction.java
index 3678978..2736b5c 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAuthenticationAction.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAuthenticationAction.java
@@ -20,6 +20,7 @@ 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;
@@ -31,6 +32,7 @@ import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.component.ComponentInitializationException;
@@ -65,6 +67,9 @@ public abstract class AbstractWebAuthnAuthenticationAction extends AbstractAuthe
/** The WebAuthn client to use.*/
@NonnullBeforeExec private WebAuthnAuthenticationClient webAuthnClient;
+ /** The credential repository to store WebAuthn credentials in.*/
+ @Nullable private StorageServiceCredentialRepository credentialRepository;
+
/**
* Set the WebAuthn client used to handle registration and authentication ceremonies.
@@ -85,7 +90,25 @@ public abstract class AbstractWebAuthnAuthenticationAction extends AbstractAuthe
checkComponentActive();
return webAuthnClient;
}
-
+
+ /**
+ * Set the credential repository used to store WebAuthn credentials.
+ *
+ * @param repository The respository to set.
+ */
+ public void setCredentialRepository(@Nonnull final StorageServiceCredentialRepository repository) {
+ checkSetterPreconditions();
+ credentialRepository = Constraint.isNotNull(repository, "Credential respository can not be null");
+ }
+
+ /**
+ * Get the credential repository used to store WebAuthn credentials.
+ *
+ * @return the credential repository.
+ */
+ @Nullable protected StorageServiceCredentialRepository getCredentialRepository() {
+ return credentialRepository;
+ }
/** Constructor.*/
protected AbstractWebAuthnAuthenticationAction() {
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index d4bc589..1fce097 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.plugin.authn.webauthn.impl;
import java.util.Collection;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
@@ -29,10 +30,13 @@ import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
/**
- * An action that lookups existing registered credentials and sets them onto the base WebAuthn context.
+ * An action that lookups existing registered credentials based on the username contained in the WebAuthn context
+ * and sets them back onto the context. If no credentials exist and the trigger event condition is set, an error
+ * event will be produced.
*
* @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
* @post BaseWebAuthnContext.setExistingCredentials() is either null if no existing credentials are found, or contains
@@ -45,7 +49,7 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
private final Logger log = LoggerFactory.getLogger(LookupRegisteredCredentials.class);
/** Should an event be built if there are no credentials found?.*/
- private boolean triggerEventOnNoCredentials;
+ private Predicate<ProfileRequestContext> triggerEventOnNoCredentialsPredicate;
/**
* The EventID of the event to build if no credentials are foud and <code>triggerEventOnNoCredentials<code>
@@ -59,13 +63,24 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
}
/**
- * Set a flag which triggers a non-proceed event to be built if no credentials are found?
+ * Set a flag which triggers a custom event to be built if no credentials are found?
*
* @param trigger the flag to set
*/
public void setTriggerEventOnNoCredentials(final boolean trigger) {
checkSetterPreconditions();
- triggerEventOnNoCredentials = trigger;
+ triggerEventOnNoCredentialsPredicate = trigger ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set a predicate which triggers a custom event to be built if no credentials are found?
+ *
+ * @param trigger the flag to set
+ */
+ public void setTriggerEventOnNoCredentialsPredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+ checkSetterPreconditions();
+ triggerEventOnNoCredentialsPredicate =
+ Constraint.isNotNull(predicate, "TriggerEventOnNoCredentialsPredicate can not be null");
}
/**
@@ -85,7 +100,7 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
final String username = context.getUsername();
if (username == null) {
- log.error("{} Unable to find username in registration context", getLogPrefix());
+ log.error("{} Unable to find username in WebAuthn context", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return;
}
@@ -96,8 +111,8 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(), credentials.size(), username);
context.setExistingCredentials(credentials);
- if (triggerEventOnNoCredentials && credentials.isEmpty()) {
- log.error("{} Triggering event '{}' ", getLogPrefix(), noCredentialsEventId);
+ if (triggerEventOnNoCredentialsPredicate.test(profileRequestContext) && credentials.isEmpty()) {
+ log.debug("{} Triggering event '{}' ", getLogPrefix(), noCredentialsEventId);
ActionSupport.buildEvent(profileRequestContext, noCredentialsEventId);
return;
}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
new file mode 100644
index 0000000..39bcaf8
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
@@ -0,0 +1,160 @@
+/*
+ * 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.Collection;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.authn.WebAuthnAuthenticationEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An action that lookups existing registered credentials from the userHandle supplied in the authenticators assertion
+ * response. If no credentials exist and the trigger condition is set, an error event will be produced.
+ *
+ * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
+ * @post BaseWebAuthnContext.setExistingCredentials() is either null if no existing credentials are found, or contains
+ * the credentials from the credential repository
+ */
+public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(LookupRegisteredCredentialsFromUserHandle.class);
+
+ /** Should an event be built if there are no credentials found?.*/
+ private Predicate<ProfileRequestContext> triggerEventOnNoCredentialsPredicate;
+
+ /**
+ * The EventID of the event to build if no credentials are foud and <code>triggerEventOnNoCredentials<code>
+ * is set.
+ */
+ @Nonnull @NotEmpty private String noCredentialsEventId;
+
+ /** Constructor. */
+ public LookupRegisteredCredentialsFromUserHandle() {
+ noCredentialsEventId = WebAuthnAuthenticationEventIds.NO_REGISTERED_WEBAUTHN_CREDENTIALS;
+ }
+
+ /**
+ * Set a flag which triggers a custom event to be built if no credentials are found?
+ *
+ * @param trigger the flag to set
+ */
+ public void setTriggerEventOnNoCredentials(final boolean trigger) {
+ checkSetterPreconditions();
+ triggerEventOnNoCredentialsPredicate = trigger ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set a predicate which triggers a custom event to be built if no credentials are found?
+ *
+ * @param trigger the flag to set
+ */
+ public void setTriggerEventOnNoCredentialsPredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+ checkSetterPreconditions();
+ triggerEventOnNoCredentialsPredicate =
+ Constraint.isNotNull(predicate, "TriggerEventOnNoCredentialsPredicate can not be null");
+ }
+
+ /**
+ * Set the EventID of the event to build if no credentials are foud and <code>triggerEventOnNoCredentials<code>
+ * is set.
+ *
+ * @param eventId the eventId to build.
+ */
+ public void setNoCredentialsEventId(@Nonnull @NotEmpty final String eventId) {
+ checkSetterPreconditions();
+ noCredentialsEventId = Constraint.isNotEmpty(eventId, "NoCredentialsEventId can not be null or empty");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (getCredentialRepository() == null) {
+ throw new ComponentInitializationException("The credential repository can not be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nonnull final WebAuthnAuthenticationContext context) {
+
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion =
+ context.getAuthenticatorAssertionResponse();
+ if (assertion == null) {
+ log.error("{} Unable to find Assertion in WebAuthn authentication context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ final StorageServiceCredentialRepository repository = getCredentialRepository();
+ assert repository != null;
+
+ final Optional<ByteArray> userHandle = assertion.getResponse().getUserHandle();
+ boolean credentialsFound = false;
+ if (userHandle.isEmpty()) {
+ log.debug("{} User could not be found, the authenticator did not supply a userHandle, "
+ + "no registered credentials", getLogPrefix());
+ } else {
+ final Optional<String> potentialUsername = repository.getUsernameForUserHandle(userHandle.get());
+ if (potentialUsername.isEmpty()) {
+ log.debug("{} User could not be found from the supplied userHandle, no registered credentials", getLogPrefix());
+ } else {
+ final Collection<CredentialRegistration> credentials =
+ repository.getRegistrationsByUsername(potentialUsername.get());
+ if (credentials.isEmpty()) {
+ log.debug("{} Could not find any registered credentials for userHandle '{}'", getLogPrefix(),
+ userHandle.get().getBase64());
+ } else {
+ credentialsFound = true;
+ }
+
+ }
+ }
+
+ if (triggerEventOnNoCredentialsPredicate.test(profileRequestContext) && credentialsFound == false) {
+ log.debug("{} Triggering event '{}' ", getLogPrefix(), noCredentialsEventId);
+ ActionSupport.buildEvent(profileRequestContext, noCredentialsEventId);
+ return;
+ }
+ }
+
+}
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 fe8b0db..716933c 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
@@ -63,36 +63,36 @@
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"
p:userVerificationRequirement="%{idp.authn.webauthn.registration.userVerification:discouraged}" />
- <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnBaseAction"
+ <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnBaseAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
- <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
+ <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
- <bean id="AddUserId" parent="AbstractWebAuthnRegistrationAction"
+ <bean id="AddUserId" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AddUserId" />
- <bean id="CreatePublicKeyCredentialCreationOptions" parent="AbstractWebAuthnRegistrationAction"
+ <bean id="CreatePublicKeyCredentialCreationOptions" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.CreatePublicKeyCredentialCreationOptions"/>
- <bean id="ExtractAuthenticatorAttestationFromFormRequest" parent="AbstractWebAuthnRegistrationAction"
+ <bean id="ExtractAuthenticatorAttestationFromFormRequest" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractAuthenticatorAttestationFromFormRequest"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
- <bean id="ExtractKeyRemovalInformationFromFormRequest" parent="AbstractWebAuthnRegistrationAction"
+ <bean id="ExtractKeyRemovalInformationFromFormRequest" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyRemovalInformationFromFormRequest"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
- <bean id="DeletePublicKeyCredential" parent="AbstractWebAuthnRegistrationAction"
+ <bean id="DeletePublicKeyCredential" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.DeletePublicKeyCredential" />
<bean id="ValidateAuthenticatorAttestationResponse" parent="AbstractWebAuthnRegistrationAction"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ValidateAuthenticatorAttestationResponse" />
- <bean id="StorePublicKeyCredential" parent="AbstractWebAuthnRegistrationAction"
+ <bean id="StorePublicKeyCredential" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.StorePublicKeyCredential"
p:storageService-ref="shibboleth.authn.webauthn.StorageService"
p:credentialRepository-ref="shibboleth.authn.webauthn.DefaultCredentialRepository" />
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 8b22d66..7e2c029 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
@@ -30,7 +30,8 @@
</bean>
<bean id="PopulateWebAuthnAuthenticationContextUsernameless" scope="prototype"
- class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext"/>
+ class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext"
+ p:usernameRequired="false"/>
<bean id="PopulateWebAuthnAuthenticationContextFor2FA" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.PopulateWebAuthnAuthenticationContext"
@@ -75,23 +76,29 @@
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
p:userVerificationRequirement="discouraged" />
- <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnBaseAction"
+ <bean id="LookupRegisteredCredentials" scope="prototype" parent="AbstractWebAuthnBaseAction"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
p:triggerEventOnNoCredentials="%{idp.authn.webauthn.triggerEventOnNoCredentials:false}"
p:noCredentialsEventId="%{idp.authn.webauthn.noCredentialsEventId:NoRegisteredWebAuthnCredentials}"
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" />
- <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
+ <bean id="GenerateServerChallenge" scope="prototype" parent="AbstractWebAuthnBaseAction"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" />
- <bean id="CreatePublicKeyCredentialRequestOptions" parent="AbstractWebAuthnAuthenticationAction"
+ <bean id="CreatePublicKeyCredentialRequestOptions" scope="prototype" parent="AbstractWebAuthnAuthenticationAction"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.CreatePublicKeyCredentialRequestOptions"/>
- <bean id="ExtractAuthenticatorAssertionFromFormRequest" parent="AbstractWebAuthnAuthenticationAction"
+ <bean id="ExtractAuthenticatorAssertionFromFormRequest" scope="prototype" parent="AbstractWebAuthnAuthenticationAction"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.ExtractAuthenticatorAssertionFromFormRequest"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
p:objectMapper-ref="shibboleth.authn.WebAuthn.JSONObjectMapper" />
+
+ <bean id="LookupRegisteredCredentialsFromUserHandle" scope="prototype" parent="AbstractWebAuthnAuthenticationAction"
+ class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentialsFromUserHandle"
+ p:credentialRepository-ref="shibboleth.authn.webauthn.DefaultCredentialRepository"
+ p:triggerEventOnNoCredentials="%{idp.authn.webauthn.triggerEventOnUserHandleNotRegistered:false}"
+ p:noCredentialsEventId="%{idp.authn.webauthn.userHandleNotRegisteredEventId:UserHandleNotRegistered}"/>
<bean id="ValidateWebAuthnAssertion" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.ValidateWebAuthnAssertion"
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 b10bd24..0c69d56 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
@@ -119,6 +119,9 @@
<action-state id="AuthenticatePublicKeyCredential">
<evaluate expression="ExtractAuthenticatorAssertionFromFormRequest"/>
+ <!-- lookup credentials here so we can exit the process before validation if no registered credentials exist and
+ the authentication plugin has been configured to trigger a custom event. Useful for the usernameless flow. -->
+ <evaluate expression="LookupRegisteredCredentialsFromUserHandle"/>
<evaluate expression="ValidateWebAuthnAssertion"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="proceed" />
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
index 95a7e40..775a301 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
@@ -50,6 +50,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredential
/**
* In memory credential repository to use for testing.
*/
+ at SuppressWarnings("null")
public class InMemoryRegistrationStorage implements StorageServiceCredentialRepository {
private final Cache<String, Set<CredentialRegistration>> storage = CacheBuilder.newBuilder().maximumSize(1000)
@@ -138,6 +139,7 @@ public class InMemoryRegistrationStorage implements StorageServiceCredentialRepo
registration.getCredential().toBuilder().signatureCount(result.getSignatureCount()).build()));
}
+
public Optional<CredentialRegistration> getRegistrationByUsernameAndCredentialId(final String username,
final ByteArray id) {
try {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list