[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-11 - Pull identity information from the AttributeResolver during registration
Phil Smart
philip.smart at jisc.ac.uk
Wed Apr 24 08:45:11 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=f9523aa9a5e56e92111b8539dcbd58193353fd53
The following commit(s) were added to refs/heads/main by this push:
new f9523aa JWEBAUTHN-11 - Pull identity information from the AttributeResolver during registration
f9523aa is described below
commit f9523aa9a5e56e92111b8539dcbd58193353fd53
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Apr 24 09:45:08 2024 +0100
JWEBAUTHN-11 - Pull identity information from the AttributeResolver
during registration
- Both user.id and user.displayName can be generated or looked up.
- Attributes from the attribute context are supported if configured
https://shibboleth.atlassian.net/browse/JWEBAUTHN-11
---
webauthn-api/pom.xml | 11 ++
.../admin/CredentialCreationOptionsParameters.java | 49 +++++++-
.../webauthn/context/BaseWebAuthnContext.java | 14 +--
.../context/WebAuthnRegistrationContext.java | 29 +++++
...stractAttributeContextUserIdentityStrategy.java | 127 ++++++++++++++++++++
.../AttributeContextByteArrayLookupStrategy.java | 71 +++++++++++
.../AttributeContextStringLookupStrategy.java | 69 +++++++++++
...ttributeContextByteArrayLookupStrategyTest.java | 130 +++++++++++++++++++++
.../authn/webauthn/admin/impl/AddDisplayName.java | 116 ++++++++++++++++++
.../authn/webauthn/admin/impl/AddUserId.java | 72 +++++-------
.../CreatePublicKeyCredentialCreationOptions.java | 12 +-
.../webauthn/admin/impl/RandomUserIdGenerator.java | 49 ++++++++
.../impl/YubicoWebAuthnAuthenticationClient.java | 2 +-
.../webauthn-registration-beans.xml | 21 +++-
.../webauthn-registration-flow.xml | 17 ++-
.../authn/webauthn/conf/authn/webauthn.properties | 10 ++
.../idp/plugin/authn/webauthn/module.properties | 3 +
.../authn/webauthn/views/webauthn-register-end.vm | 48 ++++++++
.../authn/webauthn/admin/impl/AddUserIdTest.java | 27 +++--
.../admin/impl/DeletePublicKeyCredentialTest.java | 2 +-
.../YubicoWebauthnAuthenticationClientTest.java | 2 +-
.../authn/webauthn/impl/AbstractWebAuthnTest.java | 1 +
.../impl/LookupRegisteredCredentialsTest.java | 1 +
.../impl/ValidateWebAuthnAssertionTest.java | 1 +
.../impl/CredentialRegistrationSerializerTest.java | 1 -
...IdPStorageServiceCredentialRespositoryTest.java | 1 -
.../{ => storage}/impl/MockAuthenticator.java | 2 +-
27 files changed, 812 insertions(+), 76 deletions(-)
diff --git a/webauthn-api/pom.xml b/webauthn-api/pom.xml
index 136a2c0..9fa1ac1 100644
--- a/webauthn-api/pom.xml
+++ b/webauthn-api/pom.xml
@@ -95,6 +95,17 @@
<artifactId>shib-support</artifactId>
<scope>provided</scope>
</dependency>
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-testing</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${shib-shared.groupId}</groupId>
+ <artifactId>shib-profile-testing</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/CredentialCreationOptionsParameters.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/CredentialCreationOptionsParameters.java
index 20b65af..2a25f40 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/CredentialCreationOptionsParameters.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/CredentialCreationOptionsParameters.java
@@ -47,6 +47,12 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
*/
@Nonnull @NotEmpty final String username;
+ /**
+ * The user.displayName supplied to the authenticator during registration. A human-palatable name for the user
+ * account, intended only for display
+ */
+ @Nonnull @NotEmpty final String displayName;
+
/** The AuthenticatorAttachment requirement. {@code null} would represent either possibility.*/
@Nullable final AuthenticatorAttachment authenticatorAttachment;
@@ -81,6 +87,7 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
this.attestationConveyancePreference = Constraint.isNotNull(builder.attestationConveyancePreference,
"AttestationConveyancePreference can not be null");
this.enableCredProperties = builder.enableCredProperties;
+ this.displayName = builder.displayName;
}
/**
@@ -132,6 +139,17 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
return userId;
}
+
+
+ /**
+ * Get the user.displayName.
+ *
+ * @return the user's display name.
+ */
+ @Nonnull public String getDisplayName() {
+ return displayName;
+ }
+
/**
* Is the credential properties extension enabled?
*
@@ -207,7 +225,19 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
* @param username the username
* @return the next stage
*/
- public IResidentKeyRequirementStage withUsername(@Nonnull @NotEmpty final String username);
+ public IDisplayNameStage withUsername(@Nonnull @NotEmpty final String username);
+ }
+
+ /** Stage interface.*/
+ public interface IDisplayNameStage {
+ /**
+ * The user.displayName supplied to the authenticator during registration. A human-palatable name for the user
+ * account, intended only for display
+ *
+ * @param name the display name
+ * @return the next stage
+ */
+ public IResidentKeyRequirementStage withDisplayName(@Nonnull @NotEmpty final String name);
}
@@ -280,7 +310,7 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
/** Builder class.*/
public static final class Builder implements IUserVerificationRequirementStage, IChallengeStage,
- IExcludeCredentialsStage, IUsernameStage, IResidentKeyRequirementStage, IUserHandleStage,
+ IExcludeCredentialsStage, IUsernameStage, IDisplayNameStage, IResidentKeyRequirementStage, IUserHandleStage,
IAttestationConveyancePreferenceStage, IBuildStage {
/** Does the authentication/registration require user verification.*/
@@ -307,6 +337,12 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
/** The Attestation conveyance preference.*/
private AttestationConveyancePreference attestationConveyancePreference;
+ /**
+ * The user.displayName supplied to the authenticator during registration. A human-palatable name for the user
+ * account, intended only for display
+ */
+ private String displayName;
+
/**
* Enable the credential properties extenions. For example, to report discoverable credentials i.e.
* 'passkeys'.
@@ -339,10 +375,16 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
}
@Override
- public IResidentKeyRequirementStage withUsername(@Nonnull final String username) {
+ public IDisplayNameStage withUsername(@Nonnull final String username) {
this.username = username;
return this;
}
+
+ @Override
+ public IResidentKeyRequirementStage withDisplayName(@Nonnull final String name) {
+ displayName = name;
+ return this;
+ }
@Override
public IUserHandleStage withResidentKeyRequirement(
@@ -386,5 +428,6 @@ public class CredentialCreationOptionsParameters extends BaseOptionsParameters {
}
+
}
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 f724601..3802a7b 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
@@ -37,9 +37,8 @@ import net.shibboleth.shared.logic.Constraint;
public class BaseWebAuthnContext extends BaseContext {
/**
- * The username of the user that is the subject of this authentication. In the authentication ceremony, if
- * {@code null} we can not determine the userHandle (and hence public key) to use, and the flow will require a
- * discoverable credential.
+ * The user.name of the user. If {@code null} in the authentication ceremony if we can not determine the
+ * userHandle (and hence public key) to use, and the flow will require a discoverable credential.
*/
@Nullable private String username;
@@ -52,9 +51,9 @@ public class BaseWebAuthnContext extends BaseContext {
/** The challenge sent to the authenticator in both registration and authentication ceremonies.*/
@Nullable private byte[] serverChallenge;
- /** The user.id supplied to the authenticator as a userHandle during registration. Generated by the IdP.*/
+ /** The user.id supplied to the authenticator during registration. Generated by the IdP.*/
@Nullable private byte[] userId;
-
+
/** Does the authentication/registration require user verification.*/
@Nullable private UserVerificationRequirement userVerificationRequirement;
@@ -68,9 +67,9 @@ public class BaseWebAuthnContext extends BaseContext {
}
/**
- * Gets the username.
+ * Gets the user.name.
*
- * @return the username
+ * @return the user.name
*/
@Nullable public String getUsername() {
return username;
@@ -175,7 +174,6 @@ public class BaseWebAuthnContext extends BaseContext {
return userId;
}
-
/**
* Set the user verification requirement.
*
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 5051c25..e19be11 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
@@ -13,6 +13,8 @@ import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
import com.yubico.webauthn.data.ResidentKeyRequirement;
import net.shibboleth.idp.plugin.authn.webauthn.authn.RegistrationResult;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
/**
@@ -53,6 +55,12 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
/** The attestation preference.*/
@Nullable private AttestationConveyancePreference attestationConveyancePreference;
+ /**
+ * The user.displayName supplied to the authenticator during registration. A human-palatable name for the user
+ * account, intended only for display.
+ */
+ @Nullable private String displayName;
+
/**
* Set the AuthenticatorAttachment requirement. {@code null} would represent either possibility.
*
@@ -232,4 +240,25 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
@Nullable public AttestationConveyancePreference getAttestationConveyancePreference() {
return attestationConveyancePreference;
}
+
+
+ /**
+ * Set the human-palatable name for the user account.
+ *
+ * @param name The display name to set.
+ */
+ @Nonnull public BaseWebAuthnContext setDisplayName(@Nonnull @NotEmpty final String name) {
+ displayName = Constraint.isNotEmpty(name, "The Display Name can not be null");
+ return this;
+ }
+
+ /**
+ * Get the human-palatable name for the user account.
+ *
+ * @return the displayName.
+ */
+ @Nullable public String getDisplayName() {
+ return displayName;
+ }
+
}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AbstractAttributeContextUserIdentityStrategy.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AbstractAttributeContextUserIdentityStrategy.java
new file mode 100644
index 0000000..545752a
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AbstractAttributeContextUserIdentityStrategy.java
@@ -0,0 +1,127 @@
+/*
+ * 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.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A base class for functions that retrieve a single attribute value from the
+ * {@link AttributeContext attribute context}.
+ *
+ * @param <T> the type of attribute value to return
+ */
+public abstract class AbstractAttributeContextUserIdentityStrategy<T> extends AbstractIdentifiableInitializableComponent
+ implements Function<ProfileRequestContext, T> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractAttributeContextUserIdentityStrategy.class);
+
+ /**
+ * Strategy used to locate the {@link AttributeContext} associated with a given {@link ProfileRequestContext}.
+ */
+ @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextLookupStrategy;
+
+ /** The attribute Id to extract the value from.*/
+ @Nullable @NotEmpty private String attributeId;
+
+ /** Constructor.*/
+ protected AbstractAttributeContextUserIdentityStrategy() {
+ final Function<ProfileRequestContext,AttributeContext> acls =
+ new ChildContextLookup<>(AttributeContext.class).compose(
+ new ChildContextLookup<>(RelyingPartyContext.class));
+ assert acls !=null;
+ attributeContextLookupStrategy = acls;
+
+ }
+
+ /**
+ * Set the attribute Id to extract the value from.
+ *
+ * @param id the attributeId.
+ */
+ public void setAttributeId(@Nullable final String id) {
+ checkSetterPreconditions();
+ attributeId = id;
+ }
+
+ /**
+ * Get the attributeId.
+ *
+ * @return the attributeId.
+ */
+ @Nullable @NotEmpty protected String getAttributeId() {
+ return attributeId;
+ }
+
+ /**
+ * Set the strategy used to locate the {@link AttributeContext} associated with a given
+ * {@link ProfileRequestContext}.
+ *
+ * @param strategy strategy used to locate the {@link AttributeContext} associated with a given
+ * {@link ProfileRequestContext}
+ */
+ public void setAttributeContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
+ checkSetterPreconditions();
+ attributeContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Get the attribute with the given attribute Id from the AttributeContext (if it exists).
+ *
+ * @param profileRequestContext the profile request context to locate the attribute context and the attribute from
+ *
+ * @return the IdPAttribute, or <code>null</code> if not found.
+ */
+ @Nullable protected IdPAttribute getAttribute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ checkComponentActive();
+
+ final AttributeContext attrContext = attributeContextLookupStrategy.apply(profileRequestContext);
+
+ if (attrContext != null && attributeId != null) {
+ for (final IdPAttribute attribute : attrContext.getIdPAttributes().values()) {
+
+ if (attribute != null && !attribute.getValues().isEmpty() &&
+ attribute.getId().equals(attributeId)) {
+ return attribute;
+ }
+ }
+ }
+ log.trace("{}: Attribute '{}' could not be found", getId(), attributeId);
+ return null;
+
+ }
+
+
+
+
+}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextByteArrayLookupStrategy.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextByteArrayLookupStrategy.java
new file mode 100644
index 0000000..8ac2971
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextByteArrayLookupStrategy.java
@@ -0,0 +1,71 @@
+/*
+ * 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.nio.charset.StandardCharsets;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An {@link AbstractAttributeContextUserIdentityStrategy} that pulls out an {@link StringAttributeValue} from the
+ * attribute context and represents it as a UTF-8 encoded byte array. Will return an empty array if the attribute can
+ * not be found, or if there is more than one attribute value.
+ */
+public class AttributeContextByteArrayLookupStrategy extends AbstractAttributeContextUserIdentityStrategy<byte[]> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeContextByteArrayLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull public byte[] apply(final ProfileRequestContext profileRequestContext) {
+ checkComponentActive();
+
+ if (profileRequestContext == null) {
+ return new byte[0];
+ }
+
+ final IdPAttribute attribute = getAttribute(profileRequestContext);
+ if (attribute == null) {
+ return new byte[0];
+ }
+
+ final List<IdPAttributeValue> values = attribute.getValues();
+ if (values.size() != 1) {
+ log.warn("{}: Attribute '{}' has more than one value", getId(), getAttributeId());
+ return new byte[0];
+ }
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof final StringAttributeValue strValue) {
+ log.debug("{}: Found attribute '{}' with value '{}'", getId(), attribute.getId(), strValue.getValue());
+ final String valueAsString = strValue.getValue();
+ final byte[] valueAsBytes = valueAsString.getBytes(StandardCharsets.UTF_8);
+ return valueAsBytes != null ? valueAsBytes : new byte[0];
+ }
+
+ log.warn("{}: Attribute '{}' could not be found", getId(), getAttributeId());
+ return new byte[0];
+ }
+
+}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextStringLookupStrategy.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextStringLookupStrategy.java
new file mode 100644
index 0000000..39dcae0
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextStringLookupStrategy.java
@@ -0,0 +1,69 @@
+/*
+ * 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.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An {@link AbstractAttributeContextUserIdentityStrategy} that pulls out an {@link StringAttributeValue} from the
+ * attribute context. Will return {@code null} if the attribute can not be found, or if there is more than one
+ * attribute value.
+ */
+public class AttributeContextStringLookupStrategy extends AbstractAttributeContextUserIdentityStrategy<String> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeContextStringLookupStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public String apply(final ProfileRequestContext profileRequestContext) {
+ checkComponentActive();
+
+ if (profileRequestContext == null) {
+ return null;
+ }
+
+ final IdPAttribute attribute = getAttribute(profileRequestContext);
+ if (attribute == null) {
+ return null;
+ }
+
+ final List<IdPAttributeValue> values = attribute.getValues();
+ if (values.size() != 1) {
+ log.warn("{}: Attribute '{}' has more than one value", getId(), getAttributeId());
+ return null;
+ }
+ final IdPAttributeValue value = values.get(0);
+ if (value instanceof final StringAttributeValue strValue) {
+ log.debug("{}: Found attribute '{}' with value '{}'", getId(), attribute.getId(), strValue.getValue());
+ return strValue.getValue();
+ }
+
+ log.warn("{}: Attribute '{}' could not be found", getId(), getAttributeId());
+ return null;
+ }
+
+}
diff --git a/webauthn-api/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextByteArrayLookupStrategyTest.java b/webauthn-api/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextByteArrayLookupStrategyTest.java
new file mode 100644
index 0000000..9248e60
--- /dev/null
+++ b/webauthn-api/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/AttributeContextByteArrayLookupStrategyTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.nio.charset.StandardCharsets;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Tests for the {@link AttributeContextByteArrayLookupStrategy}.
+ */
+public class AttributeContextByteArrayLookupStrategyTest {
+
+ private AttributeContextByteArrayLookupStrategy strategy;
+
+ /** The profile request context to use.*/
+ protected ProfileRequestContext prc;
+
+ /** The request context to use.*/
+ protected RequestContext src;
+
+ @BeforeMethod
+ public void setup() throws Exception {
+ src = new RequestContextBuilder().buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+ strategy = new AttributeContextByteArrayLookupStrategy();
+ strategy.setId("Test AttributeContextByteArrayLookupStrategy");
+ strategy.setAttributeId("attributeOne");
+ }
+
+ @Test
+ public void testAttributeLookup() throws ComponentInitializationException {
+ strategy.initialize();
+
+ final AttributeContext attrCtx = prc.ensureSubcontext(RelyingPartyContext.class)
+ .ensureSubcontext(AttributeContext.class);
+ final var idpAttribute = new IdPAttribute("attributeOne");
+ idpAttribute.setValues(CollectionSupport.listOf(new StringAttributeValue("value")));
+
+ attrCtx.setIdPAttributes(CollectionSupport.listOf(idpAttribute));
+
+ final byte[] attributeValue = strategy.apply(prc);
+ assertEquals(new String(attributeValue, StandardCharsets.UTF_8), "value");
+
+ }
+
+ @Test
+ public void testAttributeLookup_Attribute_NotFound() throws ComponentInitializationException {
+ strategy.initialize();
+
+ final AttributeContext attrCtx = prc.ensureSubcontext(RelyingPartyContext.class)
+ .ensureSubcontext(AttributeContext.class);
+ final var idpAttribute = new IdPAttribute("attributeNotFound");
+ idpAttribute.setValues(CollectionSupport.listOf(new StringAttributeValue("value")));
+
+ attrCtx.setIdPAttributes(CollectionSupport.listOf(idpAttribute));
+
+ final byte[] attributeValue = strategy.apply(prc);
+ assertNotNull(attributeValue);
+ assertEquals(attributeValue.length, 0);
+
+ }
+
+ @Test
+ public void testAttributeLookup_AttributeHasTooManyValues() throws ComponentInitializationException {
+ strategy.initialize();
+
+ final AttributeContext attrCtx = prc.ensureSubcontext(RelyingPartyContext.class)
+ .ensureSubcontext(AttributeContext.class);
+ final var idpAttribute = new IdPAttribute("attributeOne");
+ idpAttribute.setValues(CollectionSupport.listOf(new StringAttributeValue("value"),
+ new StringAttributeValue("valueTwo")));
+
+ attrCtx.setIdPAttributes(CollectionSupport.listOf(idpAttribute));
+
+ final byte[] attributeValue = strategy.apply(prc);
+ assertNotNull(attributeValue);
+ assertEquals(attributeValue.length, 0);
+
+ }
+
+ @Test
+ public void testAttributeLookup_NoAttributeContext() throws ComponentInitializationException {
+ strategy.initialize();
+
+ final AttributeContext attrCtx = prc.ensureSubcontext(RelyingPartyContext.class)
+ .ensureSubcontext(AttributeContext.class);
+
+ attrCtx.removeFromParent();
+
+ final var idpAttribute = new IdPAttribute("attributeOne");
+ idpAttribute.setValues(CollectionSupport.listOf(new StringAttributeValue("value"),
+ new StringAttributeValue("valueTwo")));
+
+ attrCtx.setIdPAttributes(CollectionSupport.listOf(idpAttribute));
+
+ final byte[] attributeValue = strategy.apply(prc);
+ assertNotNull(attributeValue);
+ assertEquals(attributeValue.length, 0);
+
+ }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddDisplayName.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddDisplayName.java
new file mode 100644
index 0000000..17a7be7
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddDisplayName.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.nio.charset.StandardCharsets;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An action to generate or lookup a user.displayName. The display name is for display purposes only.
+ *
+ * @event {@link WebAuthnRegistrationEventIds#INVALID_REGISTRATION}
+ * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
+ * @post a displayName is added to the registration context
+ */
+public class AddDisplayName extends AbstractWebAuthnRegistrationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AddDisplayName.class);
+
+ /** Strategy used to lookup the user.displayName. */
+ @NonnullAfterInit private Function<ProfileRequestContext,String> displayNameLookupStrategy;
+
+ /** The stashed username.*/
+ @NonnullBeforeExec private String username;
+
+ /**
+ * Set the strategy used to generate the user.displayName.
+ *
+ * @param strategy the strategy
+ */
+ public void setDisplayNameLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ checkSetterPreconditions();
+ displayNameLookupStrategy =
+ Constraint.isNotNull(strategy, "Challenge Generator cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (displayNameLookupStrategy == null) {
+ throw new ComponentInitializationException("DisplayName generation strategy can not be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final WebAuthnRegistrationContext context) {
+
+ if (!super.doPreExecute(profileRequestContext, context)) {
+ return false;
+ }
+
+ username = context.getUsername();
+ if (username == null) {
+ log.error("{} Username not available in registration context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final WebAuthnRegistrationContext context) {
+
+
+ // else we build a user.display from the lookup function
+ final String displayName = displayNameLookupStrategy.apply(profileRequestContext);
+ if (displayName == null) {
+ log.trace("{} DisplayName was null for user '{}'", getLogPrefix(), username);
+ ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
+ return;
+ }
+ if (displayName.getBytes(StandardCharsets.UTF_8).length > 64) {
+ log.warn("{} DisplayName exceeds 64 bytes and might get truncated by the authenticator", getLogPrefix());
+ }
+ log.trace("{} Populating DisplayName '{}'",getLogPrefix(),displayName);
+ context.setDisplayName(displayName);
+
+ }
+
+}
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 7886376..1ed0a45 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
@@ -17,13 +17,10 @@
package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.context.ProfileRequestContext;
@@ -38,13 +35,14 @@ import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
/**
- * An action to generate or lookup a user.id used as a userHandle. This is used by the IdP to map a public key
- * credential to a users session map of public keys, and by the Authenticator to map the IdP's ID (RelyingParty ID)
- * and the userHandle to a public key credential source (which contains the private key).
+ * An action to generate or lookup a user.id (stored by an authenticator as the userHandle). This is used by the IdP to
+ * map a public key credential to a users session map of public keys, and by the authenticator to map the IdP's ID
+ * (RelyingParty ID) and the userHandle to a public key credential source (which contains the private key).
*
- * <p>Note, the same user should use the same UserHandle.</p>
+ * <p>The user.id could contain some form of state if required, but must not contain retrievable PII.</p>
+ *
+ * <p>The same user should have the same user.id.</p>
*
- * TODO: Support a generated unique to username approach, as well as one from the resolver.
*
* @event {@link WebAuthnRegistrationEventIds#INVALID_REGISTRATION}
* @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
@@ -55,7 +53,7 @@ public class AddUserId extends AbstractWebAuthnRegistrationAction {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(AddUserId.class);
- /** Strategy used to generate the UserHandle. */
+ /** Strategy used to generate the user.id. */
@Nonnull private Function<ProfileRequestContext,byte[]> userIdGeneratorStrategy;
/** The stashed username.*/
@@ -63,7 +61,7 @@ public class AddUserId extends AbstractWebAuthnRegistrationAction {
/** Constructor. */
public AddUserId() {
- userIdGeneratorStrategy = new DefaultUserIdGenerator();
+ userIdGeneratorStrategy = new RandomUserIdGenerator();
}
/**
@@ -107,44 +105,26 @@ public class AddUserId extends AbstractWebAuthnRegistrationAction {
if (existingUserHandle.isPresent()) {
final byte[] handleAsBytes = existingUserHandle.get().getBytes();
assert handleAsBytes != null;
+ log.trace("{} Found user.id '{}'",getLogPrefix(),handleAsBytes);
context.setUserId(handleAsBytes);
- } else {
- final byte[] userId = userIdGeneratorStrategy.apply(profileRequestContext);
- if (userId == null) {
- log.trace("{} Generated UserID was null", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
- return;
- }
- log.trace("{} Generated UserID '{}'",getLogPrefix(),userId);
- context.setUserId(userId);
- }
+ return;
+ }
+
+ // else we build a user.id from the generator function
+ final byte[] userId = userIdGeneratorStrategy.apply(profileRequestContext);
+ if (userId == null || userId.length == 0) {
+ log.trace("{} Generated user.id was empty or null", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
+ return;
+ }
+ if (userId.length > 64) {
+ log.warn("{}: User.id is larger than 64 bytes", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
+ return;
+ }
+ log.trace("{} Generated user.id '{}'",getLogPrefix(),userId);
+ context.setUserId(userId);
}
- /**
- * 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>
- */
- private static final class DefaultUserIdGenerator implements Function<ProfileRequestContext, byte[]>{
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultUserIdGenerator.class);
-
- /** {@inheritDoc} */
- @Override
- @Nullable public byte[] apply(final ProfileRequestContext input) {
- try {
- final byte[] bytes = new byte[64];
- SecureRandom.getInstanceStrong().nextBytes(bytes);
- return bytes;
- } catch (final NoSuchAlgorithmException e) {
- log.error("Unable to generate UserHandle", e);
- return null;
- }
- }
- }
-
-
}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
index 35a9666..01b5edf 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
@@ -78,13 +78,19 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnRe
}
final String username = context.getUsername();
if (username == null) {
- log.error("{} Username is null",getLogPrefix());
+ log.error("{} user.name is null",getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION_CTX);
+ return;
+ }
+ final String displayName = context.getDisplayName();
+ if (displayName == null) {
+ log.error("{} user.displayName is null",getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION_CTX);
return;
}
final byte[] userId = context.getUserId();
if (userId == null) {
- log.error("{} UserID is null",getLogPrefix());
+ log.error("{} user.id is null",getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION_CTX);
return;
}
@@ -105,11 +111,13 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnRe
assert null != existingCredentialDescriptors;
+ //TODO helper?
final CredentialCreationOptionsParameters creationOptions = CredentialCreationOptionsParameters.builder()
.withUserVerificationRequirement(uvRequirement)
.withChallenge(challenge)
.withExcludeCredentials(existingCredentialDescriptors)
.withUsername(username)
+ .withDisplayName(displayName)
.withResidentKeyRequirement(residentKeyRequirement)
.withUserId(userId)
.withAttestationConveyancePreference(attestationPreference)
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/RandomUserIdGenerator.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/RandomUserIdGenerator.java
new file mode 100644
index 0000000..18d9e05
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/RandomUserIdGenerator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+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.shared.primitive.LoggerFactory;
+
+/**
+ * A user.id generator that generates a 64 byte randomized user.id. Returns {@code null} iff one can not be generated.
+ */
+public final class RandomUserIdGenerator implements Function<ProfileRequestContext, byte[]>{
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RandomUserIdGenerator.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public byte[] apply(final ProfileRequestContext input) {
+ try {
+ final byte[] bytes = new byte[64];
+ SecureRandom.getInstanceStrong().nextBytes(bytes);
+ return bytes;
+ } catch (final NoSuchAlgorithmException e) {
+ log.error("Unable to generate user.id", e);
+ return null;
+ }
+ }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java
index 84e826b..38b0fec 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java
@@ -119,7 +119,7 @@ public class YubicoWebAuthnAuthenticationClient implements WebAuthnAuthenticatio
final UserIdentity identity =
UserIdentity.builder().name(creationOptions.getUsername())
- .displayName(creationOptions.getUsername())
+ .displayName(creationOptions.getDisplayName())
.id(new ByteArray(creationOptions.getUserId()))
.build();
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 716933c..aa5f3d4 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
@@ -72,7 +72,26 @@
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
<bean id="AddUserId" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
- class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AddUserId" />
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AddUserId"
+ p:userIdGeneratorStrategy="#{getObject('%{idp.authn.webauthn.registration.userid.strategy:shibboleth.authn.webauthn.RandomUserIdGenerator}')}"/>
+
+ <bean id="AddDisplayName" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AddDisplayName"
+ p:displayNameLookupStrategy="#{getObject('%{idp.authn.webauthn.registration.displayname.strategy:shibboleth.authn.webauthn.SubjectDisplayNameLookupStrategy}')}"/>
+
+ <bean id="shibboleth.authn.webauthn.SubjectDisplayNameLookupStrategy" lazy-init="true"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.UsernameLookupFromSubjectContext"/>
+
+ <bean id="shibboleth.authn.webauthn.AttributeContextDisplayNameLookupStrategy" lazy-init="true"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.AttributeContextStringLookupStrategy"
+ p:attributeId="%{idp.authn.webauthn.registration.displayname.attributeId:#{null}}"/>
+
+ <bean id="shibboleth.authn.webauthn.AttributeContextUserIdLookupStrategy" scope="singleton" lazy-init="true"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.AttributeContextByteArrayLookupStrategy"
+ p:attributeId="%{idp.authn.webauthn.registration.userid.attributeId:#{null}}"/>
+
+ <bean id="shibboleth.authn.webauthn.RandomUserIdGenerator" scope="singleton" lazy-init="true"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.RandomUserIdGenerator"/>
<bean id="CreatePublicKeyCredentialCreationOptions" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.CreatePublicKeyCredentialCreationOptions"/>
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 9131091..fe13bc0 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
@@ -53,6 +53,7 @@
<evaluate expression="LookupRegisteredCredentials"/>
<evaluate expression="GenerateServerChallenge"/>
<evaluate expression="AddUserId"/>
+ <evaluate expression="AddDisplayName"/>
<evaluate expression="AddResidentKeyRequirement"/>
<evaluate expression="AddAuthenticatorAttachmentRequirement"/>
<evaluate expression="AddAttestationConveyancePreference"/>
@@ -64,7 +65,6 @@
<view-state id="DisplayWebAuthnView" view="webauthn/webauthn-register">
<on-render>
-
<evaluate expression="environment" result="viewScope.environment" />
<evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
<evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="viewScope.webauthnRegContext" />
@@ -109,7 +109,20 @@
</transition>
</action-state>
- <end-state id="RegistrationComplete"/>
+
+ <end-state id="RegistrationComplete" view="webauthn/webauthn-register-end">
+ <on-entry>
+ <evaluate expression="environment" result="requestScope.environment" />
+ <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="requestScope.webauthnRegContext" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('WebAuthnCSPDigester')" result="requestScope.cspDigester" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('WebAuthnCSPNonce')" result="requestScope.cspNonce" />
+ <evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="requestScope.encoder" />
+ <evaluate expression="T(net.shibboleth.idp.plugin.authn.webauthn.impl.WebAuthnEncoder)" result="requestScope.webAuthnEncoder"/>
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="requestScope.request" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="requestScope.response" />
+ </on-entry>
+ </end-state>
<bean-import resource="webauthn-registration-beans.xml" />
<bean-import resource="../../authn/WebAuthn/webauthn-abstract-beans.xml" />
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
index 1d010fb..13ba04e 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
@@ -44,6 +44,16 @@ idp.authn.webauthn.supportedPrincipals = \
# Should user.id and display name be resolve from the attribute resolver? else the user.id will be generated by the plugin
#idp.authn.webauthn.admin.registration.resolveIdentityAttributes = true;
+# The bean name of the user.id generator or lookup strategy
+#idp.authn.webauthn.registration.userid.strategy = shibboleth.authn.webauthn.RandomUserIdGenerator
+# If using the attribute context lookup strategy, which attribute should the user.id be taken from
+#idp.authn.webauthn.registration.userid.attributeId = WebAuthnUserID
+
+# The bean name of the user.displayName lookup strategy
+#idp.authn.webauthn.registration.displayname.strategy = shibboleth.authn.webauthn.SubjectDisplayNameLookupStrategy
+# If using the attribute context lookup strategy, which attribute should the user.displayName be taken from
+#idp.authn.webauthn.registration.displayname.attributeId = mail
+
# Enable FIDO Alliance metadata? If you want to use the metadata service, you *must* download and set the trust root and the legal blob file headers
#idp.authn.webauthn.metadata.enabled = false
#idp.authn.webauthn.metadata.trustRootFile =
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties
index 93b0eea..6e944e2 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties
@@ -40,4 +40,7 @@ idp.authn.WebAuthn.8.dest = edit-webapp/js/webauthn/webauthn-json.browser-ponyfi
idp.authn.WebAuthn.9.src = /net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js
idp.authn.WebAuthn.9.dest = edit-webapp/js/webauthn/webauthn-support.js
+idp.authn.WebAuthn.10.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register-end.vm
+idp.authn.WebAuthn.10.dest = views/webauthn/webauthn-register-end.vm
+
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register-end.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register-end.vm
new file mode 100644
index 0000000..937d077
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register-end.vm
@@ -0,0 +1,48 @@
+##
+## Velocity Template for DisplayWebauthnView view-state
+##
+## Velocity context will contain the following properties
+## flowExecutionUrl - the form action location
+## flowRequestContext - the Spring Web Flow RequestContext
+## flowExecutionKey - the SWF execution key (this is built into the flowExecutionUrl)
+## profileRequestContext - root of context tree
+## authenticationContext - context with authentication request information
+## authenticationErrorContext - context with login error state
+## webauthnContext = web authentication context
+## authenticationWarningContext - context with login warning state
+## rpUIContext - the context with SP UI information from the metadata
+## encoder - HTMLEncoder class
+## webAuthnEncoder - WebAuthnEncoder class
+## request - HttpServletRequest
+## response - HttpServletResponse
+## environment - Spring Environment object for property resolution
+## custom - arbitrary object injected by deployer
+##
+#set ($debug = $environment.getProperty("idp.authn.webauthn.ui.debug", "false"))
+#set ($rpContext = $profileRequestContext.getSubcontext('net.shibboleth.profile.context.RelyingPartyContext'))
+##
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>#springMessageText("idp.title", "Web Login Service")</title>
+ <meta charset="UTF-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
+ <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("idp.css", "/css/placeholder.css" )">
+ </head>
+ <body>
+ <main class="main">
+ <header>
+ <img class="main-logo" src="$request.getContextPath()#springMessageText("idp.logo", "/images/placeholder-logo.png" )" alt="#springMessageText(" idp.logo.alt-text", "logo" )" />
+ </header>
+ <section>
+ #springMessageText("idp.webauthn.authn.register.ended", "Your registration session has ended")
+ </section>
+ <footer>
+ <div class="container container-footer">
+ <p class="footer-text">#springMessageText("idp.footer", "Insert your footer text here.")</p>
+ </div>
+ </footer>
+ </div>
+ </body>
+</html>
\ No newline at end of file
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserIdTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserIdTest.java
index 8fb4ad3..93e43a8 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserIdTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserIdTest.java
@@ -39,12 +39,11 @@ import com.yubico.webauthn.data.UserIdentity;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.ConstraintViolationException;
/**
* Tests for {@link AddUserId}.
@@ -78,7 +77,13 @@ public class AddUserIdTest extends AbstractWebAuthnTest {
@Test
public void testNewUserId_CustomGenerator() throws ComponentInitializationException {
- addAction.setUserIdGeneratorStrategy(input -> new byte[] {(byte)0xFF});
+ addAction.setUserIdGeneratorStrategy(input -> {
+ final byte[] bytes = new byte[34];
+ for (int i = 0; i <34; i++) {
+ bytes[i] = (byte)0xFF;
+ }
+ return bytes;
+ });
addAction.initialize();
context.setUsername("jdoe");
@@ -88,7 +93,8 @@ public class AddUserIdTest extends AbstractWebAuthnTest {
assertNotNull(context.getUserId());
final byte[] userId = context.getUserId();
assert userId != null;
- assertEquals(userId.length,1);
+ assertEquals(userId.length,34);
+ // just check the first byte, the rest should be the same
assertEquals(userId[0], (byte)0xFF);
}
@@ -172,7 +178,7 @@ public class AddUserIdTest extends AbstractWebAuthnTest {
assertEquals(result.getId(), "InvalidRegistrationContext");
}
- @Test(expectedExceptions = ConstraintViolationException.class)
+ @Test
public void testNewUserId_MoreThan64Bytes() throws ComponentInitializationException {
addAction.setUserIdGeneratorStrategy(input -> {
final Random random = new Random();
@@ -180,9 +186,14 @@ public class AddUserIdTest extends AbstractWebAuthnTest {
random.nextBytes(byteArray);
return byteArray;
});
- addAction.initialize();
- context.setUsername("jdoe");
- addAction.execute(src);
+ addAction.initialize();
+ final Event result = addAction.execute(src);
+ assertNotNull(result);
+ assert result != null;
+ assertEquals(result.getId(), "InvalidRegistrationContext");
}
+
+
+
}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredentialTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredentialTest.java
index 495e6e8..6a14df1 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredentialTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredentialTest.java
@@ -39,8 +39,8 @@ import com.yubico.webauthn.data.UserIdentity;
import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
index eaf1eda..3d69350 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
@@ -45,9 +45,9 @@ import net.shibboleth.idp.plugin.authn.webauthn.authn.RegistrationResult;
import net.shibboleth.idp.plugin.authn.webauthn.exception.AssertionFailureException;
import net.shibboleth.idp.plugin.authn.webauthn.exception.RegistrationFailureException;
import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.collection.CollectionSupport;
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
index e4ca99f..cedd238 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
@@ -49,6 +49,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationCo
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
import net.shibboleth.idp.profile.testing.RequestContextBuilder;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
index 0ab06b7..4ed0f21 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
@@ -39,6 +39,7 @@ import com.yubico.webauthn.data.UserIdentity;
import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java
index 7643cc3..1dee29f 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java
@@ -38,6 +38,7 @@ import com.yubico.webauthn.data.UserVerificationRequirement;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.plugin.authn.webauthn.client.impl.MockWebAuthnClient;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.codec.DecodingException;
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
index 123bb02..3536138 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
@@ -35,7 +35,6 @@ import com.yubico.webauthn.data.PublicKeyCredential;
import com.yubico.webauthn.data.UserIdentity;
import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.collection.CollectionSupport;
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
index e1d182c..87cb832 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
@@ -43,7 +43,6 @@ import com.yubico.webauthn.data.PublicKeyCredential;
import com.yubico.webauthn.data.UserIdentity;
import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.shared.collection.CollectionSupport;
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java
similarity index 99%
rename from webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
rename to webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java
index 0c01fa8..b9ee8d7 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java
@@ -12,7 +12,7 @@
* limitations under the License.
*/
-package net.shibboleth.idp.plugin.authn.webauthn.impl;
+package net.shibboleth.idp.plugin.authn.webauthn.storage.impl;
import static org.testng.Assert.assertEquals;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list