[java-idp-plugin-webauthn] branch main updated: Externalise registration messages

Phil Smart philip.smart at jisc.ac.uk
Fri Jun 7 14:49:37 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=8ca5c5ee2633c091260fd395e442eed9a376939d

The following commit(s) were added to refs/heads/main by this push:
     new 8ca5c5e  Externalise registration messages
8ca5c5e is described below

commit 8ca5c5ee2633c091260fd395e442eed9a376939d
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jun 7 15:49:33 2024 +0100

    Externalise registration messages
    
     - Use new error and info message contexts to store information for the
    views to display e.g. key registered, key removed etc.
     - Add functions to retrieve that information using the
    MessageSourceAccessor (similar to the normal login view of the password
    flow).
      - Add new info and error functions to views
---
 .../context/WebAuthnRegistrationErrorContext.java  |  86 ++++++++++++++
 .../WebAuthnRegistrationInformationContext.java    |  86 ++++++++++++++
 .../RegistrationErrorMessageLookupFunction.java    | 122 ++++++++++++++++++++
 .../RegistrationInfoMessageLookupFunction.java     | 124 +++++++++++++++++++++
 .../ValidateAuthenticatorAttestationResponse.java  |   5 +
 .../webauthn-management-beans.xml                  |  28 ++++-
 .../webauthn-management-flow.xml                   |   5 +-
 .../webauthn-registration-beans.xml                |   9 ++
 .../webauthn-registration-flow.xml                 |  13 +--
 .../authn/webauthn/conf/authn/webauthn.properties  |   8 ++
 .../idp/plugin/authn/webauthn/messages.properties  |   7 +-
 .../plugin/authn/webauthn/views/webauthn-end.vm    |   2 -
 .../webauthn/views/webauthn-management-search.vm   |   2 -
 .../authn/webauthn/views/webauthn-management.vm    |  15 ++-
 .../authn/webauthn/views/webauthn-register.vm      |  20 ++--
 15 files changed, 501 insertions(+), 31 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationErrorContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationErrorContext.java
