[java-plugin-shibd] branch main updated: Sketch out machinery for token consumer flows.

Scott Cantor cantor.2 at osu.edu
Mon Aug 26 19:37:02 UTC 2024


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

scantor pushed a commit to branch main
in repository java-plugin-shibd.

View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd.git;a=commit;h=1e5fdef1532152644d0b98d88c7dacf271ec8452

The following commit(s) were added to refs/heads/main by this push:
     new 1e5fdef  Sketch out machinery for token consumer flows.
1e5fdef is described below

commit 1e5fdef1532152644d0b98d88c7dacf271ec8452
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Aug 26 15:36:59 2024 -0400

    Sketch out machinery for token consumer flows.
---
 .../META-INF/net.shibboleth.idp/postconfig.xml     |   6 ++
 .../idp/flows/sp/consumer/consumer-flow.xml        |  40 ++++++++
 .../parse-request-map/parse-request-map-beans.xml  |   2 +-
 .../shibboleth/idp/flows/sp/ping/ping-beans.xml    |   2 +-
 .../idp/flows/sp/sealer/sealer-beans.xml           |   2 +-
 .../session-initiator/session-initiator-beans.xml  |   2 +-
 .../idp/flows/sp/storage/storage-beans.xml         |   2 +-
 .../token-consumer-beans.xml}                      |  12 +--
 .../sp/token-consumer/token-consumer-flow.xml      |  34 +++++++
 .../shibboleth/idp/module/conf/sp/sp.properties    |   3 +-
 .../net/shibboleth/sp/conf/agents-system.xml       |   4 +
 .../shibboleth/sp/flows/TokenConsumerFlowText.java |  93 ++++++++++++++++++
 .../main/java/net/shibboleth/sp/Application.java   |   9 ++
 .../profile/BasicTokenConsumerFlowDescriptor.java  |  86 +++++++++++++++++
 .../sp/profile/TokenConsumerFlowDescriptor.java    |  46 +++++++++
 .../net/shibboleth/sp/impl/BasicApplication.java   |  40 +++++++-
 .../sp/profile/impl/SelectTokenConsumerFlow.java   | 104 +++++++++++++++++++++
 .../impl/TokenConsumerFlowDescriptorManager.java   |  41 ++++++++
 18 files changed, 515 insertions(+), 13 deletions(-)

diff --git a/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 4f8975c..ae73bef 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -41,4 +41,10 @@
     <bean id="shibboleth.RemotedHttpServletRequestSupplier" class="net.shibboleth.sp.messaging.impl.RemotedHttpServletRequestSupplier" />
     <bean id="shibboleth.RemotedHttpServletResponseSupplier" class="net.shibboleth.sp.messaging.impl.RemotedlHttpServletResponseSupplier" />
 
+    <!-- Management bean to collect and expose TokenConsumerFlowDescriptors. -->
+    
+    <bean id="shibboleth.TokenConsumerFlowDescriptorManager"
+        class="net.shibboleth.sp.profile.impl.TokenConsumerFlowDescriptorManager"
+        p:components="#{getObject('shibboleth.AvailableTokenConsumerFlows')}" />
+
 </beans>
\ No newline at end of file
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/consumer-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/consumer-flow.xml
new file mode 100644
index 0000000..f84660c
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/consumer-flow.xml
@@ -0,0 +1,40 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+      abstract="true">
+
+    <!-- Rudimentary impediment to direct execution of subflow. -->
+    <input name="calledAsSubflow" type="boolean" required="true" />
+
+    <!-- Defines the possible end-state events, and a global transition rule. -->
+
+    <!-- Successful completion of subflow. -->
+    <end-state id="proceed" />
+
+    <!-- Error events to reflect back from this subflow. -->
+    <end-state id="InvalidEvent" />
+    <end-state id="InvalidProfileContext" />
+    <end-state id="InvalidRelyingPartyContext" />
+    <end-state id="InvalidRelyingPartyConfiguration" />
+    <end-state id="ReselectFlow" />
+    <end-state id="RuntimeException" />
+
+    <action-state id="LogRuntimeException">
+        <on-entry>
+            <evaluate expression="LogRuntimeException.log('net.shibboleth.sp.profile.consumer', flowExecutionException)" />
+        </on-entry>
+        <evaluate expression="'RuntimeException'" />
+        
+        <transition to="RuntimeException" />
+    </action-state>
+
+    <global-transitions>
+        <transition on-exception="java.lang.RuntimeException" to="LogRuntimeException" />
+        <transition on="InvalidProfileContext" to="InvalidProfileContext" />
+        <transition on="InvalidRelyingPartyContext" to="InvalidRelyingPartyContext" />
+        <transition on="InvalidRelyingPartyConfiguration" to="InvalidRelyingPartyConfiguration" />
+        <transition on="ReselectFlow" to="ReselectFlow" />
+        <transition on="RuntimeException" to="RuntimeException" />
+    </global-transitions>
+
+</flow>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/parse-request-map/parse-request-map-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/parse-request-map/parse-request-map-beans.xml
index d817289..361c4b8 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/parse-request-map/parse-request-map-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/parse-request-map/parse-request-map-beans.xml
@@ -8,7 +8,7 @@
     default-init-method="initialize" default-destroy-method="destroy">
 
     <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/parse-request-map" />
