[java-idp-plugin-webauthn] branch main updated: Fix message selection in WebAuthn error lookup function

Phil Smart philip.smart at jisc.ac.uk
Fri Nov 29 17:21:57 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=9c958a1753fae897d1dc295bc32e7e3278a20c0d

The following commit(s) were added to refs/heads/main by this push:
     new 9c958a1  Fix message selection in WebAuthn error lookup function
9c958a1 is described below

commit 9c958a1753fae897d1dc295bc32e7e3278a20c0d
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Nov 29 17:21:54 2024 +0000

    Fix message selection in WebAuthn error lookup function
    
     - Add customizable cleanup hook
     - Add classified messages map
---
 .../WebAuthnErrorMessageLookupFunction.java        | 50 +++++-----------------
 .../webauthn/impl/ValidateWebAuthnAssertion.java   | 29 ++++++++++++-
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |  6 ++-
 .../idp/flows/authn/WebAuthn/webauthn-flow.xml     |  9 +++-
 .../authn/webauthn/conf/authn/webauthn-config.xml  | 11 +++++
 .../idp/plugin/authn/webauthn/messages.properties  |  5 ++-
 6 files changed, 65 insertions(+), 45 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/WebAuthnErrorMessageLookupFunction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/WebAuthnErrorMessageLookupFunction.java
index cd17a8e..8a7d504 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/WebAuthnErrorMessageLookupFunction.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/WebAuthnErrorMessageLookupFunction.java
@@ -14,22 +14,17 @@
 
 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.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.context.support.ApplicationObjectSupport;
 import org.springframework.context.support.MessageSourceAccessor;
 
+import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.context.AuthenticationErrorContext;
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationErrorContext;
-import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
@@ -44,27 +39,6 @@ public class WebAuthnErrorMessageLookupFunction extends ApplicationObjectSupport
     /** Message ID to use for generic messages. */
     private String genericMessageID;
     
-    /** Lookup strategy to locate the WebAuthn registration information context. */
-    @Nonnull 
-    private Function<ProfileRequestContext,WebAuthnRegistrationErrorContext> webauthnErrorContextLookupStrategy;
-    
-    /** Constructor.*/
-    public WebAuthnErrorMessageLookupFunction() {
-        webauthnErrorContextLookupStrategy = new ChildContextLookup<>(WebAuthnRegistrationErrorContext.class)
-                .compose(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
-    }
-    
-    /**
-     * Set the strategy used to lookup the {@link WebAuthnRegistrationErrorContext}.
-     * 
-     * @param strategy The strategy to set.
-     */
-    public void setWebauthnErrorContextLookupStrategy(
-            final Function<ProfileRequestContext, WebAuthnRegistrationErrorContext> strategy) {
-        webauthnErrorContextLookupStrategy = Constraint.isNotNull(strategy,
-                "WebAuthnRegistrationErrorContextLookupStrategy can not be null");
-    }
-    
     /**
      * Sets whether non-message-based error messages should be exposed or turned into a more
      * generic value.
@@ -103,24 +77,20 @@ public class WebAuthnErrorMessageLookupFunction extends ApplicationObjectSupport
      * Get classified message.
      * 
      * @param messageSource Spring message source
-     * @param classifiedMessage classified message
+     * @param classifiedError classified error event
      * 
-     * @return mapped message, or null if an empty string was produced.
+     * @return mapped message, or null
      */
     @Nullable private String getClassifiedMessage(@Nonnull final MessageSourceAccessor messageSource,
-            @Nonnull final String classifiedMessage) {        
+            @Nonnull final String classifiedError) {
         
-        String message = messageSource.getMessage(classifiedMessage, "");
-        if (message.isEmpty()) {
-            message = messageSource.getMessage( genericMessageID != null ? genericMessageID : 
-                "idp.webauthn.message", "WebAuthn Failure: " 
-                    + classifiedMessage);
-        }        
-        if (message.isEmpty()) {
-            return null;
+        if (!AuthnEventIds.RESELECT_FLOW.equals(classifiedError)) {
+            final String eventKey = messageSource.getMessage(classifiedError,
+                    genericMessageID != null ? genericMessageID : "authn");
+            return messageSource.getMessage(eventKey + ".message", "Login Failure: " + classifiedError);
         }
-        return message;
-
+        
+        return null;
     }
     
 
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
index 9a09c96..bced460 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -215,7 +216,8 @@ public class ValidateWebAuthnAssertion extends AbstractAuditingValidationAction
         } catch (final AssertionFailureException e) {
             log.warn("{} Error validating authenticator assertion for '{}'", 
                     getLogPrefix(),context.getUsername() != null ? context.getUsername() : "unknown username", e);
-            handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.INVALID_CREDENTIALS);
+            handleError(profileRequestContext, authenticationContext, AuthnEventIds.INVALID_CREDENTIALS, 
+                    AuthnEventIds.INVALID_CREDENTIALS);
             recordFailure(profileRequestContext);              
             return; 
         }