new file mode 100644
index 0000000..fbd1ca3
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationErrorContext.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationErrorContext;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * A WebAuthn version of {@link AuthenticationErrorContext} that holds information about WebAuthn registration events.
+ * For example, if a new credential registration is invalid.
+ */
+public class WebAuthnRegistrationErrorContext extends BaseContext {        
+    
+    /** Error conditions detected through classified error messages. */
+    @Nonnull private final Collection<String> classifiedErrors;
+    
+    /** Constructor. */
+    public WebAuthnRegistrationErrorContext() {
+        classifiedErrors = new LinkedHashSet<>();
+    }
+    
+    /**
+     * Get a mutable collection of error "tokens" associated with the context.
+     * 
+     * @return mutable collection of error strings
+     */
+    @Nonnull @Live public Collection<String> getClassifiedErrors() {
+        return classifiedErrors;
+    }
+    
+    /**
+     * Check for the presence of a particular error condition in the context.
+     * 
+     * @param error the condition to check for
+     * @return  true iff the context contains the error condition specified
+     */
+    public boolean isClassifiedError(@Nonnull @NotEmpty final String error) {
+        return classifiedErrors.contains(error);
+    }
+    
+    /**
+     * Adds a classified error to the context, ensuring that it will be returned
+     * from {@link #getLastClassifiedError()} until another is added.
+     * 
+     * @param error error to add
+     * 
+     * @return this context
+     */
+    @Nonnull public WebAuthnRegistrationErrorContext addClassifiedError(@Nonnull @NotEmpty final String error) {
+        // This is done to preserve ordering so that the error is the "last one added".
+        classifiedErrors.remove(error);
+        classifiedErrors.add(error);
+        return this;
+    }
+    
+    /**
+     * Gets the last classified error added, or null if none.
+     * 
+     * @return last error added or null
+     */
+    @Nullable public String getLastClassifiedError() {
+        return classifiedErrors.stream().reduce((first, second) -> second).orElse(null);
+    }
+
+}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationInformationContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationInformationContext.java
new file mode 100644
index 0000000..43cf9e5
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationInformationContext.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationWarningContext;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * A WebAuthn version of {@link AuthenticationWarningContext} that holds information about WebAuthn registration events.
+ * For example, if a new credential has been added or an old credential has been removed.
+ */
+public class WebAuthnRegistrationInformationContext extends BaseContext {
+        
+    /** Event conditions detected through classified messages. */
+    @Nonnull private final Collection<String> classifiedMessages;
+    
+    /** Constructor. */
+    public WebAuthnRegistrationInformationContext() {
+        classifiedMessages = new LinkedHashSet<>();
+    }
+    
+    /**
+     * Get a mutable collection of message "tokens" associated with the context.
+     * 
+     * @return mutable collection of message strings
+     */
+    @Nonnull @Live public Collection<String> getClassifiedMessages() {
+        return classifiedMessages;
+    }
+    
+    /**
+     * Check for the presence of a particular message condition in the context.
+     * 
+     * @param msg the condition to check for
+     * @return  true iff the context contains the msg condition specified
+     */
+    public boolean isClassifiedMessage(@Nonnull @NotEmpty final String msg) {
+        return classifiedMessages.contains(msg);
+    }
+    
+    /**
+     * Adds a classified message to the context, ensuring that it will be returned
+     * from {@link #getClassifiedMessages()} until another is added.
+     * 
+     * @param msg message to add
+     * 
+     * @return this context
+     */
+    @Nonnull public WebAuthnRegistrationInformationContext addClassifiedMessage(@Nonnull @NotEmpty final String msg) {
+        // This is done to preserve ordering so that the message is the "last one added".
+        classifiedMessages.remove(msg);
+        classifiedMessages.add(msg);
+        return this;
+    }
+    
+    /**
+     * Gets the last classified message added, or null if none.
+     * 
+     * @return last message added or null
+     */
+    @Nullable public String getLastClassifiedMessage() {
+        return classifiedMessages.stream().reduce((first, second) -> second).orElse(null);
+    }
+
+}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/RegistrationErrorMessageLookupFunction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/RegistrationErrorMessageLookupFunction.java
new file mode 100644
index 0000000..e67a6d6
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/RegistrationErrorMessageLookupFunction.java
@@ -0,0 +1,122 @@
+/*
+ * 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.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.context.support.ApplicationObjectSupport;
+import org.springframework.context.support.MessageSourceAccessor;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationErrorContext;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A function that examines the state of a request and produces an appropriate message for WebAuthn flow views.
+ * 
+ * <p>NOTE: The result of this function is <strong>NOT</strong> HTML-encoded in any way and must
+ * be encoded for safety if used.</p>
+ */
+public class RegistrationErrorMessageLookupFunction extends ApplicationObjectSupport
+                            implements ContextDataLookupFunction<ProfileRequestContext,String>{
+    
+    /** Message ID to use for generic messages. */
+    private String genericMessageID;
+    
+    /** Lookup strategy to locate the WebAuthn registration information context. */
+    @Nonnull 
+    private Function<ProfileRequestContext,WebAuthnRegistrationErrorContext> webauthnErrorContextLookupStrategy;
+    
+    
+    public RegistrationErrorMessageLookupFunction() {
+        webauthnErrorContextLookupStrategy = new ChildContextLookup<>(WebAuthnRegistrationErrorContext.class)
+                .compose(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+    }
+    
+    /**
+     * Set the strategy used to lookup the {@link WebAuthnRegistrationErrorContext}.
+     * 
+     * @param strategy The strategy to set.
+     */
+    public void setWebauthnErrorContextLookupStrategy(
+            final Function<ProfileRequestContext, WebAuthnRegistrationErrorContext> strategy) {
+        webauthnErrorContextLookupStrategy = Constraint.isNotNull(strategy,
+                "WebAuthnRegistrationErrorContextLookupStrategy can not be null");
+    }
+    
+    /**
+     * Sets whether non-message-based error messages should be exposed or turned into a more
+     * generic value.
+     * 
+     * @param id message ID
+     */
+    public void setGenericMessageID(@Nullable final String id) {
+        genericMessageID = StringSupport.trimOrNull(id);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String apply(@Nullable final ProfileRequestContext input) {
+        
+        final MessageSourceAccessor messageSource = getMessageSourceAccessor();
+        if (messageSource == null) {
+            return null;
+        }
+        
+        final WebAuthnRegistrationErrorContext regErrorCtx = webauthnErrorContextLookupStrategy.apply(input);
+        if (regErrorCtx == null) {
+            return null;
+        }
+        
+        final String classifiedError = regErrorCtx.getLastClassifiedError(); 
+        if (classifiedError != null && !classifiedError.isEmpty()) {
+            return getClassifiedMessage(messageSource, classifiedError);
+        }         
+        return null;
+    }
+    
+    /**
+     * Get classified message.
+     * 
+     * @param messageSource Spring message source
+     * @param classifiedMessage classified message
+     * 
+     * @return mapped message, or null if an empty string was produced.
+     */
+    @Nullable private String getClassifiedMessage(@Nonnull final MessageSourceAccessor messageSource,
+            @Nonnull final String classifiedMessage) {        
+        
+        String message = messageSource.getMessage(classifiedMessage, "");
+        if (message.isEmpty()) {
+            message = messageSource.getMessage( genericMessageID != null ? genericMessageID : 
+                "idp.webauthn.register.message", "Registration result: " 
+                    + classifiedMessage);
+        }        
+        if (message.isEmpty()) {
+            return null;
+        }
+        return message;
+
+    }
+    
+
+}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/RegistrationInfoMessageLookupFunction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/RegistrationInfoMessageLookupFunction.java
new file mode 100644
index 0000000..d6edd16
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/navigate/RegistrationInfoMessageLookupFunction.java
@@ -0,0 +1,124 @@
+/*
+ * 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 javax.annotation.concurrent.NotThreadSafe;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.context.support.ApplicationObjectSupport;
+import org.springframework.context.support.MessageSourceAccessor;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationInformationContext;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A function that examines the state of a request and produces an appropriate message for WebAuthn flow views.
+ * 
+ * <p>NOTE: The result of this function is <strong>NOT</strong> HTML-encoded in any way and must
+ * be encoded for safety if used.</p>
+ */
+ at NotThreadSafe
+public class RegistrationInfoMessageLookupFunction extends ApplicationObjectSupport
+                            implements ContextDataLookupFunction<ProfileRequestContext,String>{
+    
+    /** Message ID to use for generic messages. */
+    private String genericMessageID;
+    
+    /** Lookup strategy to locate the WebAuthn registration information context. */
+    @Nonnull 
+    private Function<ProfileRequestContext,WebAuthnRegistrationInformationContext> webauthnInfoContextLookupStrategy;
+    
+    /** Constructor.*/
+    public RegistrationInfoMessageLookupFunction() {
+        webauthnInfoContextLookupStrategy = new ChildContextLookup<>(WebAuthnRegistrationInformationContext.class)
+                .compose(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+    }
+    
+    /**
+     * Set the strategy used to lookup the {@link WebAuthnRegistrationInformationContext}.
+     * 
+     * @param strategy The strategy to set.
+     */
+    public void setWebauthnInfoContextLookupStrategy(
+            final Function<ProfileRequestContext, WebAuthnRegistrationInformationContext> strategy) {
+        webauthnInfoContextLookupStrategy = Constraint.isNotNull(strategy,
+                "WebauthnInfoContextLookupStrategy can not be null");
+    }
+    
+    /**
+     * Sets whether non-message-based error messages should be exposed or turned into a more
+     * generic value.
+     * 
+     * @param id message ID
+     */
+    public void setGenericMessageID(@Nullable final String id) {
+        genericMessageID = StringSupport.trimOrNull(id);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String apply(@Nullable final ProfileRequestContext input) {
+        
+        final MessageSourceAccessor messageSource = getMessageSourceAccessor();
+        if (messageSource == null) {
+            return null;
+        }
+
+        final WebAuthnRegistrationInformationContext regInfoCtx =  webauthnInfoContextLookupStrategy.apply(input);
+        if (regInfoCtx == null) {
+            return null;
+        }
+        
+        final String classifiedError = regInfoCtx.getLastClassifiedMessage(); 
+        if (classifiedError != null && !classifiedError.isEmpty()) {
+            return getClassifiedMessage(messageSource, classifiedError);
+        }         
+        return null;
+    }
+    
+    /**
+     * Get classified message.
+     * 
+     * @param messageSource Spring message source
+     * @param classifiedMessage classified message
+     * 
+     * @return mapped message, or null if an empty string was produced.
+     */
+    @Nullable private String getClassifiedMessage(@Nonnull final MessageSourceAccessor messageSource,
+            @Nonnull final String classifiedMessage) {        
+        
+        String message = messageSource.getMessage(classifiedMessage, "");
+        if (message.isEmpty()) {
+            message = messageSource.getMessage( genericMessageID != null ? genericMessageID : 
+                "idp.webauthn.register.message", "Registration result: " 
+                    + classifiedMessage);
+        }        
+        if (message.isEmpty()) {
+            return null;
+        }
+        return message;
+
+    }
+    
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java
index 651817f..2c5476f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java
@@ -32,6 +32,7 @@ import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 import net.shibboleth.idp.plugin.authn.webauthn.admin.RegistrationResult;
 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.context.WebAuthnRegistrationErrorContext;
 import net.shibboleth.idp.plugin.authn.webauthn.exception.RegistrationFailureException;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -71,6 +72,8 @@ public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnRe
             log.error("{} PublicKeyCredential containing the authenticator attestation response was null", 
                     getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
+            context.ensureSubcontext(WebAuthnRegistrationErrorContext.class)
+                .addClassifiedError(WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
             return false;
         }
         
@@ -78,6 +81,8 @@ public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnRe
         if (pkCredCreationOptions == null) {
             log.error("{} PublicKeyCredential creation options was null", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
+            context.ensureSubcontext(WebAuthnRegistrationErrorContext.class)
+            .   addClassifiedError(WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
             return false;
         }
         
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 c0d6969..42c252d 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
@@ -17,8 +17,22 @@
     <bean id="shibboleth.ChildLookup.WebAuthnManagementContext"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext) }" />
-
-
+   
+    <bean id="shibboleth.ChildLookup.WebAuthnAdminInfoContext" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <bean class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+            c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationInformationContext) }" />
+        </constructor-arg>
+        <constructor-arg name="f" ref="shibboleth.ChildLookup.WebAuthnManagementContext"/>
+    </bean>
+    
+    <bean id="shibboleth.ChildLookup.WebAuthnAdminErrorContext" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <bean class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+            c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationErrorContext) }" />
+        </constructor-arg>
+        <constructor-arg name="f" ref="shibboleth.ChildLookup.WebAuthnManagementContext"/>
+    </bean>
 
     <!-- Abstract parent beans -->
 
@@ -57,6 +71,16 @@
     <bean id="DeletePublicKeyCredential" parent="AbstractWebAuthnManagementAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AdminDeletePublicKeyCredential" />
 
+    <!-- Default functions to produce messages for the management view. -->
+    <bean id="DefaultAdminInfoMessageFunction" class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.RegistrationInfoMessageLookupFunction" lazy-init="true"
+        p:genericMessageID="%{idp.authn.webauthn.admin.management.genericMessageID:}" scope="prototype" p:webauthnInfoContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAdminInfoContext"/>
+
+    <bean id="DefaultAdminErrorMessageFunction" class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.RegistrationErrorMessageLookupFunction" lazy-init="true"
+        p:genericMessageID="%{idp.authn.webauthn.admin.management.genericMessageID:}" scope="prototype" p:webauthnErrorContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAdminErrorContext"/>
+        
+    <alias alias="AdminInfoMessageFunction" name="%{idp.authn.webauthn.admin.management.infoMessageFunction:DefaultAdminInfoMessageFunction}" />       
+    <alias alias="AdminErrorMessageFunction" name="%{idp.authn.webauthn.admin.management.errorMessageFunction:DefaultAdminErrorMessageFunction}" />
+    
 
 
 </beans>
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 33af679..c13fd7d 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
@@ -72,6 +72,8 @@
             <evaluate expression="T(net.shibboleth.idp.plugin.authn.webauthn.impl.WebAuthnEncoder)" result="viewScope.webAuthnEncoder"/>
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('AdminInfoMessageFunction')" result="viewScope.adminInfoMessageFunction" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('AdminErrorMessageFunction')" result="viewScope.adminErrorMessageFunction" />
         </on-render>
         
        <transition on="again" to="UsernameSearchView" />
@@ -86,8 +88,7 @@
         
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="LookupCredentials">
-            <!-- TODO externalise message bundle-->
-            <set name="flashScope.managementOutcomes" value="'Key was removed successfully'"/>
+             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext)).ensureSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationInformationContext)).addClassifiedMessage('KeyRemoved')"/>
         </transition>
     </action-state>  
 
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 2e91dbb..7465374 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
@@ -127,6 +127,15 @@
     <bean id="StorePublicKeyCredential" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.StorePublicKeyCredential"
         p:credentialRepository-ref="shibboleth.authn.webauthn.DefaultCredentialRepository" />