-    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.sp:SPAgent}" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.requestmap:SPAgent.ParseRequestMap}" />
 
     <bean id="ParseAgentRequestMap"
         class="net.shibboleth.sp.config.impl.ParseAgentRequestMap" scope="prototype"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/ping/ping-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/ping/ping-beans.xml
index b70be65..1fbca80 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/ping/ping-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/ping/ping-beans.xml
@@ -8,7 +8,7 @@
     default-init-method="initialize" default-destroy-method="destroy">
 
     <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/ping" />
-    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.sp:SPAgent}" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.ping:SPAgent.Ping}" />
 
     <bean id="DoPing" class="net.shibboleth.sp.profile.impl.DoPing" scope="prototype" />
 
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml
index 38f3087..c915cc5 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml
@@ -8,7 +8,7 @@
     default-init-method="initialize" default-destroy-method="destroy">
 
     <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/parse-request-map" />
-    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.sp:SPAgent}" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.sealer:SPAgent.Sealer}" />
 
     <bean id="DoSealerOperation"
         class="net.shibboleth.sp.profile.impl.DoSealerOperation" scope="prototype"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
index 90b2971..8b9e448 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
@@ -8,7 +8,7 @@
     default-init-method="initialize" default-destroy-method="destroy">
 
     <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/session-initiator" />
-    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.sp:SPAgent}" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.initiator:SPAgent.SessionInitiator}" />
 
     <bean id="MapResourceToStateToken" 
         class="net.shibboleth.sp.profile.impl.MapResourceToStateToken" scope="prototype"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml
index e9cec31..df35b1c 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml
@@ -8,7 +8,7 @@
     default-init-method="initialize" default-destroy-method="destroy">
 
     <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/parse-request-map" />
-    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.sp:SPAgent}" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.storage:SPAgent.Storage}" />
 
     <bean id="DoStorageOperation"
         class="net.shibboleth.sp.profile.impl.DoStorageOperation" scope="prototype"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
similarity index 72%
copy from sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
copy to sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
index 90b2971..ddf9bcb 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
@@ -7,11 +7,11 @@
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
     default-init-method="initialize" default-destroy-method="destroy">
 
-    <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/session-initiator" />
-    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.sp:SPAgent}" />
-
-    <bean id="MapResourceToStateToken" 
-        class="net.shibboleth.sp.profile.impl.MapResourceToStateToken" scope="prototype"
-        p:createOutputObjects="true" />
+    <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/token-consumer" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.consumer:SPAgent.TokenConsumer}" />
+    
+    <bean id="SelectTokenConsumerFlow"
+        class="net.shibboleth.sp.profile.impl.SelectTokenConsumerFlow" scope="prototype"
+        p:availableFlows="#{getObject('shibboleth.TokenConsumerFlowDescriptorManager').getComponents()}" />
     
 </beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-flow.xml
