[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-33 - Make collecting nickname during key registration optional/configurable

Phil Smart philip.smart at jisc.ac.uk
Fri Feb 14 17:30:07 UTC 2025


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=9b50a91517d595394abe08367d29be2e54654394

The following commit(s) were added to refs/heads/main by this push:
     new 9b50a91  JWEBAUTHN-33 - Make collecting nickname during key registration optional/configurable
9b50a91 is described below

commit 9b50a91517d595394abe08367d29be2e54654394
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Feb 14 17:30:04 2025 +0000

    JWEBAUTHN-33 - Make collecting nickname during key registration
    optional/configurable
    
     - nickname collection can now be toggled by setting the property
    idp.authn.webauthn.registration.nicknameRequired
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-33
---
 .../context/WebAuthnRegistrationContext.java       | 54 ++++++--------
 .../webauthn/admin/impl/AddNicknameRequired.java   | 87 ++++++++++++++++++++++
 ...licKeyCredentialAttestationFromFormRequest.java |  9 ++-
 .../webauthn-registration-beans.xml                |  4 +
 .../webauthn-registration-flow.xml                 |  5 +-
 .../conf/authn/webauthn-registration.properties    |  3 +
 .../authn/webauthn/views/webauthn-register.vm      |  6 +-
 ...eyCredentialAttestationFromFormRequestTest.java | 27 ++++++-
 8 files changed, 157 insertions(+), 38 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
index b899908..2e87dea 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
@@ -29,9 +29,6 @@ import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 import com.yubico.webauthn.data.ResidentKeyRequirement;
 
 import net.shibboleth.idp.plugin.authn.webauthn.admin.RegistrationResult;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
 
 
 /** 
@@ -90,8 +87,29 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
      */
     @Nullable private Map<String, String> authenticatorCapabilities;
     
+    /** Is nickname collection enabled or disabled?.*/
+    private boolean nicknameRequired;
+    
+    
+    /**
+     * Is nickname collection required or not. The nickname is used for display purposes only.
+     * 
+     * @return true if the nickname is required, false otherwise.
+     */
+    public boolean isNicknameRequired() {
+        return nicknameRequired;
+    }
+    
+    /**
+     * Set if nickname collection is required or not. The nickname is used for display purposes only.
+     * 
+     * @param flag the flag to set
+     */
+    public BaseWebAuthnContext setNicknameRequired(final boolean flag) {
+        nicknameRequired = flag;
+        return this;
+    }
     
-
     /**
      * Set the WebAuthn user.name of the user to use during credential registration.  A human-palatable 
      * identifier for a user account. It is intended only for display.
@@ -251,7 +269,7 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
     }
 
     /**
-     * Set the ResidentKey requirement. A residenty key is also know as a discoverable credential.
+     * Set the ResidentKey requirement. A residenty key is also known as a discoverable credential.
      * 
      * @param requirement The resident key requirement to set.
      * 
@@ -264,7 +282,7 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
     }
     
     /**
-     * Get the ResidentKey requirement. A residenty key is also know as a discoverable credential.
+     * Get the ResidentKey requirement. A residenty key is also known as a discoverable credential.
      * 
      * @return the requirement.
      */
@@ -317,29 +335,5 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
         return displayName;
     }
 