+    
+    <!-- Default functions to produce messages for the registration view. -->
+    <bean id="DefaultRegistrationInfoMessageFunction" class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.RegistrationInfoMessageLookupFunction" lazy-init="true"
+        p:genericMessageID="%{idp.authn.webauthn.registration.genericMessageID:}" />
+    <bean id="DefaultRegistrationErrorMessageFunction" class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.RegistrationErrorMessageLookupFunction" lazy-init="true"
+        p:genericMessageID="%{idp.authn.webauthn.registration.genericMessageID:}" />
+        
+    <alias alias="RegistrationInfoMessageFunction" name="%{idp.authn.webauthn.registration.infoMessageFunction:DefaultRegistrationInfoMessageFunction}" />       
+    <alias alias="RegistrationErrorMessageFunction" name="%{idp.authn.webauthn.registration.errorMessageFunction:DefaultRegistrationErrorMessageFunction}" />
 
 
 </beans>
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 b308e87..a52038c 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
@@ -82,6 +82,8 @@
             <evaluate expression="T(net.shibboleth.idp.plugin.authn.webauthn.impl.WebAuthnEncoder)" result="viewScope.webAuthnEncoder"/>
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('RegistrationInfoMessageFunction')" result="viewScope.registrationInfoMessageFunction" />
+            <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('RegistrationErrorMessageFunction')" result="viewScope.registrationErrorMessageFunction" />
         </on-render>
         
        <transition on="finish" to="RegistrationComplete" />