new file mode 100644
index 0000000..ca582de
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-flow.xml
@@ -0,0 +1,34 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+    parent="sp/application">
+
+    <action-state id="InitializeProfileRequestContext">
+        <evaluate expression="InitializeProfileRequestContext" />
+        <evaluate expression="'proceed'" />
+        
+        <!-- Branch to parent flow for authentication. -->
+        <transition on="proceed" to="AuthenticationSetup" />
+    </action-state>
+
+    <!-- Resume flow operation after set up by parent. -->
+    <action-state id="DoApplicationOperation">
+        <evaluate expression="SelectTokenConsumerFlow" />
+        
+        <!-- Call a subflow with the same ID as the event. -->
+        <transition on="#{currentEvent.id.startsWith('sp/consumer/')}" to="CallConsumerFlow" />
+    </action-state>
+
+    <subflow-state id="CallConsumerFlow" subflow="sp/consumer/#{TokenConsumerFlowID}">
+        <input name="calledAsSubflow" value="true" />
+        <transition on="proceed" to="EncodeAgentResponse" />
+    </subflow-state>
+    
+    <action-state id="NoPotentialFlow">
+        <evaluate expression="'NoPotentialFlow'" />
+    </action-state>
+    
+    <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml" />
+
+</flow>
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index 73d3fdb..2c488c4 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -15,8 +15,9 @@ sp.service.agents.checkInterval = PT5M
 # Set to DataSealer to use for remoted data encryption.
 #sp.dataSealer = shibboleth.DataSealer
 
-# Default precedence/set of Session Initiator flows to attempt
+# Default precedence/set of Session Initiator and Token Consumer flows to attempt
 #sp.application.sessionInitiators = 
+#sp.application.tokenConsumers =
 
 # General SP cookie properties (maxAge only applies to persistent cookies)
 #sp.cookie.secure = true
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
index 5d1f1ac..d6d746b 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
@@ -35,11 +35,15 @@
         p:relyingPartyConfigurations-ref="shibboleth.RelyingPartyOverrides"
         p:metricName="net.shibboleth.sp.relyingparty.configurations"
         p:sessionInitiators-ref="shibboleth.SessionInitiators"
+        p:tokenConsumers-ref="shibboleth.TokenConsumers"
         p:stateTokenManager-ref="#{'%{sp.stateToken.Manager:shibboleth.StorageStateTokenManager}'.trim()}" />
 
     <bean id="shibboleth.SessionInitiators" parent="shibboleth.CommaDelimStringArray"
         c:_0="%{sp.application.sessionInitiators:}" />
 