-    /**
-     * Set the capabilities map of the authenticator. 
-     * 
-     * @param capabilities the map of capabilities to set
-     * 
-     * @return this context
-     */
-    @Nonnull public BaseWebAuthnContext setAuthenticatorCapabilities(@Nullable final Map<String, String> capabilities) {
-        authenticatorCapabilities = capabilities;
-        return this;        
-    }
-    
-    /**
-     * Get the capabilities map of the authenticator. 
-     * 
-     * @return Returns the authenticatorCapabilities.
-     */
-    @Nonnull @NotLive @Unmodifiable public Map<String, String> getAuthenticatorCapabilities() {
-        if (authenticatorCapabilities != null) {
-            return CollectionSupport.copyToMap(authenticatorCapabilities);
-        } else {
-            return CollectionSupport.emptyMap();
-        }
-    }
     
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddNicknameRequired.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddNicknameRequired.java
new file mode 100644
index 0000000..4138df7
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddNicknameRequired.java
@@ -0,0 +1,87 @@
+/*
+ * 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.admin.impl;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+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 sets onto the context if nickname collection is required or not. The nickname is only used for display
+ * purposes. 
+ * 
+ * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
+ * @post nicknameRequired is set onto the context
+ * 
+ * @since 1.1.0
+ */
+public class AddNicknameRequired extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddNicknameRequired.class);
+   
+    /** Predicate used to determine if nickname collection is optional for this request. */
+    @NonnullAfterInit private Predicate<ProfileRequestContext> nicknameRequiredPredicate;
+    
+    /** Constructor.*/
+    protected AddNicknameRequired() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+        nicknameRequiredPredicate = PredicateSupport.alwaysTrue();
+    }
+        
+    /**
+     * Set the predicate used to determine if the nickname is required or not.
+     * 
+     * @param predicate the is nickname required predicate to set.
+     */
+    public void setNicknameRequiredPredicate(final Predicate<ProfileRequestContext> predicate) {
+        checkSetterPreconditions();
+        nicknameRequiredPredicate = Constraint.isNotNull(predicate,
+                "isNicknameRequiredPredicate can not be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (nicknameRequiredPredicate == null) {
+            throw new ComponentInitializationException("isNicknameRequiredPredicate can not be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final WebAuthnRegistrationContext context) {
+             
+        final boolean nicknameRequired = nicknameRequiredPredicate.test(profileRequestContext);
+        log.debug("{} Nickname collection {}",getLogPrefix(), nicknameRequired ? "enabled" : "disabled");
+        context.setNicknameRequired(nicknameRequired);
+             
+    }        
+    
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java
index 7dbc165..e43e56e 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java
@@ -39,12 +39,13 @@ import net.shibboleth.shared.primitive.StringSupport;
 
 /**
  * An action that extracts the PublicKeyCredential containing the authenticator attestation response from the incoming
- * HTTP request. Also extracts the user entered credential nickname. Failure to find either results in an error event.
+ * HTTP request. Also extracts the user entered credential nickname. Failure to find the attestation results in an
+ * a non-proceed event. Similarly, if a nickname is required and not provided, it results in a non-proceed event..
  * 
  * @event {WebAuthnRegistrationEventIds#INVALID_REGISTRATION}
  * @event {AuthnEventIds#NO_CREDENTIALS}
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
- * @post add an AuthenticatorAttestationResponse and Credential Nickname to the registration context
+ * @post add an AuthenticatorAttestationResponse and credential nickname (if present) to the registration context
  */
 public class ExtractPublicKeyCredentialAttestationFromFormRequest 
                     extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
@@ -118,8 +119,8 @@ public class ExtractPublicKeyCredentialAttestationFromFormRequest
         
         final String credNickname = request.getParameter(credentialNicknameParameterName);   
         log.trace("{} Credential nickname is '{}'",getLogPrefix(), credNickname);
-        if (StringSupport.trimOrNull(credNickname) == null) {
-            log.debug("{} No credential nickname in request", getLogPrefix());
+        if (StringSupport.trimOrNull(credNickname) == null && context.isNicknameRequired()) {
+            log.debug("{} Credential nickname is not in the request but is required", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
             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 abbb5e1..ad9076b 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
@@ -124,6 +124,10 @@
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.AddUserVerificationRequirement" scope="prototype"
         p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"
         p:userVerificationRequirement="%{idp.authn.webauthn.registration.userVerification:discouraged}" />
+        
+    <bean id="AddNicknameRequired" parent="AbstractWebAuthnRegistrationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AddNicknameRequired" scope="prototype"
+        p:nicknameRequiredPredicate="#{getObject('idp.authn.WebAuthn.registration.NicknameRequiredPredicate')  == null ? '%{idp.authn.webauthn.registration.nicknameRequired:true}' : getObject('idp.authn.WebAuthn.registration.NicknameRequiredPredicate')}"/>
 
     <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnBaseAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
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 f50e19c..b2b1fb2 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
@@ -102,6 +102,7 @@
         <evaluate expression="AddAuthenticatorAttachmentRequirement"/>
         <evaluate expression="AddAttestationConveyancePreference"/>
         <evaluate expression="AddUserVerificationRequired"/>
+        <evaluate expression="AddNicknameRequired"/>
         <evaluate expression="CreatePublicKeyCredentialCreationOptions"/>
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="DisplayWebAuthnRegistrationView" />    
@@ -140,7 +141,9 @@
         <evaluate expression="StorePublicKeyCredential"/> 
         <evaluate expression="'proceed'" />
         
-        <transition on="InvalidRegistration" to="GeneratePublicKeyCredentialCreationOptions"/>
+        <transition on="InvalidRegistration" to="GeneratePublicKeyCredentialCreationOptions">
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext)).ensureSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationErrorContext)).addClassifiedError('InvalidRegistration')"/>
+        </transition>
         <transition on="proceed" to="DoClientStorageSaveContext">
             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext)).ensureSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationInformationContext)).addClassifiedMessage('ValidRegistration')"/>
         </transition>
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties
index bcfc7de..dd6f991 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties
@@ -52,6 +52,9 @@
 # State the preference of the IdP during registration to receive an authenticator attestation. One-of 'none', 'indirect', 'direct', or 'enterprise'.
 #idp.authn.webauthn.registration.attestationConveyancePreference = none
 