@@ -96,13 +98,9 @@
         <evaluate expression="StorePublicKeyCredential"/> 
         <evaluate expression="'proceed'" />
         
-        <transition on="InvalidRegistration" to="GeneratePublicKeyCredentialCreationOptions">
-            <!-- TODO externalise message bundle-->
-            <set name="flashScope.registrationErrorOutcomes" value="'Key registration unsuccessful'"/>
-        </transition>      
+        <transition on="InvalidRegistration" to="GeneratePublicKeyCredentialCreationOptions"/>
         <transition on="proceed" to="GeneratePublicKeyCredentialCreationOptions">
-            <!-- TODO externalise message bundle-->
-            <set name="flashScope.registrationOutcomes" value="'Key was registered successfully'"/>
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext)).ensureSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationInformationContext)).addClassifiedMessage('ValidRegistration')"/>
         </transition>
     </action-state>
     
@@ -112,8 +110,7 @@
         
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="GeneratePublicKeyCredentialCreationOptions">
-            <!-- TODO externalise message bundle-->
-            <set name="flashScope.registrationOutcomes" value="'Key was removed successfully'"/>
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext)).ensureSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationInformationContext)).addClassifiedMessage('KeyRemoved')"/>       
         </transition>
     </action-state> 
 
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 1684641..55e4e53 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
@@ -96,6 +96,10 @@ idp.authn.webauthn.supportedPrincipals = \
 #idp.authn.webauthn.registration.username.lowercase = false
 #idp.authn.webauthn.registration.username.trim = false
 