+    <bean id="shibboleth.TokenConsumers" parent="shibboleth.CommaDelimStringArray"
+        c:_0="%{sp.application.tokenConsumers:}" />
+
     <bean id="shibboleth.PassthroughStateTokenManager" class="net.shibboleth.sp.impl.PassthroughStateTokenManager" lazy-init="true" />
 
     <bean id="shibboleth.StorageStateTokenManager" class="net.shibboleth.sp.impl.StorageServiceStateTokenManager" lazy-init="true"
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/TokenConsumerFlowText.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/TokenConsumerFlowText.java
new file mode 100644
index 0000000..d387d6c
--- /dev/null
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/TokenConsumerFlowText.java
@@ -0,0 +1,93 @@
+/*
+ * 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.sp.flows;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.profile.impl.ResolveApplication;
+
+/**
+ * Unit test for the SP token-consumer flow (basics only).
+ */
+public class TokenConsumerFlowText extends AbstractSPFlowTest {
+    
+    /** Flow ID. */
+    @Nonnull public static final String FLOW_ID = "sp/token-consumer";
+
+    protected TokenConsumerFlowText() {
+        super(FLOW_ID);
+    }
+    
+    /**
+     * Test flow with no application specified.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testNoApplication() throws IOException {
+        setDefaultAuth();
+        setRequest("POST", new DDF(null));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+    }
+    
+    /**
+     * Test flow with invalid application ID.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testBadApplication() throws IOException {
+        setDefaultAuth();
+
+        final DDF input = new DDF(null).structure();
+        input.addmember(ResolveApplication.APPLICATION_ID).string("foo");
+        setRequest("POST", input);
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+    }
+    
+    /**
+     * Test flow with no consumer subflows installed.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testNoFlows() throws IOException {
+        setDefaultAuth();
+        
+        final DDF input = new DDF(null).structure();
+        input.addmember(ResolveApplication.APPLICATION_ID).string(APPLICATION_ID);
+        setRequest("POST", input);
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
+    }
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
index 1d6aa3c..f0d22a4 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
@@ -78,6 +78,15 @@ public interface Application extends RelyingPartyConfigurationResolver {
      */
     @Nonnull List<String> getSessionInitiators(@Nullable final ProfileRequestContext profileRequestContext);
 
+    /**
+     * Get an ordered list of token consumer flows to attempt for this application.
+     * 
+     * @param profileRequestContext profile request context
+     *  
+     * @return ordered list of unprefixed flow IDs
+     */
+    @Nonnull List<String> getTokenConsumers(@Nullable final ProfileRequestContext profileRequestContext);
+
     /**
      * Get the {@link StateTokenManager} to use for thie application.
      * 
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/BasicTokenConsumerFlowDescriptor.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/BasicTokenConsumerFlowDescriptor.java
new file mode 100644
index 0000000..0c1acb3
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/BasicTokenConsumerFlowDescriptor.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.sp.profile;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+
+/**
+ * Basic implementation of a descriptor for a token consumer flow.
+ */
+public class BasicTokenConsumerFlowDescriptor extends AbstractIdentifiableInitializableComponent
+        implements TokenConsumerFlowDescriptor {
+    
+    /** Whether this flow supports non-browser clients. */
+    @Nonnull private Predicate<ProfileRequestContext> activationCondition;
+    
+    /** Constructor. */
+    public BasicTokenConsumerFlowDescriptor() {
+        activationCondition = PredicateSupport.alwaysFalse();
+    }
+
+    /** {@inheritDoc} */
+    @Nonnull public Predicate<ProfileRequestContext> getActivationCondition() {
+        return activationCondition;
+    }
+    
+    /**
+     * Set activation condition to determine whether this flow supports a request.
+     * 
+     * @param condition condition to use
+     */
+    public void setActivationCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+        activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return getId().hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof BasicTokenConsumerFlowDescriptor) {
+            return getId().equals(((BasicTokenConsumerFlowDescriptor) obj).getId());
+        }
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("flowId", getId())
+                .toString();
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/TokenConsumerFlowDescriptor.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/TokenConsumerFlowDescriptor.java
new file mode 100644
index 0000000..c33adf8
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/TokenConsumerFlowDescriptor.java
@@ -0,0 +1,46 @@
+/*
+ * 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.sp.profile;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.profile.FlowDescriptor;
+
+/**
+ * A descriptor for a token consumer subflow.
+ * 
+ * <p>Token consumers are the portion of the SP responsible for consuming SSO protocol
+ * responses that result in authenticated access to resources. In SAML, this would be the
+ * "assertion consumer service".</p>
+ * 
+ * <p>The primary need for this is to associate each subflow with a condition that can
+ * determine whether a given request is handled by a given subflow. For example, determining
+ * the protocol in use, the parametes included in the request, etc.</p>
+ */
+public interface TokenConsumerFlowDescriptor extends FlowDescriptor {
+
+    /**
+     * Gets the activation condition to run to determine whether this flow can handle the
+     * request.
+     * 
+     * @return a condition governing use of this flow
+     */
+    @Nonnull Predicate<ProfileRequestContext> getActivationCondition();
+    
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
index d3a0e5d..e42dcde 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
@@ -56,7 +56,10 @@ public class BasicApplication extends DefaultRelyingPartyConfigurationResolver i
     
     /** Session initiator list lookup strategy. */
     @Nonnull private Function<ProfileRequestContext,List<String>> sessionInitiatorLookupStrategy;
-    
+
+    /** Token consumer list lookup strategy. */
+    @Nonnull private Function<ProfileRequestContext,List<String>> tokenConsumerLookupStrategy;
+
     /** State token management. */
     @NonnullAfterInit private StateTokenManager stateTokenManager;
     
@@ -77,6 +80,7 @@ public class BasicApplication extends DefaultRelyingPartyConfigurationResolver i
         issuerLookupStrategy = FunctionSupport.constant(null);
         authenticatingAuthorityLookupStrategy = FunctionSupport.constant(null);
         sessionInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+        tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
     }
     
     /** {@inheritDoc} */
@@ -204,7 +208,41 @@ public class BasicApplication extends DefaultRelyingPartyConfigurationResolver i
         sessionInitiatorLookupStrategy = Constraint.isNotNull(strategy,
                 "Session initiators lookup strategy cannot be null");
     }
+
+    
+    /** {@inheritDoc} */
+    @Nonnull public List<String> getTokenConsumers(@Nullable final ProfileRequestContext profileRequestContext) {
+        final List<String> ret = tokenConsumerLookupStrategy.apply(profileRequestContext);
+        return ret != null ? ret : CollectionSupport.emptyList(); 
+    }
+    
+    /**
+     * Sets the ordered collection of unprefixed token consumer flow IDs to use for this application.
+     * 
+     * @param consumers ordered collection of unprefixed token consumer flow IDs
+     */
+    public void setTokenConsumers(@Nullable final Collection<String> consumers) {
+        checkSetterPreconditions();
+        
+        if (consumers != null) {
+            tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(consumers));
+        } else {
+            tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+        }
+    }
     