@@ -279,5 +281,30 @@ public class ValidateWebAuthnAssertion extends AbstractAuditingValidationAction
             return subject;
         }
     }
+    
+    /**
+     * A default cleanup hook that removes the {@link WebAuthnAuthenticationContext} from the tree.
+     */
+    public static class WebAuthnCleanupHook implements Consumer<ProfileRequestContext> {
+
+        /** {@inheritDoc} */
+        @Override
+        public void accept(final ProfileRequestContext input) {
+            
+            final AuthenticationContext authnCtx =
+                    input != null ? input.getSubcontext(AuthenticationContext.class) : null;
+            if (authnCtx == null) {
+                return;
+            }
+
+            final WebAuthnAuthenticationContext webAuthnCtx = authnCtx.getSubcontext(WebAuthnAuthenticationContext.class);
+            if (webAuthnCtx == null) {
+                return;
+            }           
+            authnCtx.removeSubcontext(webAuthnCtx);
+            
+        }
+        
+    }
 
 }
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 410e153..155aabe 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
@@ -160,7 +160,11 @@
         p:credentialRepository="#{getObject('shibboleth.authn.WebAuthn.CredentialRepository') ?: getObject('shibboleth.authn.WebAuthn.DefaultCredentialRepository')}"
         p:updateSignatureCountPredicate="#{getObject('shibboleth.authn.WebAuthn.UpdateSignatureCountPredicate') ?: %{idp.authn.webauthn.updateSignatureCount:true}}"
         p:populateAuditContextAction="#{%{idp.authn.webauthn.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('shibboleth.authn.WebAuthn.PopulateAuditContext') : null}"
-        p:writeAuditLogAction="#{%{idp.authn.webauthn.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuthnAuditLog') : null}"/>
+        p:writeAuditLogAction="#{%{idp.authn.webauthn.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuthnAuditLog') : null}"
+        p:cleanupHook="#{getObject('shibboleth.authn.WebAuthn.RemoveAfterValidation') == true ? getObject('DefaultCleanupHook') : null}"        
+        p:classifiedMessages="#{getObject('shibboleth.authn.WebAuthn.ClassifiedMessageMap')}"/>
+        
+    <bean id="DefaultCleanupHook" class="net.shibboleth.idp.plugin.authn.webauthn.impl.ValidateWebAuthnAssertion.WebAuthnCleanupHook"/>    
 
     <!-- Error beans -->
     <alias alias="WebAuthnErrorFunction" name="%{idp.authn.webauthn.errorMessageFunction:DefaultWebAuthnErrorFunction}" />
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 330dd56..d5b6d37 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
@@ -105,7 +105,10 @@
         <transition on="proceed" to="GenerateAuthenticationCeremonyOptions" />    
     </action-state>
     
-     <action-state id="GenerateAuthenticationCeremonyOptions">
+    <!-- 
+        Now the authentication options have been set, add them to the WebAuthn request options for the browser API.
+     -->
+    <action-state id="GenerateAuthenticationCeremonyOptions">
         <evaluate expression="GenerateServerChallenge"/>
         <evaluate expression="CreatePublicKeyCredentialRequestOptions"/>
         <evaluate expression="'proceed'" />
@@ -146,7 +149,9 @@
         <transition on="proceed" to="proceed" />
         <!-- Import here we backtrack to a suitable action. For example, regenerate the challenge and options but
         we do not need a new username input etc. -->
-        <transition on="CredentialPolicyRejection" to="GenerateAuthenticationCeremonyOptions"/>        
+        <transition on="CredentialPolicyRejection" to="GenerateAuthenticationCeremonyOptions"/> 
+        <!-- The default invalid credentials transition would be InvalidCredentials, configuring this event instead allows control to be returned to the view -->  
+        <transition on="InvalidWebAuthnCredential" to="GenerateAuthenticationCeremonyOptions"/>           
     </action-state>
     
     <bean-import resource="webauthn-beans.xml" />
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
index 637576e..08c2bf4 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
@@ -20,5 +20,16 @@
                 p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.authenticator.policy.secondFactorOnlyAuthenticators:null}"/>
                 
     </util:list>
+    
+    <!-- Uncomment this map if you want control returned to the WebAuthn view if a credential fails to validate -->
+    <!--  
+    <util:map id="shibboleth.authn.WebAuthn.ClassifiedMessageMap">
+        <entry key="InvalidWebAuthnCredential">
+            <list>
+                <value>InvalidCredentials</value>
+            </list>
+        </entry>
+    </util:map>
+    -->
 
 </beans>
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
index bac27e9..a7fb47d 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
@@ -65,4 +65,7 @@ ValidRegistration = Key was registered successfully
 KeyRemoved = Key was removed successfully
 
 # Messages to report back to the user during authentication
-CredentialPolicyRejection = Credential was rejected by policy
\ No newline at end of file
+InvalidWebAuthnCredential = authn
+CredentialPolicyRejection = webauthn-policy
+
+webauthn-policy.message = Credential was rejected by policy
\ 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