[java-identity-provider] 02/02: IDP-2365 - Support multiple definitions of a given login flow

Scott Cantor cantor.2 at osu.edu
Fri Mar 21 17:17:31 UTC 2025


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=628da5f00b79390fd68e55c6218054238d4be0f2

commit 628da5f00b79390fd68e55c6218054238d4be0f2
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Mar 21 13:16:26 2025 -0400

    IDP-2365 - Support multiple definitions of a given login flow
    
    https://shibboleth.atlassian.net/browse/IDP-2365
    
    Also some cleanup in some method calls inside flows.
---
 .../idp/authn/AuthenticationFlowDescriptor.java    | 53 +++++++++++++++++++++-
 .../context/MultiFactorAuthenticationContext.java  | 37 ++++++++++++++-
 .../authn/AuthenticationFlowDescriptorTest.java    |  2 +
 .../PopulateMultiFactorAuthenticationContext.java  |  2 +-
 .../idp/authn/impl/SelectAuthenticationFlow.java   |  6 +--
 .../impl/TransitionMultiFactorAuthentication.java  | 26 +++++------
 .../DefaultAuthenticationResultSerializerTest.java |  2 +
 ...pulateMultiFactorAuthenticationContextTest.java |  4 ++
 .../authn/impl/SelectAuthenticationFlowTest.java   | 26 ++++++-----
 .../testing/BaseAuthenticationContextTest.java     |  3 ++
 .../net/shibboleth/idp/conf/authn-system.xml       | 33 +++++++++-----
 .../net/shibboleth/idp/flows/authn/authn-beans.xml |  2 +-
 .../net/shibboleth/idp/flows/authn/authn-flow.xml  |  4 +-
 .../shibboleth/idp/flows/authn/mfa-authn-flow.xml  |  4 +-
 .../impl/StorageBackedSessionManagerTest.java      |  2 +
 .../UpdateSessionWithAuthenticationResultTest.java |  1 +
 16 files changed, 157 insertions(+), 50 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
index 90576c3fe..dc496ea3e 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
@@ -43,6 +43,7 @@ import net.shibboleth.idp.authn.principal.PrincipalService;
 import net.shibboleth.idp.authn.principal.PrincipalServiceManager;
 import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
 import net.shibboleth.idp.profile.FlowDescriptor;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -72,6 +73,9 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     /** Additional allowance for storage of result records to avoid race conditions during use. */
     @Nonnull public static final Duration STORAGE_EXPIRATION_OFFSET;
 
+    /** Optional flow ID that may differ from component ID. */
+    @NonnullAfterInit private String flowId;
+    
     /** Spring auto-wiring order. */
     private int order;
     
@@ -155,6 +159,38 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
         stringBasedPrincipals = CollectionSupport.emptySet();
     }
     
+    
+    /**
+     * Gets the webflow ID for this descriptor (inclusive of the authn/ prefix).
+     * 
+     * @return webflow ID
+     * 
+     * @since 5.2.0
+     */
+    @NonnullAfterInit public String getFlowId() {
+        return flowId;
+    }
+    
+    /**
+     * Sets the flow ID for this descriptor.
+     * 
+     * <p>This defaults to the component ID, but can be overridden to allow multiple
+     * components to be defined for a given webflow.</p>
+     * 
+     * @param id
+     * 
+     * @since 5.2.0
+     */
+    public void setFlowId(@Nullable final String id) {
+        checkSetterPreconditions();
+        
+        flowId = StringSupport.trimOrNull(id);
+        if (id != null) {
+            Constraint.isTrue(flowId.startsWith("authn/"), "Flow ID must begin with authn/ prefix.");
+        }
+    }
+    
+    
     /** {@inheritDoc} */
     public int getOrder() {
         return order;
@@ -629,6 +665,14 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
             throw new ComponentInitializationException("AuthenticationResult serializer cannot be null");
         }
         