+#idp.authn.webauthn.registration.genericMessageID = 
+#idp.authn.webauthn.registration.infoMessageFunction = DefaultRegistrationInfoMessageFunction
+#idp.authn.webauthn.registration.errorMessageFunction = DefaultRegistrationErrorMessageFunction
+
 
 #### Administrator properties
 
@@ -107,6 +111,10 @@ idp.authn.webauthn.supportedPrincipals = \
 #idp.authn.webauthn.admin.management.defaultAuthenticationMethods = saml2/http://example.org/ac/classes/mfa
 #idp.authn.webauthn.admin.management.authenticated = true
 
+#idp.authn.webauthn.admin.management.genericMessageID = 
+#idp.authn.webauthn.admin.management.infoMessageFunction = DefaultAdminInfoMessageFunction
+#idp.authn.webauthn.admin.management.errorMessageFunction = DefaultAdminErrorMessageFunction
+
 #### Authentication properties
 
 # Which type of flow is supported? Usernameless or passwordless
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 cc9fd41..d8df1a2 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
@@ -45,4 +45,9 @@ idp.webauthn.ended = Your session has ended
 # When debug information has been enabled
 idp.webauthn.debug.title = Debugging
 idp.webauthn.debug.request = Request options
-idp.webauthn.register.debug.registration = Registration options
\ No newline at end of file
+idp.webauthn.register.debug.registration = Registration options
+
+# Messages to report back to the user during registration
+InvalidRegistration = Key registration unsuccessful
+ValidRegistration = Key was registered successfully
+KeyRemoved = Key was removed successfully
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-end.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-end.vm
index 2f864c7..e374d58 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-end.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-end.vm
@@ -7,9 +7,7 @@
 ## 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
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management-search.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management-search.vm
index 33aa561..3b554c3 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management-search.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management-search.vm
@@ -7,8 +7,6 @@
 ## flowExecutionKey - the SWF execution key (this is built into the flowExecutionUrl)
 ## profileRequestContext - root of context tree
 ## webAuthnManContext = WebAuthn management 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
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 4dd1f5a..fcf1eb3 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
@@ -8,13 +8,14 @@
 ## profileRequestContext - root of context tree
 ## webAuthnManContext = WebAuthn management 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