+# Is nickname collection required?
+#idp.authn.webauthn.registration.nicknameRequired = true
+
 # Enable the registration policy engine?
 #idp.authn.webauthn.registration.authenticator.policy.enabled = false
 # Set the authenticator policy to use, defaults to a chained set of policies
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
index 8047f54..202ea57 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
@@ -50,8 +50,10 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
              var pkCredOptionsParsed = parseCreationOptionsFromJSON(pkCredOptions);         
              await create(pkCredOptionsParsed)
                      .then(function (publicKeyCredentialAttestation){
-                         var nickname = prompt('#springMessageText("idp.webauthn.register.credential.nickname", "Credential Nickname")');
-                         document.getElementById("credentialNickname").value = nickname;
+                         #if ($webauthnRegContext.isNicknameRequired() == true)
+                            var nickname = prompt('#springMessageText("idp.webauthn.register.credential.nickname", "Credential Nickname")');
+                            document.getElementById("credentialNickname").value = nickname;
+                         #end                         
                          document.getElementById("publicKeyCredential").value = JSON.stringify(publicKeyCredentialAttestation);    
                          document.getElementById("registrationSubmit").click();
                      }).catch(function (err){
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequestTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequestTest.java
index 3eedc47..966133f 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequestTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequestTest.java
@@ -53,6 +53,7 @@ public class ExtractPublicKeyCredentialAttestationFromFormRequestTest extends Ab
         action = new ExtractPublicKeyCredentialAttestationFromFormRequest();
         action.setWebAuthnClient(client);
         action.setCredentialRepository(credentialRepo);
+        context.setNicknameRequired(true);
     } 
     
     @SuppressWarnings("null")
@@ -109,7 +110,7 @@ public class ExtractPublicKeyCredentialAttestationFromFormRequestTest extends Ab
     
     @SuppressWarnings("null")
     @Test
-    public void testExtraction_NoNicknameInResponse() throws Exception {
+    public void testExtraction_NoNicknameInResponse_IsRequired() throws Exception {
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
             attestationResponse = createAttestationReponse();
@@ -119,6 +120,7 @@ public class ExtractPublicKeyCredentialAttestationFromFormRequestTest extends Ab
         request.addParameter(ExtractPublicKeyCredentialAttestationFromFormRequest.DEFAULT_PARAMETER_NAME, 
                 attestationResponseJson);
 
+        context.setNicknameRequired(true);
         action.setHttpServletRequestSupplier(new ConstantSupplier<>(request));
         action.initialize();
         
@@ -126,5 +128,28 @@ public class ExtractPublicKeyCredentialAttestationFromFormRequestTest extends Ab
         assertFailure(result, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);  
     }
     
+    @SuppressWarnings("null")
+    @Test
+    public void testExtraction_NoNicknameInResponse_NotRequired() throws Exception {
+        
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
+            attestationResponse = createAttestationReponse();
+ 
+        final String attestationResponseJson = jsonMapper.writeValueAsString(attestationResponse);
+        
+        request.addParameter(ExtractPublicKeyCredentialAttestationFromFormRequest.DEFAULT_PARAMETER_NAME, 
+                attestationResponseJson);
+
+        context.setNicknameRequired(false);
+        action.setHttpServletRequestSupplier(new ConstantSupplier<>(request));
+        action.initialize();
+        
+        final Event result = action.execute(src);
+        assertNull(result);
+        assertNull(context.getCredentialNickname());
+        assertNotNull(context.getPublicKeyCredentialAttestationResponse());
+        assertEquals(context.getPublicKeyCredentialAttestationResponse().getId(), attestationResponse.getId()) ;
+    }
+    
 
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list