[java-idp-plugin-webauthn] branch main updated: Add credential labeller
Phil Smart
philip.smart at jisc.ac.uk
Tue Oct 29 16:59:36 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=513daf5ece761e8cdf9fde2496a146845c6e30e0
The following commit(s) were added to refs/heads/main by this push:
new 513daf5 Add credential labeller
513daf5 is described below
commit 513daf5ece761e8cdf9fde2496a146845c6e30e0
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Oct 29 16:59:33 2024 +0000
Add credential labeller
- Allow for configurable, customisable, sets of credential labellers
- Labels are for display purposes only e.g. to display 'this is a
passkey', or 'this is only allowed for 2fa' type labels in both the
registration and management views.
---
.../webauthn/context/BaseWebAuthnContext.java | 2 +-
.../webauthn/storage/EnhancedCredentialRecord.java | 193 +++++++--------------
.../impl/AuthenticatorCapabilitiesLabeller.java | 89 ++++++++++
.../admin/impl/ChainingCredentialLabeller.java | 69 ++++++++
.../CreatePublicKeyCredentialCreationOptions.java | 4 +-
.../admin/impl/LabelAdminCredentialRecords.java | 90 ++++++++++
.../admin/impl/LabelCredentialRecords.java | 89 ++++++++++
.../admin/impl/LookupCredentialsForUser.java | 10 +-
.../admin/impl/PasskeyCredentialLabeller.java | 60 +++++++
.../CreatePublicKeyCredentialRequestOptions.java | 2 +-
.../webauthn/impl/LookupRegisteredCredentials.java | 11 +-
.../webauthn-management-beans.xml | 12 +-
.../webauthn-management-flow.xml | 5 +-
.../webauthn-registration-beans.xml | 11 +-
.../webauthn-registration-flow.xml | 3 +-
.../conf/authn/webauthn-management-config.xml | 25 +++
.../conf/authn/webauthn-registration-config.xml | 25 +++
.../idp/plugin/authn/webauthn/css/webauthn.css | 15 +-
.../idp/plugin/authn/webauthn/messages.properties | 9 +-
.../authn/webauthn/views/webauthn-management.vm | 28 +--
.../authn/webauthn/views/webauthn-register.vm | 16 +-
...eatePublicKeyCredentialCreationOptionsTest.java | 6 +-
.../impl/LookupRegisteredCredentialsTest.java | 13 +-
.../authn/webauthn/test-beans-registration.xml | 19 ++
24 files changed, 629 insertions(+), 177 deletions(-)
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 c2a2025..63b0261 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
@@ -95,7 +95,7 @@ public class BaseWebAuthnContext extends BaseContext {
}
/**
- * Set user's credentials that have already been registered with the credential repository.
+ * Set the user's credentials that have already been registered with the credential repository.
*
* @param credentials the set of credentials
*
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/EnhancedCredentialRecord.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/EnhancedCredentialRecord.java
index 2206abc..53a7f2c 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/EnhancedCredentialRecord.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/EnhancedCredentialRecord.java
@@ -14,6 +14,7 @@
package net.shibboleth.idp.plugin.authn.webauthn.storage;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -25,7 +26,6 @@ import javax.annotation.Nullable;
import com.yubico.fido.metadata.MetadataBLOBPayloadEntry;
import net.shibboleth.idp.plugin.authn.webauthn.metadata.AaguidEntry;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NonnullElements;
import net.shibboleth.shared.annotation.constraint.NotLive;
import net.shibboleth.shared.annotation.constraint.Unmodifiable;
@@ -36,30 +36,33 @@ import net.shibboleth.shared.logic.Constraint;
* An ephemeral wrapper class that holds a {@link CredentialRecord} and any associated metadata. Created and
* used during registration or authentication and then discarded. This is not meant to be serialised or stored.
*/
-public final class EnhancedCredentialRecord {
+public class EnhancedCredentialRecord {
- /** The wrapped credential registration.*/
- @Nonnull private final CredentialRecord credentialRegistration;
+ /** The wrapped credential record.*/
+ @Nonnull private final CredentialRecord credentialRecord;
/** Optional metadata about the authenticator. Will be an empty set if not used. */
- @Nonnull @Unmodifiable @NonnullElements @NotLive private final Set<MetadataBLOBPayloadEntry> authenticatorMetadata;
+ @Nonnull @Unmodifiable @NonnullElements @NotLive private Set<MetadataBLOBPayloadEntry> authenticatorMetadata;
/**
- * An AAGUID entry provided by the supplementary JSON file that describes authenticators not in the MDS feed e.g.
+ * An AAGUID entry provided by a supplementary JSON file that describes authenticators not in the MDS feed e.g.
* for software providers. Supplies a description and icon for UI only.
*/
- @Nullable private final AaguidEntry aaguidMetadata;
-
+ @Nullable private AaguidEntry aaguidMetadata;
+
+ /** Labels that can be set about the credential or authenticator that created it. For display only.*/
+ @Nonnull @Unmodifiable @NonnullElements @NotLive private List<String> labels;
+
/**
*
* Constructor.
*
- * @param builder the builder to construct this instance from
+ * @param credential the credential to wrap.
*/
- private EnhancedCredentialRecord(final Builder builder) {
- this.credentialRegistration = builder.credentialRecord;
- this.authenticatorMetadata = builder.authenticatorMetadata;
- this.aaguidMetadata = builder.aaguidMetadata;
+ public EnhancedCredentialRecord(@Nonnull final CredentialRecord credential) {
+ credentialRecord = Constraint.isNotNull(credential, "Credential can not be null");
+ authenticatorMetadata = CollectionSupport.emptySet();
+ labels = CollectionSupport.emptyList();
}
/**
@@ -67,8 +70,20 @@ public final class EnhancedCredentialRecord {
*
* @return the wrapped credential record.
*/
- public CredentialRecord getCredentialRegistration() {
- return credentialRegistration;
+ @Nonnull public CredentialRecord getCredentialRecord() {
+ return credentialRecord;
+ }
+
+ /**
+ * Set the authenticator metadata.
+ *
+ * @param metadata The authenticator metadata to set.
+ */
+ public void setAuthenticatorMetadata(@Nullable final Set<MetadataBLOBPayloadEntry> metadata) {
+ if (metadata != null) {
+ authenticatorMetadata = CollectionSupport.copyToSet(metadata.stream().filter(Objects::nonNull)
+ .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get());
+ }
}
/**
@@ -77,15 +92,46 @@ public final class EnhancedCredentialRecord {
* @return Returns the attestationMetadata.
*/
@Nonnull @Unmodifiable @NonnullElements @NotLive
- public synchronized Set<MetadataBLOBPayloadEntry> getAuthenticatorMetadata() {
+ public Set<MetadataBLOBPayloadEntry> getAuthenticatorMetadata() {
return authenticatorMetadata;
}
+
+ /**
+ * Set the AAGUID entry that describes the authenticator. Typically set for those authenticators that are not
+ * in the FIDO MDS feed.
+ *
+ * @param metadata The aaguid metadata to set.
+ */
+ public void setAaguidMetadata(@Nullable final AaguidEntry metadata) {
+ if (metadata != null) {
+ aaguidMetadata = metadata;
+ }
+ }
+
+ /**
+ * Set labels about the credential or authenticator that created it. For display only.
+ *
+ * @param labels The labels to set.
+ */
+ public void setLabels(@Nonnull final List<String> labelsIn) {
+ labels = CollectionSupport.copyToList(Constraint.isNotNull(labelsIn, "labels can not be null"));
+ }
+
+ /**
+ * Get labels about the credential or the authenticator that created it. For display only.
+ *
+ * @return the labels.
+ */
+ @Nonnull @Unmodifiable @NonnullElements @NotLive public List<String> getLabels() {
+ return labels;
+ }
+
/**
* Get the human-readable, short description of the authenticator (in English) iff either; the attestation metadata
* exists or aaguid metadata exists (preferencing the FIDO2 attestation metadata).
*
- * <p>If there is more than one metadata entry, it picks the first it can find with a description.</p>
+ * <p>If there is more than one metadata entry, pick the first it can find with a description.</p>
*
* @return the human-readable, short description of the authenticator (in English), or <code>null</code>.
*/
@@ -110,7 +156,7 @@ public final class EnhancedCredentialRecord {
/**
* Get a <code>data:</code> URL encoded PNG icon for the authenticator.
*
- * <p>If there is more than one metadata entry, it picks the first it can find with an icon. If the
+ * <p>If there is more than one metadata entry, pick the first it can find with an icon. If the
* FIDO2 attestation metadata does not exist, the icon is looked up in the aaguid metadata.</p>
*
* @return the icon encoded as a PNG <code>data:</code> URL
@@ -134,115 +180,4 @@ public final class EnhancedCredentialRecord {
}
- /**
- * The builder.
- *
- * @return the next stage
- */
- public static ICredentialRegistrationStage builder() {
- return new Builder();
- }
-
- /**
- * A builder stage.
- */
- public interface ICredentialRegistrationStage {
- /**
- * Set the credential record.
- *
- * @param credentialRecord the record
- *
- * @return the next stage
- */
- public IBuildStage withCredentialRecord(@Nonnull final CredentialRecord credentialRecord);
- }
-
- /**
- * A builder stage.
- */
- public interface IBuildStage {
- /**
- * Set the metadata for the authenticator that created the credential.
- *
- * @param authenticatorMetadata the metadata
- *
- * @return the next stage
- */
- public IBuildStage withAuthenticatorMetadata(
- @Nullable final Set<MetadataBLOBPayloadEntry> authenticatorMetadata);
-
- /**
- * Set the metadata for the authenticator from additional sources e.g. for those that do not exist in the
- * MDS feed such as most software providers.
- *
- * @param aaguidMetadata the metadata
- *
- * @return the next stage
- */
- public IBuildStage withAaguidMetadata(
- @Nullable final AaguidEntry aaguidMetadata);
-
- /**
- * Build this object.
- *
- * @return an instance of this object
- */
- public EnhancedCredentialRecord build();
- }
-
- /**
- * The builder.
- */
- public static final class Builder implements ICredentialRegistrationStage, IBuildStage {
- /** The wrapped credential record.*/
- @NonnullAfterInit private CredentialRecord credentialRecord;
- /** Optional metadata about the authenticator. Will be an empty set if not used. */
- @Nonnull @Unmodifiable @NonnullElements @NotLive
- private Set<MetadataBLOBPayloadEntry> authenticatorMetadata;
- /**
- * An entry provided by the supplementary JSON file that describes authenticators not in the MDS feed e.g. for
- * software providers. Supplies a description and icon for UI only.
- */
- @Nullable private AaguidEntry aaguidMetadata;
-
- /** Constructor.*/
- private Builder() {
- authenticatorMetadata = CollectionSupport.emptySet();
- }
-
- @Override
- public IBuildStage withCredentialRecord(
- @Nonnull final CredentialRecord record) {
- credentialRecord = Constraint.isNotNull(record, "Credential record can not be null");
- return this;
- }
-
- @Override
- public IBuildStage withAuthenticatorMetadata(@Nullable final Set<MetadataBLOBPayloadEntry> metadata) {
- if (metadata != null) {
- authenticatorMetadata = CollectionSupport.copyToSet(metadata.stream().filter(Objects::nonNull)
- .collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get());
- }
- return this;
- }
-
-
- /** {@inheritDoc} */
- @Override
- public IBuildStage withAaguidMetadata(@Nullable final AaguidEntry metadata) {
- aaguidMetadata = metadata;
- return this;
- }
-
- @Override
- public EnhancedCredentialRecord build() {
- return new EnhancedCredentialRecord(this);
- }
-
- }
-
-
-
-
-
}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AuthenticatorCapabilitiesLabeller.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AuthenticatorCapabilitiesLabeller.java
new file mode 100644
index 0000000..f1667e3
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AuthenticatorCapabilitiesLabeller.java
@@ -0,0 +1,89 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+
+/**
+ * An authenticator capabilities labeller function that maps authenticator capabilities to labels for display.
+ */
+ at ThreadSafe
+public class AuthenticatorCapabilitiesLabeller
+ implements BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> {
+
+ /** Map authenticator capabilities to labels to display in the registration pages.*/
+ @Nonnull @NonnullElements @Unmodifiable @NotLive private Map<Pair<String,String>,List<String>> capabilityToLabelMap;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param map the authenticator to capabilities map. The key is a {@link Pair} where the first entry is the
+ * capability and the second entry is the value of that capability to match on. The Value of the map is the label
+ * to set.
+ */
+ public AuthenticatorCapabilitiesLabeller(
+ @Nullable @ParameterName(name="capabilityToLabelMap") final Map<Pair<String,String>,List<String>> map) {
+ if (map != null) {
+ capabilityToLabelMap = CollectionSupport.copyToMap(map);
+ } else {
+ capabilityToLabelMap = CollectionSupport.emptyMap();
+ }
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<String> apply(@Nullable final EnhancedCredentialRecord cred,
+ @Nullable final ProfileRequestContext prc) {
+
+ if (cred == null) {
+ return CollectionSupport.emptyList();
+ }
+ final List<String> labels = new ArrayList<>();
+ final CredentialRecord credential = cred.getCredentialRecord();
+
+ final Map<String, String> capabilities = credential.getAuthenticatorCapabilities();
+ for (final Entry<Pair<String,String>, List<String>> entry : capabilityToLabelMap.entrySet()) {
+ if (capabilities.containsKey(entry.getKey().getFirst())) {
+ final String value = capabilities.get(entry.getKey().getFirst());
+ if (value != null && value.equals(entry.getKey().getSecond())) {
+ labels.addAll(entry.getValue());
+ }
+
+ }
+ }
+ return labels;
+ }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ChainingCredentialLabeller.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ChainingCredentialLabeller.java
new file mode 100644
index 0000000..a82346c
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ChainingCredentialLabeller.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.admin.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * A credential labeller that labels credentials based on a chain of other labellers. All labellers are iterated over
+ * to produce the final list of labels.
+ */
+public class ChainingCredentialLabeller
+ implements BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>>{
+
+ /** The chain of credential labellers to use. All are iterated over to produce the final set of labels.*/
+ private final List<BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>>> labellers;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param labellersIn The chain of credential labellers to use.
+ */
+ public ChainingCredentialLabeller(@Nullable @ParameterName(name="labellers")
+ final List<BiFunction<EnhancedCredentialRecord, ProfileRequestContext,
+ List<String>>> labellersIn) {
+
+ if (labellersIn != null) {
+ labellers = CollectionSupport.copyToList(labellersIn);
+ } else {
+ labellers = CollectionSupport.emptyList();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<String> apply(final EnhancedCredentialRecord credential, final ProfileRequestContext prc) {
+ final List<String> labels = new ArrayList<>();
+ for (final BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labeller : labellers) {
+ final List<String> newLabels = labeller.apply(credential, prc);
+ if (newLabels != null) {
+ labels.addAll(newLabels);
+ }
+ }
+ return labels;
+ }
+
+}
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 46fe5a5..f5a09a3 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
@@ -110,8 +110,10 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnAc
try {
+ @SuppressWarnings("null")
final Set<PublicKeyCredentialDescriptor> existingCredentialDescriptors = context.getExistingCredentials()
- .stream().map(cred -> cred.getCredentialRegistration())
+ .stream().map(cred -> cred.getCredentialRecord())
+ .filter(Objects::nonNull)
.map(cred -> cred.toPublicKeyCredentialDescriptor())
.filter(Objects::nonNull)
.collect(Collectors.toSet());
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelAdminCredentialRecords.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelAdminCredentialRecords.java
new file mode 100644
index 0000000..ca2c315
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelAdminCredentialRecords.java
@@ -0,0 +1,90 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.function.BiFunction;
+
+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.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/**
+ * Use the labelling function to add labels to each {@link EnhancedCredentialRecord} found in the context. The labels
+ * can then be displayed in the registration view.
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @post Add labels to each enhanced credential record in the context
+ */
+// TODO another admin function which is almost identical to the registration variant
+public class LabelAdminCredentialRecords extends AbstractWebAuthnAction<WebAuthnManagementContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LabelAdminCredentialRecords.class);
+
+ /** The labeller used to generate labels for the credential, for display only.*/
+ @Nonnull @NotLive private BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labeller;
+
+ /**
+ * Constructor.
+ *
+ * @param defaultStrategy
+ */
+ protected LabelAdminCredentialRecords() {
+ super(new ChildContextLookup<>(WebAuthnManagementContext.class).
+ compose(new ChildContextLookup<>(AuthenticationContext.class)));
+ labeller = (cred, prc) -> CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set the labeller used to generate labels for the credential, for display only.
+ *
+ * @param labeller The labeller to set.
+ */
+ public void setLabeller(@Nonnull
+ final BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labellerIn) {
+ checkSetterPreconditions();
+ labeller = Constraint.isNotNull(labellerIn, "labeller can not be null");
+ }
+
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final WebAuthnManagementContext context) {
+
+ final Collection<EnhancedCredentialRecord> credentials = context.getFoundCredentials();
+
+ log.trace("{} Labelling '{}' credentials",getLogPrefix(), credentials.size());
+ for (final EnhancedCredentialRecord credential : credentials) {
+ final List<String> labels = labeller.apply(credential, profileRequestContext);
+ if (labels != null) {
+ log.trace("{} Added labels '{}' for credential '{}'",
+ getLogPrefix(), labels, credential.getCredentialRecord().getCredentialIdBase64Url());
+ credential.setLabels(labels);
+ }
+ }
+ }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelCredentialRecords.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelCredentialRecords.java
new file mode 100644
index 0000000..14f71a5
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelCredentialRecords.java
@@ -0,0 +1,89 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.function.BiFunction;
+
+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.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/**
+ * Use the labelling function to add labels to each {@link EnhancedCredentialRecord} found in the context. The labels
+ * can then be displayed in the registration view.
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @post Add labels to each enhanced credential record in the context
+ */
+public class LabelCredentialRecords extends AbstractWebAuthnAction<BaseWebAuthnContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LabelCredentialRecords.class);
+
+ /** The labeller used to generate labels for the credential, for display only.*/
+ @Nonnull @NotLive private BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labeller;
+
+ /**
+ * Constructor.
+ *
+ * @param defaultStrategy
+ */
+ protected LabelCredentialRecords() {
+ super(new ChildContextLookup<>(BaseWebAuthnContext.class).
+ compose(new ChildContextLookup<>(AuthenticationContext.class)));
+ labeller = (cred, prc) -> CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set the labeller used to generate labels for the credential, for display only.
+ *
+ * @param labeller The labeller to set.
+ */
+ public void setLabeller(@Nonnull
+ final BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labellerIn) {
+ checkSetterPreconditions();
+ labeller = Constraint.isNotNull(labellerIn, "labeller can not be null");
+ }
+
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final BaseWebAuthnContext context) {
+
+ final Collection<EnhancedCredentialRecord> credentials = context.getExistingCredentials();
+
+ log.trace("{} Labelling '{}' credentials",getLogPrefix(), credentials.size());
+ for (final EnhancedCredentialRecord credential : credentials) {
+ final List<String> labels = labeller.apply(credential, profileRequestContext);
+ if (labels != null) {
+ log.trace("{} Added labels '{}' for credential '{}'",
+ getLogPrefix(), labels, credential.getCredentialRecord().getCredentialIdBase64Url());
+ credential.setLabels(labels);
+ }
+ }
+ }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
index 530841e..58a3b03 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
@@ -32,7 +32,6 @@ import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContex
import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord.IBuildStage;
import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.collection.CollectionSupport;
@@ -91,13 +90,14 @@ public class LookupCredentialsForUser extends AbstractWebAuthnAction<WebAuthnMan
new HashSet<>(credentials.size());
credentials.stream().filter(Objects::nonNull).forEach(cred -> {
- final IBuildStage builder = EnhancedCredentialRecord.builder().withCredentialRecord(cred);
+ assert cred != null;
+ final EnhancedCredentialRecord enhancedRecord = new EnhancedCredentialRecord(cred);
final byte[] aaguid = cred.getAaguid();
if (aaguid != null && aaguid.length == 16) {
- builder.withAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(aaguid)));
- builder.withAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(aaguid))));
+ enhancedRecord.setAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(aaguid)));
+ enhancedRecord.setAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(aaguid))));
}
- enhancedCredentialRegistrations.add(builder.build());
+ enhancedCredentialRegistrations.add(enhancedRecord);
});
context.setFoundCredentials(enhancedCredentialRegistrations);
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PasskeyCredentialLabeller.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PasskeyCredentialLabeller.java
new file mode 100644
index 0000000..65b9894
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PasskeyCredentialLabeller.java
@@ -0,0 +1,60 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * A credential labeller function that labels credentials as 'Passkey' if they are discoverable.
+ */
+ at ThreadSafe
+public class PasskeyCredentialLabeller
+ implements BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> {
+
+ /**
+ *
+ * Constructor.
+ **/
+ public PasskeyCredentialLabeller() {
+ super();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<String> apply(@Nullable final EnhancedCredentialRecord cred,
+ @Nullable final ProfileRequestContext prc) {
+
+ if (cred == null) {
+ return CollectionSupport.emptyList();
+ }
+ final List<String> labels = new ArrayList<>();
+ final CredentialRecord credential = cred.getCredentialRecord();
+ if (credential.isDiscoverable().isPresent() && credential.isDiscoverable().get()==Boolean.TRUE) {
+ labels.add("Passkey");
+ }
+ return labels;
+ }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
index 72cc0b9..060ebc6 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
@@ -81,7 +81,7 @@ public class CreatePublicKeyCredentialRequestOptions extends AbstractWebAuthnAct
try {
final Collection<EnhancedCredentialRecord> existingCredentials = context.getExistingCredentials();
final List<PublicKeyCredentialDescriptor> existingCredentialDescriptors = existingCredentials.stream()
- .map(cred -> cred.getCredentialRegistration())
+ .map(cred -> cred.getCredentialRecord())
.map(cred -> cred.toPublicKeyCredentialDescriptor())
.filter(Objects::nonNull)
.collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableList())).get();
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index 284c620..4b98a4f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
@@ -37,7 +37,6 @@ import net.shibboleth.idp.plugin.authn.webauthn.authn.WebAuthnAuthenticationEven
import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord.IBuildStage;
import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -179,12 +178,14 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebA
new HashSet<>(credentials.size());
credentials.stream().filter(Objects::nonNull).forEach(cred -> {
- final IBuildStage builder = EnhancedCredentialRecord.builder().withCredentialRecord(cred);
+ assert cred != null;
+ final EnhancedCredentialRecord enhancedRecord = new EnhancedCredentialRecord(cred);
+ final byte[] aaguid = cred.getAaguid();
if (cred.getAaguid() != null) {
- builder.withAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(cred.getAaguid())));
- builder.withAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(cred.getAaguid()))));
+ enhancedRecord.setAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(aaguid)));
+ enhancedRecord.setAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(aaguid))));
}
- enhancedCredentialRegistrations.add(builder.build());
+ enhancedCredentialRegistrations.add(enhancedRecord);
});
context.setExistingCredentials(enhancedCredentialRegistrations);
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
index 891e252..ed54578 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
@@ -66,13 +66,21 @@
<bean id="UpdateAdminSearchUsernameWithC14nPrincipal" parent="AbstractWebAuthnBaseAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.UpdateAdminContextWithC14nPrincipal"
- p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext"/>
-
+ p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext"/>
<bean id="LookupCredentialsForUser" parent="AbstractWebAuthnBaseAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LookupCredentialsForUser"
p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext" />
+ <bean id="LabelCredentialRecords" parent="AbstractWebAuthnBaseAction" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LabelAdminCredentialRecords"
+ p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext"
+ p:labeller="#{getObject('%{idp.authn.webauthn.registration.labeller:shibboleth.authn.WebAuthn.ChainingCredentialLabeller}')}"/>
+
+ <bean id="shibboleth.authn.WebAuthn.ChainingCredentialLabeller" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ChainingCredentialLabeller"
+ c:labellers="#{getObject('shibboleth.authn.WebAuthn.CredentialLabellerList')}"/>
+
<bean id="ExtractKeyRemovalInformationFromFormRequest" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyInformationFromFormRequest"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml
index 98192cc..8cc5a5b 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-flow.xml
@@ -68,11 +68,12 @@
<evaluate expression="UpdateAdminSearchUsernameWithC14nPrincipal"/>
<evaluate expression="'proceed'" />
- <transition on="proceed" to="LookupCredentials" />
+ <transition on="proceed" to="LookupCredentials" />
</action-state>
<action-state id="LookupCredentials">
- <evaluate expression="LookupCredentialsForUser"/>
+ <evaluate expression="LookupCredentialsForUser"/>
+ <evaluate expression="LabelCredentialRecords"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="ManagementView" />
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 1a8be50..ce929a9 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
@@ -132,7 +132,16 @@
<bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnBaseAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
-
+
+ <bean id="LabelCredentialRecords" parent="AbstractWebAuthnBaseAction" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LabelCredentialRecords"
+ p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"
+ p:labeller="#{getObject('%{idp.authn.webauthn.registration.labeller:shibboleth.authn.WebAuthn.ChainingCredentialLabeller}')}"/>
+
+ <bean id="shibboleth.authn.WebAuthn.ChainingCredentialLabeller" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ChainingCredentialLabeller"
+ c:labellers="#{getObject('shibboleth.authn.WebAuthn.CredentialLabellerList')}"/>
+
<bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction" scope="prototype"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"
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 c729ece..cc6c46a 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
@@ -92,7 +92,8 @@
<!-- Recreate a registration context from the result of authentication, and create WebAuthn GET creation options -->
<action-state id="GeneratePublicKeyCredentialCreationOptions">
<evaluate expression="PopulateWebAuthnRegistrationContext"/>
- <evaluate expression="LookupRegisteredCredentials"/>
+ <evaluate expression="LookupRegisteredCredentials"/>
+ <evaluate expression="LabelCredentialRecords"/>
<evaluate expression="GenerateServerChallenge"/>
<evaluate expression="AddUserName"/>
<evaluate expression="AddUserId"/>
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml
index 92416d5..07605bd 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml
@@ -8,6 +8,31 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="initialize" default-destroy-method="destroy" default-lazy-init="true">
+
+ <!--
+ The default credential labeller list
+ -->
+ <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
+
+ <bean id="CapabilitiesCredentialLabeller" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AuthenticatorCapabilitiesLabeller"
+ c:_0-ref="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap"/>
+
+ <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
+ </util:list>
+
+ <!--
+ The default capabilities to labels map. The capabilities are first determined by the Inspectors.
+ -->
+ <util:map id="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap">
+ <entry>
+ <key>
+ <bean parent="shibboleth.Pair"
+ p:first="2faOnly" p:second="true"/>
+ </key>
+ <value>SecondFactorOnly</value>
+ </entry>
+ </util:map>
</beans>
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml
index 9202e2c..ce177f3 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml
@@ -28,5 +28,30 @@
class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.SecondFactorOnlyAuthenticatorInspector"
p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.registration.authenticator.inspector.secondFactorOnlyAuthenticators:null}"/>
</util:list>
+
+ <!--
+ The default credential labeller list
+ -->
+ <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
+
+ <bean id="CapabilitiesCredentialLabeller" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AuthenticatorCapabilitiesLabeller"
+ c:_0-ref="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap"/>
+
+ <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
+ </util:list>
+
+ <!--
+ The default capabilities to labels map. The capabilities are first determined by the Inspectors.
+ -->
+ <util:map id="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap">
+ <entry>
+ <key>
+ <bean parent="shibboleth.Pair"
+ p:first="2faOnly" p:second="true"/>
+ </key>
+ <value>SecondFactorOnly</value>
+ </entry>
+ </util:map>
</beans>
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
index 719675a..8034b7b 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
@@ -20,7 +20,7 @@ tr:hover {
}
.inline {
- display:inline;
+ display: inline;
}
@@ -89,5 +89,16 @@ tr:hover {
.authenticator-logo {
vertical-align: middle;
- max-width:2em;
+ max-width: 2em;
+
+}
+
+.label {
+ color: white;
+ padding: 5px;
+ font-size: 75%;
+}
+
+.info {
+ background-color: #2196F3;
}
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
index e84d3e1..7208933 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/messages.properties
@@ -23,8 +23,8 @@ idp.webauthn.register.credential.nickname = Credential nickname
idp.webauthn.register.credential.remove.confirm = Are you sure
idp.webauthn.register.table.header.keyName = Key Name
idp.webauthn.register.table.header.authenticatorDescription = Authenticator
-idp.webauthn.register.table.header.transports = Transports
-idp.webauthn.register.table.header.passkey = Passkey?
+idp.webauthn.register.table.header.authenticatorIcon = Icon
+idp.webauthn.register.table.header.labels = Labels
idp.webauthn.register.table.header.registrationTime = Registration Time
idp.webauthn.register.table.header.action = Action
idp.webauthn.register.table.unknownCredential = unknown
@@ -43,11 +43,10 @@ idp.webauthn.admin.header = Registered keys for
idp.webauthn.admin.table.header.userName = User
idp.webauthn.admin.table.header.keyName = Key Name
idp.webauthn.admin.table.header.authenticatorDescription = Authenticator
-idp.webauthn.admin.table.header.transports = Transports
-idp.webauthn.admin.table.header.passkey = Passkey?
+idp.webauthn.admin.table.header.authenticatorIcon = Icon
idp.webauthn.admin.table.header.registrationTime = Registration Time
idp.webauthn.admin.table.header.action = Action
-idp.webauthn.admin.noKeys = There are no registered keys
+idp.webauthn.admin.noKeys = There are no registered keys
idp.webauthn.admin.unsupported = Your browser is not WebAuthn compatible
idp.webauthn.admin.table.hasMetadata = Yes
idp.webauthn.admin.table.noMetadata = No
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
index 2f56a90..0f81268 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
@@ -60,27 +60,31 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
<tr>
<th>#springMessageText("idp.webauthn.admin.table.header.keyName", "Key Name")</th>
<th>#springMessageText("idp.webauthn.admin.table.header.authenticatorDescription", "Authenticator")</th>
- <th>#springMessageText("idp.webauthn.admin.table.header.transports", "Transports")</th>
- <th>#springMessageText("idp.webauthn.admin.table.header.passkey", "Passkey?")</th>
+ <th>#springMessageText("idp.webauthn.admin.table.header.authenticatorIcon", "Icon")</th>
+ <th>#springMessageText("idp.webauthn.admin.table.header.labels", "Labels")</th>
<th>#springMessageText("idp.webauthn.admin.table.header.registrationTime", "Registration Time")</th>
<th>#springMessageText("idp.webauthn.admin.table.header.hasMetadata", "Metadata?")</th>
<th>#springMessageText("idp.webauthn.admin.table.header.action", "Action")</th>
</tr>
#foreach($cred in $webAuthnManContext.foundCredentials)
<tr>
- <td>$encoder.encodeForHTML($cred.credentialRegistration.nickname)</td>
+ <td>$encoder.encodeForHTML($cred.credentialRecord.nickname)</td>
#if ($cred.authenticatorDescription)
- <td>
- #if ($cred.icon)
- <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon)" alt="authenticator-icon"/>
- #end
- $encoder.encodeForHTML($cred.authenticatorDescription)</td>
+ <td>$encoder.encodeForHTML($cred.authenticatorDescription)</td>
#else
<td>#springMessageText("idp.webauthn.admin.table.unknownCredential", "unknown")</td>
#end
- <td>$encoder.encodeForHTML($webAuthnEncoder.formatTransports($cred.credentialRegistration.transports))</td>
- <td>$encoder.encodeForHTML($webAuthnEncoder.formatDiscoverable($cred.credentialRegistration.isDiscoverable()))</td>
- <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.credentialRegistration.registrationTime))</td>
+ #if ($cred.icon)
+ <td> <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon)" alt="$encoder.encodeForHTML($cred.credentialRecord.nickname)-authenticator-icon"/></td>
+ #else
+ <td></td>
+ #end
+ <td>
+ #foreach($label in $cred.labels)
+ <span class="label info">$encoder.encodeForHTML($label)</span>
+ #end
+ </td>
+ <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.credentialRecord.registrationTime))</td>
<td>
#if ($webAuthnEncoder.isAuthenticatorMetadataAttached($cred))
#springMessageText("idp.webauthn.admin.table.hasMetadata", "Yes")
@@ -91,7 +95,7 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
<td>
<form id="delete_key_form" action="$flowExecutionUrl" method="post">
#parse("csrf/csrf.vm")
- <input type="hidden" name="credentialId" value="$cred.credentialRegistration.credentialIdBase64Url"/>
+ <input type="hidden" name="credentialId" value="$cred.credentialRecord.credentialIdBase64Url"/>
<button class="webauthn-table-button" onclick="$areYouSure" id="removeButton" type="submit" name="_eventId_deleteKey">
#springMessageText("idp.webauthn.admin.credential.remove", "Remove")</button>
</form>
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 f5bb507..8047f54 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
@@ -129,29 +129,33 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
<th>#springMessageText("idp.webauthn.register.table.header.keyName", "Key Name")</th>
<th>#springMessageText("idp.webauthn.register.table.header.authenticatorDescription", "Authenticator")</th>
<th>#springMessageText("idp.webauthn.register.table.header.authenticatorIcon", "Icon")</th>
- <th>#springMessageText("idp.webauthn.register.table.header.passkey", "Passkey?")</th>
+ <th>#springMessageText("idp.webauthn.register.table.header.labels", "Labels")</th>
<th>#springMessageText("idp.webauthn.register.table.header.registrationTime", "Registration Time")</th>
<th>#springMessageText("idp.webauthn.register.table.header.action", "Action")</th>
</tr>
#foreach($cred in $webauthnRegContext.existingCredentials)
<tr>
- <td>$encoder.encodeForHTML($cred.credentialRegistration.nickname)</td>
+ <td>$encoder.encodeForHTML($cred.credentialRecord.nickname)</td>
#if ($cred.authenticatorDescription)
<td>$encoder.encodeForHTML($cred.authenticatorDescription)</td>
#else
<td>#springMessageText("idp.webauthn.register.table.unknownCredential", "unknown")</td>
#end
#if ($cred.icon)
- <td> <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon)" alt="$encoder.encodeForHTML($cred.credentialRegistration.nickname)-authenticator-icon"/></td>
+ <td> <img class="authenticator-logo" src="$encoder.encodeForHTML($cred.icon)" alt="$encoder.encodeForHTML($cred.credentialRecord.nickname)-authenticator-icon"/></td>
#else
<td></td>
#end
- <td>$encoder.encodeForHTML($webAuthnEncoder.formatDiscoverable($cred.credentialRegistration.isDiscoverable()))</td>
- <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.credentialRegistration.registrationTime))</td>
+ <td>
+ #foreach($label in $cred.labels)
+ <span class="label info">$encoder.encodeForHTML($label)</span>
+ #end
+ </td>
+ <td>$encoder.encodeForHTML($webAuthnEncoder.formatInstant($cred.credentialRecord.registrationTime))</td>
<td>
<form id="delete_key_form" action="$flowExecutionUrl" method="post">
#parse("csrf/csrf.vm")
- <input type="hidden" name="credentialId" value="$cred.credentialRegistration.credentialIdBase64Url"/>
+ <input type="hidden" name="credentialId" value="$cred.credentialRecord.credentialIdBase64Url"/>
<button class="webauthn-table-button" onclick="$areYouSure" id="removeButton" type="submit" name="_eventId_deleteKey">
#springMessageText("idp.webauthn.register.credential.remove", "Remove")</button>
</form>
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java
index 532b764..3242272 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptionsTest.java
@@ -96,9 +96,9 @@ public class CreatePublicKeyCredentialCreationOptionsTest extends AbstractWebAut
public void testCreateOptions_WithExcludeCredentials() throws Exception {
context.setServerChallenge(generateRandomBytes(17));
action.initialize();
-
- context.setExistingCredentials(CollectionSupport.setOf(EnhancedCredentialRecord.builder()
- .withCredentialRecord(createCredentialRegistration()).build()));
+
+ context.setExistingCredentials(CollectionSupport.setOf(
+ new EnhancedCredentialRecord(createCredentialRegistration())));
final Event result = action.execute(src);
assertNull(result);
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 bfabb3d..030947a 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
@@ -128,11 +128,22 @@ public class LookupRegisteredCredentialsTest extends AbstractWebAuthnTest {
@SuppressWarnings("null")
@Test
- public void testLookup_NoUsername() throws ComponentInitializationException {
+ public void testLookup_NoUsernameRequired() throws ComponentInitializationException {
+ action.setUsernameRequired(true);
action.initialize();
final Event event = action.execute(src);
assertNotNull(event);
assertEquals(event.getId(), EventIds.INVALID_PROFILE_CTX);
}
+
+ @SuppressWarnings("null")
+ @Test
+ public void testLookup_NoUsernameNotRequired() throws ComponentInitializationException {
+ action.setUsernameRequired(false);
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ assertEquals(context.getExistingCredentials().size(), 0);
+ }
}
diff --git a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml
index d48f123..0dd2022 100644
--- a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml
+++ b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml
@@ -42,6 +42,25 @@
class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.SecondFactorOnlyAuthenticatorInspector"
p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.registration.authenticator.inspector.secondFactorOnlyAuthenticators:null}"/>
</util:list>
+
+ <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
+
+ <bean id="CapabilitiesCredentialLabeller" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AuthenticatorCapabilitiesLabeller"
+ c:_0-ref="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap"/>
+
+ <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
+ </util:list>
+
+ <util:map id="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap">
+ <entry>
+ <key>
+ <bean parent="shibboleth.Pair"
+ p:first="2faOnly" p:second="true"/>
+ </key>
+ <value>SecondFactorOnly</value>
+ </entry>
+ </util:map>
</beans>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list