[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