[java-idp-plugin-webauthn] branch main updated: Enabled authn flow resumption after inline key enrolment
Phil Smart
philip.smart at jisc.ac.uk
Wed Oct 2 08:34:10 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=ad7c7e824ef3a70736b86475aa436d01219fa5b8
The following commit(s) were added to refs/heads/main by this push:
new ad7c7e8 Enabled authn flow resumption after inline key enrolment
ad7c7e8 is described below
commit ad7c7e824ef3a70736b86475aa436d01219fa5b8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Oct 1 16:47:32 2024 +0100
Enabled authn flow resumption after inline key enrolment
---
.../webauthn/context/InlineEnrolmentContext.java | 50 +++++++++
.../impl/InlineEnrolmentRedirectFunction.java | 81 +++++++++++++
.../admin/impl/PopulateInlineEnrolmentContext.java | 125 +++++++++++++++++++++
.../webauthn-registration-beans.xml | 11 +-
.../webauthn-registration-flow.xml | 6 +-
.../authn/webauthn/conf/authn/webauthn.properties | 3 +
.../idp/plugin/authn/webauthn/messages.properties | 1 +
.../plugin/authn/webauthn/views/webauthn-authn.vm | 5 +-
.../authn/webauthn/views/webauthn-register.vm | 7 +-
9 files changed, 284 insertions(+), 5 deletions(-)
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/InlineEnrolmentContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/InlineEnrolmentContext.java
new file mode 100644
index 0000000..c4503be
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/InlineEnrolmentContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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.net.URL;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+/**
+ * A context that holds information about a resumable SSO flow.
+ */
+public class InlineEnrolmentContext extends BaseContext {
+
+ /** The URL to redirect the user to once the flow has ended.*/
+ @Nullable private URL ssoUrl;
+
+ /**
+ * Set the URL to redirect the user to after the flow has ended.
+ *
+ * @param url The sso Url to set.
+ */
+ public InlineEnrolmentContext setSsoUrl(@Nullable final URL url) {
+ ssoUrl = url;
+ return this;
+ }
+
+ /**
+ * Get the URL to redirect the user to after the flow has ended.
+ *
+ * @return the sso Url.
+ */
+ @Nullable public URL getSsoUrl() {
+ return ssoUrl;
+ }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/InlineEnrolmentRedirectFunction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/InlineEnrolmentRedirectFunction.java
new file mode 100644
index 0000000..1f85b18
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/InlineEnrolmentRedirectFunction.java
@@ -0,0 +1,81 @@
+/*
+ * 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.net.URL;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.InlineEnrolmentContext;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A function that generates a redirect URL to return the user to a resource relative to the server root.
+ */
+public class InlineEnrolmentRedirectFunction extends AbstractInitializableComponent
+ implements BiFunction<RequestContext,ProfileRequestContext,String>{
+
+ /** Strategy used to locate the {@link InlineEnrolementContext}. */
+ @Nonnull private
+ Function<ProfileRequestContext, InlineEnrolmentContext> inlineEnrolementContextCreationStrategy;
+
+ /** Constructor.*/
+ public InlineEnrolmentRedirectFunction() {
+ inlineEnrolementContextCreationStrategy = new ChildContextLookup<>(InlineEnrolmentContext.class, false);
+ }
+
+
+ /**
+ * Set the strategy used to locate the {@link InlineEnrolmentContext}.
+ *
+ * @param strategy The strategy to set.
+ */
+ public void setInlineEnrolementContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext, InlineEnrolmentContext> strategy) {
+ checkSetterPreconditions();
+ inlineEnrolementContextCreationStrategy = Constraint.isNotNull(strategy,
+ "InlineEnrolementContextCreationStrategy can not be null");
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nullable final RequestContext springRequestContext,
+ @Nullable final ProfileRequestContext profileRequestContext) {
+
+
+ final InlineEnrolmentContext context = inlineEnrolementContextCreationStrategy.apply(profileRequestContext);
+
+ if (context == null){
+ throw new IllegalArgumentException("InlineEnrolmentContext can not be null");
+ }
+ final URL ssoUrl = context.getSsoUrl();
+
+ if (ssoUrl != null) {
+ final StringBuilder builder = new StringBuilder(ssoUrl.getPath());
+ builder.append("?");
+ builder.append(ssoUrl.getQuery());
+ return builder.toString();
+ }
+ throw new IllegalArgumentException("SSO URL cannot be null");
+ }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateInlineEnrolmentContext.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateInlineEnrolmentContext.java
new file mode 100644
index 0000000..402d04a
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateInlineEnrolmentContext.java
@@ -0,0 +1,125 @@
+/*
+ * 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.net.MalformedURLException;
+import java.net.URL;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.google.common.net.HttpHeaders;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.plugin.authn.webauthn.context.InlineEnrolmentContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Populate the {@link InlineEnrolmentContext} from the HTTP referer header in the
+ * HTTP request iff the registration query parameter contains 'inline'.
+ */
+public class PopulateInlineEnrolmentContext extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PopulateInlineEnrolmentContext.class);
+
+ /** The name of the query parameter that contains a signal that the request was for inline enrolment.*/
+ private static final String REG_QUERY_PARAM = "reg";
+
+ /** Is inline enrolment enabled? Enabled by default. */
+ private Predicate<ProfileRequestContext> enabled;
+
+ /** Strategy used to locate or create the {@link InlineEnrolementContext} to populate. */
+ @Nonnull private
+ Function<ProfileRequestContext, InlineEnrolmentContext> inlineEnrolementContextCreationStrategy;
+
+ /** Constructor.*/
+ public PopulateInlineEnrolmentContext() {
+ inlineEnrolementContextCreationStrategy = new ChildContextLookup<>(InlineEnrolmentContext.class, true);
+ enabled = PredicateSupport.alwaysTrue();
+ }
+
+ /**
+ * Set if inline enrolment should be enabled.
+ *
+ * @param predicate the predicate to check if inline enrolment is enabled.
+ */
+ public void setEnabled(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+ checkSetterPreconditions();
+ enabled = Constraint.isNotNull(enabled, "Inline enrolment predicate can not be null");
+ }
+
+ /**
+ * Set if inline enrolment should be enabled.
+ *
+ * @param flag is inline enrolment enabled.
+ */
+ public void setEnabled(final boolean flag) {
+ checkSetterPreconditions();
+ enabled = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set the strategy used to locate or create the {@link InlineEnrolmentContext}.
+ *
+ * @param strategy The strategy to set.
+ */
+ public void setInlineEnrolementContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext, InlineEnrolmentContext> strategy) {
+ checkSetterPreconditions();
+ inlineEnrolementContextCreationStrategy = Constraint.isNotNull(strategy,
+ "InlineEnrolementContextCreationStrategy can not be null");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (enabled.test(profileRequestContext) == false) {
+ return;
+ }
+
+ final HttpServletRequest request = getHttpServletRequest();
+
+ if (request == null) {
+ log.trace("{} Unable to set inline enrolment URL, no HTTP request set",getLogPrefix());
+ return;
+ }
+
+ final String registrationQueryParam = request.getParameter(REG_QUERY_PARAM);
+
+ if (registrationQueryParam != null && "inline".equals(registrationQueryParam)) {
+ final String referer = request.getHeader(HttpHeaders.REFERER);
+ final InlineEnrolmentContext inlineCtx =
+ inlineEnrolementContextCreationStrategy.apply(profileRequestContext);
+ try {
+ inlineCtx.setSsoUrl(new URL(referer));
+ } catch (final MalformedURLException e) {
+ // We do not error the flow if this happens
+ log.trace("{} Unable to set inline enrolment URL '{}'",getLogPrefix(), referer);
+ return;
+ }
+ log.debug("{} Registration is inline '{}'",getLogPrefix(), referer);
+ }
+ }
+}
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 f50e985..6fcf0d9 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
@@ -37,10 +37,15 @@
<!-- Flow beans -->
+ <bean id="PopulateInlineEnrolmentContext" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PopulateInlineEnrolmentContext"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
+ p:enabled="%{idp.authnwebauthn.registration.allowInline:true}"/>
+
<!-- Initial Username input collection -->
<bean id="PopulateInitialWebAuthnRegistrationContext" scope="prototype"
- class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PopulateWebAuthnRegistrationContext"
+ class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PopulateWebAuthnRegistrationContext"
p:usernameRequired="false">
</bean>
@@ -169,7 +174,9 @@
p:credentialRepository-ref="shibboleth.authn.webauthn.DefaultCredentialRepository"
p:populateAuditContextAction="#{%{idp.authn.webauthn.registration.audit.enabled:false} ? getObject('RegistrationOperationPopulateAuditContext') : null}"
p:writeAuditLogAction="#{%{idp.authn.webauthn.registration.audit.enabled:false} ? getObject('WriteAdminAuditLog') : null}"
- p:auditContextCreationStrategy-ref="AdminAuditContextLookup" />
+ p:auditContextCreationStrategy-ref="AdminAuditContextLookup" />
+
+ <bean id="InlineEnrolmentRedirectFunction" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.InlineEnrolmentRedirectFunction"/>
<!-- Default functions to produce messages for the registration view. -->
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 6fc4904..2b69ddf 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
@@ -6,6 +6,7 @@
<!-- Start action. -->
<action-state id="InitializeProfileRequestContext">
<evaluate expression="InitializeProfileRequestContext" />
+ <evaluate expression="PopulateInlineEnrolmentContext" />
<evaluate expression="FlowStartPopulateAuditContext" />
<evaluate expression="PopulateClientStorageLoadContext" />
<evaluate expression="'proceed'" />
@@ -109,6 +110,7 @@
<evaluate expression="environment" result="viewScope.environment" />
<evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
<evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="viewScope.webauthnRegContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.InlineEnrolmentContext))" result="viewScope.webauthnInlineEnrolmentContext" />
<evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('WebAuthnCSPDigester')" result="requestScope.cspDigester" />
<evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('WebAuthnCSPNonce')" result="requestScope.cspNonce" />
<evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
@@ -120,6 +122,7 @@
</on-render>
<transition on="finish" to="RegistrationComplete" />
+ <transition on="resume" to="RegistrationCompleteResumeFlow" />
<transition on="addKey" to="AddKey" />
<transition on="deleteKey" to="DeleteKey" />
<on-exit>
@@ -162,7 +165,8 @@
<input name="calledAsSubflow" value="true" />
<transition on="proceed" to="GeneratePublicKeyCredentialCreationOptions"/>
</subflow-state>
-
+
+ <end-state id="RegistrationCompleteResumeFlow" view="externalRedirect:serverRelative:#{InlineEnrolmentRedirectFunction.apply(flowRequestContext, opensamlProfileRequestContext)}"/>
<end-state id="RegistrationComplete" view="webauthn/webauthn-end">
<on-entry>
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 b1c57ba..b1f36eb 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
@@ -82,6 +82,9 @@ idp.authn.webauthn.supportedPrincipals = \
# State the preference of the IdP during registration to receive an authenticator attestation. One-of 'none', 'indirect', 'direct', or 'enterprise'.
#idp.authn.webauthn.registration.attestationConveyancePreference = none
+# Allow inline self-enrolment
+#idp.authnwebauthn.registration.allowInline = true
+
# Basic transformations that should be applied to the username that is initially collected in the registration flow
#idp.authn.webauthn.registration.username.uppercase = false
#idp.authn.webauthn.registration.username.lowercase = false
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 9f07636..ae45412 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
@@ -32,6 +32,7 @@ idp.webauthn.register.credential.remove = Remove
idp.webauthn.register.registered.noKeys = You have no registered keys
idp.webauthn.register.addKey = Add new security key
idp.webauthn.register.finish = Finish
+idp.webauthn.register.finish.resume = Resume
idp.webauthn.register.submit = Submit registration
idp.webauthn.register.username.explain = Please enter your username below.
idp.webauthn.register.username.proceed = Next
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
index 9fc3516..7082884 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
@@ -19,6 +19,7 @@
## custom - arbitrary object injected by deployer
##
#set ($debug = $environment.getProperty("idp.authn.webauthn.ui.debug", "false"))
+#set ($inlineReg = $environment.getProperty("idp.authnwebauthn.registration.allowInline", "true"))
#set ($rpContext = $profileRequestContext.getSubcontext('net.shibboleth.profile.context.RelyingPartyContext'))
## Add CSP directives
@@ -148,7 +149,9 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
#springMessageText("idp.webauthn.authn.unsupported", "Your browser is not WebAuthn compatible")
</div>
<ul>
- <li><a href="#springMessageText('idp.webauthn.enrollment.url', '/idp/profile/admin/webauthn-registration')">#springMessageText("idp.webauthn.enrollment", "Register new credential")</a></li>
+ #if ($inlineReg)
+ <li><a href="#springMessageText('idp.webauthn.enrollment.url', '/idp/profile/admin/webauthn-registration')?reg=inline">#springMessageText("idp.webauthn.enrollment", "Register new credential")</a></li>
+ #end
<li><a href="#springMessageText('idp.url.helpdesk', '#')">#springMessageText("idp.login.needHelp", "Need Help?")</a></li>
</ul>
</section>
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 4d25b8b..3c7c8ea 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,6 +7,7 @@
## flowExecutionKey - the SWF execution key (this is built into the flowExecutionUrl)
## profileRequestContext - root of context tree
## webauthnRegContext = WebAuthn registration context
+## webauthnInlineEnrolmentContext - the inline enrolment context
## encoder - HTMLEncoder class
## webAuthnEncoder - WebAuthnEncoder class
## request - HttpServletRequest
@@ -167,7 +168,11 @@ $response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 's
<button id="registerButton">#springMessageText("idp.webauthn.register.addKey", "Add new security key")</button>
<form id="finish_button_form" action="$flowExecutionUrl" method="post" class="inline">
#parse("csrf/csrf.vm")
- <button id="finish_button" type="submit" name="_eventId_finish">#springMessageText("idp.webauthn.register.finish", "Finish")</button>
+ #if ($webauthnInlineEnrolmentContext.ssoUrl)
+ <button id="finish_button" type="submit" name="_eventId_resume">#springMessageText("idp.webauthn.register.finish.resume", "Resume")</button>
+ #else
+ <button id="finish_button" type="submit" name="_eventId_finish">#springMessageText("idp.webauthn.register.finish", "Finish")</button>
+ #end
</form>
</div>
</div>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list