+## adminInfoMessageFunction - function to return info admin messages
+## adminErrorMessageFunction - function to return error admin messages
 
 ## Add CSP directives
 #set ($areYouSure =  "return confirm('#springMessageText('idp.webauthn.register.credential.remove.confirm', 'Are you sure')');")
@@ -41,11 +42,13 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
          <section>
             <div id="supportedDiv">
                <div class="centre">
-                  #if ($managementOutcomes)
-                    <p id="reg-success-outcome" class="output-message output--success">$managementOutcomes</p>
-                  #end
-                   #if ($managementErrorOutcomes)
-                    <p id="reg-error-outcome" class="output-message output--error">$managementErrorOutcomes</p>
+                  #set ($infoMessage = $adminInfoMessageFunction.apply($profileRequestContext))
+                  #if ($infoMessage)
+                        <p id="reg-success-outcome" class="output-message output--success">$encoder.encodeForHTML($infoMessage)</p>        
+                  #end                  
+                  #set ($errorMessage = $adminErrorMessageFunction.apply($profileRequestContext))
+                  #if ($errorMessage)
+                        <p id="reg-error-outcome" class="output-message output--error">$encoder.encodeForHTML($errorMessage)</p>        
                   #end 
                   <div class="hidden output-message output--error" id="error_div">
                         <p id="error_message"></p>
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 b96a8f0..81319d9 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
@@ -7,14 +7,14 @@
 ## flowExecutionKey - the SWF execution key (this is built into the flowExecutionUrl)
 ## profileRequestContext - root of context tree
 ## webauthnRegContext = WebAuthn registration 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
+## registrationInfoMessageFunction - function to produce information message for form
+## registrationErrorMessageFunction - function to produce error message for form
 ##
 #set ($debug = $environment.getProperty("idp.authn.webauthn.ui.debug", "false"))
 
@@ -106,13 +106,17 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
          </header>
          <section>
             <div id="supportedDiv">
-               <div class="centre">
-                  #if ($registrationOutcomes)
-                    <p id="reg-success-outcome" class="output-message output--success">$registrationOutcomes</p>
+               <div class="centre">               
+                  #set ($infoMessage = $registrationInfoMessageFunction.apply($profileRequestContext))
+                  #if ($infoMessage)
+                        <p id="reg-success-outcome" class="output-message output--success">$encoder.encodeForHTML($infoMessage)</p>        
                   #end
-                   #if ($registrationErrorOutcomes)
-                    <p id="reg-error-outcome" class="output-message output--error">$registrationErrorOutcomes</p>
-                  #end 
+                  
+                  #set ($errorMessage = $registrationErrorMessageFunction.apply($profileRequestContext))
+                  #if ($errorMessage)
+                        <p id="reg-error-outcome" class="output-message output--error">$encoder.encodeForHTML($errorMessage)</p>        
+                  #end
+                    
                   <div class="hidden output-message output--error" id="error_div">
                         <p id="error_message"></p>
                   </div>                

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


More information about the commits mailing list