+        if (flowId == null) {
+            // Backfill flow ID with component ID.
+            flowId = ensureId();
+            if (!flowId.startsWith("authn/")) {
+                throw new ComponentInitializationException("Defaulted flow ID must begin with authn/ prefix.");
+            }
+        }
+        
         if (!stringBasedPrincipals.isEmpty()) {
             if (principalServiceManager == null) {
                 throw new ComponentInitializationException("PrincipalServiceManager cannot be null");
@@ -754,9 +798,14 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
 
     /** {@inheritDoc} */
     @Override public String toString() {
-        return MoreObjects.toStringHelper(this).add("flowId", getId()).add("supportsPassive", supportsPassive)
+        return MoreObjects.toStringHelper(this)
+                .add("id", getId())
+                .add("flowId", getFlowId())
+                .add("supportsPassive", supportsPassive)
                 .add("supportsForcedAuthentication", supportsForced)
-                .add("lifetime", lifetime).add("inactivityTimeout", inactivityTimeout).toString();
+                .add("lifetime", lifetime)
+                .add("inactivityTimeout", inactivityTimeout)
+                .toString();
     }
     
     /**
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java
index 16479a5f5..d6a3274e9 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java
@@ -52,9 +52,12 @@ public final class MultiFactorAuthenticationContext extends BaseContext {
     /** Login flow descriptor for the MFA flow. */
     @Nullable private AuthenticationFlowDescriptor mfaFlowDescriptor;
     
-    /** The next flow due to execute (or the currently executing flow during subflow execution). */
+    /** The next logical flow due to execute (or the currently executing flow during subflow execution). */
     @Nullable @NotEmpty private String nextFlowId;
 
+    /** The next webflow due to execute (or the currently executing web flow during subflow execution). */
+    @Nullable @NotEmpty private String nextWebFlowId;
+
     /** A SWF event to signal as the completion of the MFA flow. */
     @Nullable @NotEmpty private String event;
 
@@ -139,16 +142,46 @@ public final class MultiFactorAuthenticationContext extends BaseContext {
     /**
      * Set the next flow due to execute.
      * 
+     * <p>This also calls {@link #setNextWebFlowId(String)} automatically.</p>
+     * 
      * @param id flow ID
      * 
      * @return this context
      */
     @Nonnull public MultiFactorAuthenticationContext setNextFlowId(@Nullable @NotEmpty final String id) {
         nextFlowId = StringSupport.trimOrNull(id);
+        return setNextWebFlowId(nextFlowId);
+    }
+
+    /**
+     * Get the next webflow due to execute (or that is currently executing).
+     * 
+     * <p>This is distinct from the next "flow" in the case of login flows, to allow for the
+     * indirection between logical flow as a component designation and physical webflow implementation.</p>
+     * 
+     * @return  the ID of the next webflow to execute
+     * 
+     * @since 5.2.0
+     */
+    @Nullable @NotEmpty public String getNextWebFlowId() {
+        return nextWebFlowId;
+    }
+    
+    /**
+     * Set the next webflow due to execute.
+     * 
+     * @param id webflow ID
+     * 
+     * @return this context
+     * 
+     * @since 5.2.0
+     */
+    @Nonnull public MultiFactorAuthenticationContext setNextWebFlowId(@Nullable @NotEmpty final String id) {
+        nextWebFlowId = StringSupport.trimOrNull(id);
         
         return this;
     }
-    
+
     /**
      * Get an event that should be signaled as the result of the MFA flow.
      * 
diff --git a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java
index 8bf70c5d0..77acc03a0 100644
--- a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java
+++ b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java
@@ -32,12 +32,14 @@ public class AuthenticationFlowDescriptorTest {
     @BeforeMethod public void setUp() {
         descriptor = new AuthenticationFlowDescriptor();
         descriptor.setId("test");
+        descriptor.setFlowId("authn/test");
     }
 
     
     /** Tests that everything is properly initialized during object construction. */
     @Test public void testInstantation() {
         Assert.assertEquals(descriptor.getId(), "test");
+        Assert.assertEquals(descriptor.getFlowId(), "authn/test");
         Assert.assertFalse(descriptor.isForcedAuthenticationSupported());
         Assert.assertFalse(descriptor.isPassiveAuthenticationSupported());
     }
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
index f95336fb5..1990dc7dc 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
@@ -171,7 +171,7 @@ public class PopulateMultiFactorAuthenticationContext extends AbstractAuthentica
      * the {@link AuthenticationResultPrincipal} collection of an active {@link AuthenticationResult}
      * of the currently executing flow.
      */
-    private class DefaultResultLookupStrategy
+    private final class DefaultResultLookupStrategy
             implements Function<ProfileRequestContext,Collection<AuthenticationResult>> {
 
         /** {@inheritDoc} */
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
index 9767098a2..7037e0953 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
@@ -364,10 +364,10 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
             @Nonnull final AuthenticationContext authenticationContext,
             @Nonnull final AuthenticationFlowDescriptor descriptor) {
 
-        final String id = descriptor.ensureId();
-        log.debug("{} Selecting inactive authentication flow {}", getLogPrefix(), id);
+        log.debug("{} Selecting inactive authentication flow {} using webflow {}", getLogPrefix(),
+                descriptor.getId(), descriptor.getFlowId());
         authenticationContext.setAttemptedFlow(descriptor);
-        ActionSupport.buildEvent(profileRequestContext, id);
+        ActionSupport.buildEvent(profileRequestContext, descriptor.getFlowId());
     }    
     
     /**
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
index 3cd8840fb..e3fc8e402 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
@@ -63,14 +63,15 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * {@link MultiFactorAuthenticationContext#getEvent()} (if set), or the current WebFlow event.</p>
  * 
  * <p>If a flow is returned, it is populated into the {@link MultiFactorAuthenticationContext}.
- * The flow is checked for the "authn/" prefix, and a login flow is checked against the
- * active result map to determine if it can be reused, in which case the action recurses itself.
- * Otherwise {@link EventIds#PROCEED_EVENT_ID}is signaled to run that flow.</p>
+ * The flow is checked for a matching {@link AuthenticationFlowDescriptor}, and any login flow
+ * is checked against the active result map to determine if it can be reused, in which case the
+ * action recurses itself. Otherwise {@link EventIds#PROCEED_EVENT_ID}is signaled to run that
+ * flow, whether a login flow or not.</p>
  * 
- * <p>By default, login flow transitions are validated against the request's requirements
+ * <p>By default, login flow transitions are also validated against the request's requirements
  * in terms of passive, forced re-authn, and non-browser compatibility.</p>
  * 
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class).getSubcontext(
+ * @pre <pre>ProfileRequestContext.ensureSubcontext(AuthenticationContext.class).getSubcontext(
  *      MultiFactorAuthenticationContext.class) != null</pre>
  * @post See above.
  * @event {@link EventIds#PROCEED_EVENT_ID}
@@ -262,18 +263,12 @@ public class TransitionMultiFactorAuthentication extends AbstractAuthenticationA
         assert isPreExecuteCalled();
 
         // Non-authentication flows can just be executed (via a "proceed" event).
+        // We identify them by the absence of a matching AuthenticationFlowDescriptor.
         final String flowId = Constraint.isNotNull(mfaContext.getNextFlowId(), "No previous flow");
-        if (!flowId.startsWith("authn/")) {
-            ActionSupport.buildProceedEvent(profileRequestContext);
-            return;
-        }
-
         final AuthenticationFlowDescriptor flow = authenticationContext.getAvailableFlows().get(flowId);
         if (flow == null) {
-            log.error("{} Targeted login flow '{}' is not configured, check available flow descriptors",
-                    getLogPrefix(), flowId);
-            ActionSupport.buildEvent(profileRequestContext, authenticationContext.isPassive() ?
-                    AuthnEventIds.NO_PASSIVE : AuthnEventIds.NO_POTENTIAL_FLOW);
+            log.debug("{} Calling non-login subflow: {}", getLogPrefix(), flowId);
+            ActionSupport.buildProceedEvent(profileRequestContext);
             return;
         }
         
@@ -324,6 +319,9 @@ public class TransitionMultiFactorAuthentication extends AbstractAuthenticationA
         
         // Set for compatibility with more standard runs of a login flow at the top level.
         authenticationContext.setAttemptedFlow(flow);
+        // For login flows, we need to override the webflow ID in the MFA context with the physical flow ID to run.
+        mfaContext.setNextWebFlowId(flow.getFlowId());
+        
         ActionSupport.buildProceedEvent(profileRequestContext);
     }
 // Checkstyle: CyclomaticComplexity ON
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
index 0ff45e119..e53036c6e 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
@@ -177,6 +177,7 @@ public class DefaultAuthenticationResultSerializerTest {
         serializer = new DefaultAuthenticationResultSerializer(manager, generic);
         flowDescriptor = new AuthenticationFlowDescriptor();
         flowDescriptor.setId("test");
+        flowDescriptor.setFlowId("authn/test");
         assert serializer != null;
         flowDescriptor.setResultSerializer(serializer);
         flowDescriptor.setReuseCondition(PredicateSupport.alwaysTrue());
@@ -498,6 +499,7 @@ public class DefaultAuthenticationResultSerializerTest {
         
         final AuthenticationFlowDescriptor nestedDescriptor = new AuthenticationFlowDescriptor();
         nestedDescriptor.setId("nested");
+        nestedDescriptor.setFlowId("authn/nested");
         assert serializer != null;
         nestedDescriptor.setResultSerializer(serializer);
         nestedDescriptor.initialize();
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
index fc7cb1863..4de3e965e 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
@@ -105,6 +105,7 @@ public class PopulateMultiFactorAuthenticationContextTest {
         
         AuthenticationFlowDescriptor desc = new AuthenticationFlowDescriptor();
         desc.setId("foo");
+        desc.setFlowId("authn/foo");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
         desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
@@ -112,6 +113,7 @@ public class PopulateMultiFactorAuthenticationContextTest {
 
         desc = new AuthenticationFlowDescriptor();
         desc.setId("bar");
+        desc.setFlowId("authn/bar");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
         desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
@@ -119,6 +121,7 @@ public class PopulateMultiFactorAuthenticationContextTest {
 
         desc = new AuthenticationFlowDescriptor();
         desc.setId("bav");
+        desc.setFlowId("authn/bav");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
         desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
@@ -126,6 +129,7 @@ public class PopulateMultiFactorAuthenticationContextTest {
 
         desc = new AuthenticationFlowDescriptor();
         desc.setId("bag");
+        desc.setFlowId("authn/bag");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
         desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java
index 8555c855b..bc050f9a5 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java
@@ -54,14 +54,14 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
         assert authCtx != null;
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test1");
+        ActionTestingSupport.assertEvent(event, "authn/test1");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         final AuthenticationFlowDescriptor attemptedFlow = authCtx.getAttemptedFlow();
         assert attemptedFlow != null && event != null;
 
-        Assert.assertEquals(attemptedFlow, authCtx.getPotentialFlows().get(event.getId()));
         Assert.assertEquals(attemptedFlow.getId(), "test1");
+        Assert.assertEquals(attemptedFlow, authCtx.getPotentialFlows().get("test1"));
     }
 
     @Test public void testNoRequestNoneActivePassive() {
@@ -70,13 +70,13 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         authCtx.setIsPassive(true);
         
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test2");
+        ActionTestingSupport.assertEvent(event, "authn/test2");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         final AuthenticationFlowDescriptor attemptedFlow = authCtx.getAttemptedFlow();
         assert attemptedFlow != null && event != null;
-        Assert.assertEquals(attemptedFlow, authCtx.getPotentialFlows().get(event.getId()));
         Assert.assertEquals(attemptedFlow.getId(), "test2");
+        Assert.assertEquals(attemptedFlow, authCtx.getPotentialFlows().get("test2"));
     }
 
     @Test public void testNoRequestNoneActiveIntermediate() {
@@ -85,13 +85,13 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         authCtx.getIntermediateFlows().put("test1", authCtx.getPotentialFlows().get("test1"));
         
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test2");
+        ActionTestingSupport.assertEvent(event, "authn/test2");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         final AuthenticationFlowDescriptor attemptedFlow = authCtx.getAttemptedFlow();
         assert attemptedFlow != null && event != null;
-        Assert.assertEquals(attemptedFlow, authCtx.getPotentialFlows().get(event.getId()));
         Assert.assertEquals(attemptedFlow.getId(), "test2");
+        Assert.assertEquals(attemptedFlow, authCtx.getPotentialFlows().get("test2"));
     }
     
     @Test public void testNoRequestActive() {
@@ -117,7 +117,9 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         assert event != null;
         
         Assert.assertNull(authCtx.getAuthenticationResult());
-        Assert.assertEquals(authCtx.getAttemptedFlow(), authCtx.getPotentialFlows().get(event.getId()));
+        // Skip the authn/ prefix in the event to find the flow ID to check for.
+        Assert.assertEquals(authCtx.getAttemptedFlow(),
+                authCtx.getPotentialFlows().get(event.getId().substring(event.getId().indexOf('/') + 1)));
     }
 
     @Test public void testRequestNoMatch() {
@@ -142,7 +144,7 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         authCtx.addSubcontext(ppc, true);
         
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test3");
+        ActionTestingSupport.assertEvent(event, "authn/test3");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         final AuthenticationFlowDescriptor attemptedFlow = authCtx.getAttemptedFlow();
@@ -161,7 +163,7 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(principals);
         
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test3");
+        ActionTestingSupport.assertEvent(event, "authn/test3");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         final AuthenticationFlowDescriptor attemptedFlow = authCtx.getAttemptedFlow();
@@ -223,7 +225,7 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(principals);
         
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test3");
+        ActionTestingSupport.assertEvent(event, "authn/test3");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         final AuthenticationFlowDescriptor attemptedFlow = authCtx.getAttemptedFlow();
@@ -247,7 +249,7 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(principals);
         
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test3");
+        ActionTestingSupport.assertEvent(event, "authn/test3");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         final AuthenticationFlowDescriptor flow = authCtx.getAttemptedFlow();
@@ -273,7 +275,7 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(CollectionSupport.singletonList(principals.get(0)));
         
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "test3");
+        ActionTestingSupport.assertEvent(event, "authn/test3");
         
         Assert.assertNull(authCtx.getAuthenticationResult());
         Assert.assertEquals(authCtx.getAttemptedFlow(), authCtx.getPotentialFlows().get("test3"));
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/testing/BaseAuthenticationContextTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/testing/BaseAuthenticationContextTest.java
index 6e5d99313..f04a9fb65 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/testing/BaseAuthenticationContextTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/testing/BaseAuthenticationContextTest.java
@@ -49,9 +49,12 @@ public class BaseAuthenticationContextTest extends OpenSAMLInitBaseTestCase {
         authenticationFlows = List.of(new AuthenticationFlowDescriptor(),
                 new AuthenticationFlowDescriptor(), new AuthenticationFlowDescriptor());
         authenticationFlows.get(0).setId("test1");
+        authenticationFlows.get(0).setFlowId("authn/test1");
         authenticationFlows.get(1).setId("test2");
+        authenticationFlows.get(1).setFlowId("authn/test2");
         authenticationFlows.get(1).setPassiveAuthenticationSupported(true);
         authenticationFlows.get(2).setId("test3");
+        authenticationFlows.get(2).setFlowId("authn/test3");
     }
 
     protected void setUp() throws ComponentInitializationException {        
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
index ddfa70a4a..722e0f2c5 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
@@ -49,7 +49,8 @@
 
     <!-- Defaulted flows. -->
     
-    <bean p:id="authn/Password" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/Password" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/Password"
             p:order="%{idp.authn.Password.order:1000}"
             p:nonBrowserSupported="%{idp.authn.Password.nonBrowserSupported:true}"
             p:passiveAuthenticationSupported="%{idp.authn.Password.passiveAuthenticationSupported:true}"
@@ -70,7 +71,8 @@
         </property>
     </bean>
     
-    <bean p:id="authn/IPAddress" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/IPAddress" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/IPAddress"
             p:order="%{idp.authn.IPAddress.order:1000}"
             p:nonBrowserSupported="%{idp.authn.IPAddress.nonBrowserSupported:true}"
             p:passiveAuthenticationSupported="%{idp.authn.IPAddress.passiveAuthenticationSupported:true}"
@@ -91,7 +93,8 @@
         </property>
     </bean>
 
-    <bean p:id="authn/Function" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/Function" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/Function"
             p:order="%{idp.authn.Function.order:1000}"
             p:nonBrowserSupported="%{idp.authn.Function.nonBrowserSupported:true}"
             p:passiveAuthenticationSupported="%{idp.authn.Function.passiveAuthenticationSupported:true}"
@@ -112,7 +115,8 @@
         </property>
     </bean>
 
-    <bean p:id="authn/External" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/External" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/External"
             p:order="%{idp.authn.External.order:1000}"
             p:nonBrowserSupported="%{idp.authn.External.nonBrowserSupported:false}"
             p:passiveAuthenticationSupported="%{idp.authn.External.passiveAuthenticationSupported:false}"
@@ -133,7 +137,8 @@
         </property>
     </bean>
         
-    <bean p:id="authn/RemoteUser" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/RemoteUser" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/RemoteUser"
             p:order="%{idp.authn.RemoteUser.order:1000}"
             p:nonBrowserSupported="%{idp.authn.RemoteUser.nonBrowserSupported:false}"
             p:passiveAuthenticationSupported="%{idp.authn.RemoteUser.passiveAuthenticationSupported:false}"
@@ -154,7 +159,8 @@
         </property>
     </bean>
     
-    <bean p:id="authn/RemoteUserInternal" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/RemoteUserInternal" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/RemoteUserInternal"
             p:order="%{idp.authn.RemoteUserInternal.order:1000}"
             p:nonBrowserSupported="%{idp.authn.RemoteUserInternal.nonBrowserSupported:true}"
             p:passiveAuthenticationSupported="%{idp.authn.RemoteUserInternal.passiveAuthenticationSupported:false}"
@@ -175,7 +181,8 @@
         </property>
     </bean>
 
-    <bean p:id="authn/SAML" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/SAML" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/SAML"
             p:order="%{idp.authn.SAML.order:1000}"
             p:nonBrowserSupported="%{idp.authn.SAML.nonBrowserSupported:false}"
             p:passiveAuthenticationSupported="%{idp.authn.SAML.passiveAuthenticationSupported:true}"
@@ -196,7 +203,8 @@
         </property>
     </bean>
             
-    <bean p:id="authn/SPNEGO" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/SPNEGO" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/SPNEGO"
             p:order="%{idp.authn.SPNEGO.order:1000}"
             p:nonBrowserSupported="%{idp.authn.SPNEGO.nonBrowserSupported:false}"
             p:passiveAuthenticationSupported="%{idp.authn.SPNEGO.passiveAuthenticationSupported:false}"
@@ -217,7 +225,8 @@
         </property>
     </bean>
 
-    <bean p:id="authn/X509" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/X509" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/X509"
             p:order="%{idp.authn.X509.order:1000}"
             p:nonBrowserSupported="%{idp.authn.X509.nonBrowserSupported:false}"
             p:passiveAuthenticationSupported="%{idp.authn.X509.passiveAuthenticationSupported:false}"
@@ -238,7 +247,8 @@
         </property>
     </bean>
 
-    <bean p:id="authn/X509Internal" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/X509Internal" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/X509Internal"
             p:order="%{idp.authn.X509Internal.order:1000}"
             p:nonBrowserSupported="%{idp.authn.X509Internal.nonBrowserSupported:false}"
             p:passiveAuthenticationSupported="%{idp.authn.X509Internal.passiveAuthenticationSupported:false}"
@@ -259,7 +269,8 @@
         </property>
     </bean>
 
-    <bean p:id="authn/MFA" parent="shibboleth.AuthenticationFlow"
+    <bean id="authn/MFA" parent="shibboleth.AuthenticationFlow"
+            p:flowId="authn/MFA"
             p:order="%{idp.authn.MFA.order:1000}"
             p:nonBrowserSupported="%{idp.authn.MFA.nonBrowserSupported:true}"
             p:passiveAuthenticationSupported="%{idp.authn.MFA.passiveAuthenticationSupported:true}"
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-beans.xml
index 824d6320e..1e0f5a0d2 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-beans.xml
@@ -15,7 +15,7 @@
     <bean id="PotentialFlowsLookup" parent="shibboleth.ContextFunctions.Expression"
         p:customObject-ref="shibboleth.AuthenticationFlowDescriptorManager"
         c:outputType="#{T(java.util.Collection)}"
-        c:expression="#input.getSubcontext(T(net.shibboleth.idp.profile.context.SpringRequestContext)).getRequestContext().getFlowScope().get('potentialFlows') ?:
+        c:expression="#input.ensureSubcontext(T(net.shibboleth.idp.profile.context.SpringRequestContext)).getRequestContext().getFlowScope().get('potentialFlows') ?:
             #custom.getComponents().?[id matches 'authn/(' + '%{idp.authn.flows:Password}'.trim() + ')']" />
     
     <bean id="PopulateAuthenticationContext"
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-flow.xml
index 3751a4855..bb2eda6f0 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-flow.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/authn-flow.xml
@@ -35,7 +35,7 @@
    
     <!-- Check for an existing session, and do additional steps in that case. -->
     <decision-state id="TestForSession">
-        <if test="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.session.context.SessionContext)) != null and opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.session.context.SessionContext)).getIdPSession() != null"
+        <if test="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.session.context.SessionContext)) != null and opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.idp.session.context.SessionContext)).getIdPSession() != null"
             then="SessionExists"
             else="FilterFlows" />
     </decision-state>
@@ -86,7 +86,7 @@
 
     <!-- Checks if authentication flow already completed c14n. -->
     <decision-state id="CheckSubjectCanonicalization">
-        <if test="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.SubjectCanonicalizationContext)).getPrincipalName() == null"
+        <if test="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.idp.authn.context.SubjectCanonicalizationContext)).getPrincipalName() == null"
             then="PopulateSubjectCanonicalizationContext"
             else="DetectIdentitySwitch" />
     </decision-state>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/mfa-authn-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/mfa-authn-flow.xml
index 895a22d23..9298c033d 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/mfa-authn-flow.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/mfa-authn-flow.xml
@@ -21,7 +21,7 @@
         <transition on="proceed" to="TransitionMultiFactorAuthentication" />
         
         <on-exit>
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).getSubcontext(T(net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext))" result="flowScope.mfaContext" />
+            <evaluate expression="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).getSubcontext(T(net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext))" result="flowScope.mfaContext" />
         </on-exit>
     </action-state>
     
@@ -36,7 +36,7 @@
         <if test="mfaContext.getNextFlowId() != null" then="CallSubflow" else="FinalizeMultiFactorAuthentication" />
     </decision-state>
     
-    <subflow-state id="CallSubflow" subflow="#{mfaContext.getNextFlowId()}">
+    <subflow-state id="CallSubflow" subflow="#{mfaContext.getNextWebFlowId()}">
         <input name="calledAsSubflow" value="true" />
         <transition to="TransitionMultiFactorAuthentication" />
     </subflow-state>
diff --git a/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java b/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java
index 9acb67385..a4a9dc9dd 100644
--- a/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java
+++ b/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java
@@ -85,6 +85,7 @@ public class StorageBackedSessionManagerTest extends SessionManagerBaseTestCase
         
         AuthenticationFlowDescriptor foo = new AuthenticationFlowDescriptor();
         foo.setId("AuthenticationFlow/Foo");
+        foo.setFlowId("authn/Foo");
         foo.setLifetime(Duration.ofMinutes(1));
         foo.setInactivityTimeout(Duration.ofMinutes(1));
         foo.setResultSerializer(resultSerializer);
@@ -92,6 +93,7 @@ public class StorageBackedSessionManagerTest extends SessionManagerBaseTestCase
         
         AuthenticationFlowDescriptor bar = new AuthenticationFlowDescriptor();
         bar.setId("AuthenticationFlow/Bar");
+        bar.setFlowId("authn/Bar");
         bar.setLifetime(Duration.ofMinutes(1));
         bar.setInactivityTimeout(Duration.ofMinutes(1));
         bar.setResultSerializer(resultSerializer);
diff --git a/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/UpdateSessionWithAuthenticationResultTest.java b/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/UpdateSessionWithAuthenticationResultTest.java
index 1820578e5..0bacd845a 100644
--- a/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/UpdateSessionWithAuthenticationResultTest.java
+++ b/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/UpdateSessionWithAuthenticationResultTest.java
@@ -77,6 +77,7 @@ public class UpdateSessionWithAuthenticationResultTest extends SessionManagerBas
 
         flowDescriptor = new AuthenticationFlowDescriptor();
         flowDescriptor.setId("test1");
+        flowDescriptor.setFlowId("authn/test1");
         flowDescriptor.setResultSerializer(resultSerializer);
         flowDescriptor.initialize();
         sessionManager.setAuthenticationFlowDescriptors(CollectionSupport.arrayAsList(flowDescriptor));

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


More information about the commits mailing list