+    /**
+     * Sets the lookup strategy for the ordered collection of unprefixed token consumer flow IDs
+     * to use for this application.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTokenConsumersLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+        checkSetterPreconditions();
+        
+        tokenConsumerLookupStrategy = Constraint.isNotNull(strategy, "Token consumers lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Nonnull public StateTokenManager getStateTokenManager() {
         checkComponentActive();
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectTokenConsumerFlow.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectTokenConsumerFlow.java
new file mode 100644
index 0000000..4d88c81
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectTokenConsumerFlow.java
@@ -0,0 +1,104 @@
+/*
+ * 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.sp.profile.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.messaging.impl.RemotedHttpServletRequestResponseContext;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.profile.TokenConsumerFlowDescriptor;
+
+/**
+ * Action that checks all enabled token consumer flows and returns the flow ID of the first
+ * enabled flow indicating it can handle the request.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_POTENTIAL_FLOW}
+ * @event selected token consumer flow ID
+ */
+public class SelectTokenConsumerFlow extends AbstractApplicationAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(SelectTokenConsumerFlow.class);
+    
+    /** Map of available consumer flows. */
+    @Nonnull private Map<String,TokenConsumerFlowDescriptor> availableFlows;
+
+    /** Constructor. */
+    public SelectTokenConsumerFlow() {
+        availableFlows = CollectionSupport.emptyMap();
+    }
+    
+    /**
+     * Sets the available token consumer flows to consider.
+     * 
+     * @param flows available flows
+     */
+    public void setAvailableFlows(@Nonnull final Collection<TokenConsumerFlowDescriptor> flows) {
+        checkSetterPreconditions();
+        
+        availableFlows = flows.stream()
+            .collect(CollectionSupport.nonnullCollector(
+                    Collectors.toUnmodifiableMap(TokenConsumerFlowDescriptor::getId, Function.identity()))).get();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        // We do the crazy stuff to accomodate flow activation conditions evaluating the servlet request.
+        try {
+            RemotedHttpServletRequestResponseContext.loadCurrent(ensureAgentRequestContext().getRemotedHttpServletRequest(),
+                    ensureAgentRequestContext().getRemotedHttpServletResponse());
+
+            final List<String> flows = ensureApplication().getTokenConsumers(profileRequestContext);
+            for (final String flowId : flows) {
+                assert flowId != null;
+                final TokenConsumerFlowDescriptor flowDescriptor = availableFlows.get(flowId);
+                if (flowDescriptor != null) {
+                    if (flowDescriptor.getActivationCondition().test(profileRequestContext)) {
+                        log.debug("{} Token consumer flow {} handling request", getLogPrefix(), flowId);
+                        ActionSupport.buildEvent(profileRequestContext, flowId);
+                        return;
+                    } else {
+                        log.warn("{} Token consumer flow {} cannot handle request", getLogPrefix(), flowId);
+                    }
+                } else {
+                    log.warn("{} Token consumer flow {} is not available", getLogPrefix(), flowId);
+                }
+            }
+        } finally {
+            RemotedHttpServletRequestResponseContext.clearCurrent();
+        }
+        
+        log.warn("{} No token consumer flows were able to handle request", getLogPrefix());
+        ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_POTENTIAL_FLOW);
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/TokenConsumerFlowDescriptorManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/TokenConsumerFlowDescriptorManager.java
new file mode 100644
index 0000000..c78231e
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/TokenConsumerFlowDescriptorManager.java
@@ -0,0 +1,41 @@
+/*
+ * 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.sp.profile.impl;
+
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import net.shibboleth.shared.spring.config.IdentifiedComponentManager;
+import net.shibboleth.sp.profile.TokenConsumerFlowDescriptor;
+
+/**
+ * Manager of {@link TokenConsumerFlowDescriptor} objects.
+ */
+public class TokenConsumerFlowDescriptorManager extends IdentifiedComponentManager<TokenConsumerFlowDescriptor> {
+
+    /**
+     * Constructor.
+     *
+     * @param freeObjects  free-standing objects
+     */
+    @Autowired
+    public TokenConsumerFlowDescriptorManager(@Nullable final Collection<TokenConsumerFlowDescriptor> freeObjects) {
+        super(freeObjects);
+    }
+
+}
\ No newline at end of file

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


More information about the commits mailing list