[java-plugin-shibd] branch main updated: WIP on session initiator framework, SAML subflow.

Scott Cantor cantor.2 at osu.edu
Thu Jul 11 19:01:57 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=c990c592d4f6aa5403cc5ca7f5dbaaa7a0d9280f

The following commit(s) were added to refs/heads/main by this push:
     new c990c59  WIP on session initiator framework, SAML subflow.
c990c59 is described below

commit c990c592d4f6aa5403cc5ca7f5dbaaa7a0d9280f
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Jul 11 15:01:54 2024 -0400

    WIP on session initiator framework, SAML subflow.
---
 .../idp/flows/sp/initiator/initiator-flow.xml      |  38 ++++++
 .../idp/flows/sp/initiator/saml2/saml2-beans.xml   |  55 ++++++++
 .../idp/flows/sp/initiator/saml2/saml2-flow.xml    |  35 +++++
 .../net/shibboleth/idp/module/conf/sp/agents.xml   |   3 +-
 .../shibboleth/sp/conf/relying-party-system.xml    |   4 +
 .../profile/config/ECPProfileConfiguration.java    |  21 +++
 sp-saml-impl/pom.xml                               |  23 ++++
 .../config/impl/ECPProfileConfiguration.java       |  21 +++
 .../impl/PrepareOutboundMessageContext.java        |  83 ++++++------
 .../sp/saml/saml2/profile/impl/package-info.java   |  18 +++
 .../impl/PrepareOutboundMessageContextTest.java    |  89 +++++++++++++
 sp-server-api/pom.xml                              |  11 ++
 .../shibboleth/sp/profile/InitiatorConstants.java  |  37 ++++++
 .../AbstractAgentRequestLookupFunction.java        |  86 +++++++++++++
 .../ApplicationMetadataResolverLookupFunction.java |  49 +++++++
 .../context/navigate/messaging/package-info.java   |  18 +++
 .../impl/RemotedHttpServletRequestSupplier.java    |  61 +++++++++
 .../impl/RemotedlHttpServletResponseSupplier.java  |  61 +++++++++
 .../impl/InitializeRelyingPartyContext.java        |   7 +-
 .../impl/SelectRelyingPartyConfiguration.java      | 141 +++++++++++++++++++++
 20 files changed, 814 insertions(+), 47 deletions(-)

diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/initiator-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/initiator-flow.xml
new file mode 100644
index 0000000..1d4e083
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/initiator-flow.xml
@@ -0,0 +1,38 @@
+<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="ReselectFlow" />
+    <end-state id="RuntimeException" />
+
+    <action-state id="LogRuntimeException">
+        <on-entry>
+            <evaluate expression="LogRuntimeException.log('net.shibboleth.sp.profile.initiator', 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="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/initiator/saml2/saml2-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-beans.xml
new file mode 100644
index 0000000..d22705d
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-beans.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           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="PrepareOutboundMessageContext"
+        class="net.shibboleth.sp.saml.saml2.profile.impl.PrepareOutboundMessageContext" scope="prototype" />
+
+    <bean id="WebFlowMessageHandlerAdaptor" abstract="true"
+        class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype" />
+
+    <bean id="SAMLProtocolAndRole" parent="WebFlowMessageHandlerAdaptor" c:executionDirection="OUTBOUND">
+        <constructor-arg name="messageHandler">
+            <bean class="org.opensaml.saml.common.binding.impl.SAMLProtocolAndRoleHandler" scope="prototype"
+                p:protocol-ref="shibboleth.MetadataLookup.Protocol"
+                p:role-ref="shibboleth.MetadataLookup.Role" />
+        </constructor-arg>
+    </bean>
+    
+    <util:constant id="shibboleth.MetadataLookup.Protocol"
+        static-field="org.opensaml.saml.common.xml.SAMLConstants.SAML20P_NS" />
+
+    <util:constant id="shibboleth.MetadataLookup.Role"
+        static-field="org.opensaml.saml.saml2.metadata.IDPSSODescriptor.DEFAULT_ELEMENT_NAME" />
+    
+    <bean id="SAMLMetadataLookup" parent="WebFlowMessageHandlerAdaptor" c:executionDirection="OUTBOUND">
+        <constructor-arg name="messageHandler">
+            <bean class="org.opensaml.saml.common.binding.impl.SAMLMetadataLookupHandler" scope="prototype">
+                <property name="roleDescriptorResolverLookupStrategy">
+                    <bean class="net.shibboleth.sp.profile.context.navigate.messaging.ApplicationMetadataResolverLookupFunction" />
+                </property>
+            </bean>
+        </constructor-arg>
+    </bean>
+
+    <bean id="InitializeRelyingPartyContextFromSAMLPeer"
+        class="net.shibboleth.idp.saml.profile.impl.InitializeRelyingPartyContextFromSAMLPeer" scope="prototype"
+        p:peerEntityContextLookupStrategy-ref="OutboundPeerEntityContextLoookup" />
+        
+    <bean id="OutboundPeerEntityContextLoookup" parent="shibboleth.Functions.Compose"
+        c:g-ref="shibboleth.ChildLookup.SAMLPeerEntityContext"
+        c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+
+    <bean id="SelectRelyingPartyConfiguration"
+        class="net.shibboleth.sp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype" />
+
+    <bean id="SelectProfileConfiguration"
+        class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
+        p:profileId="T(net.shibboleth.saml.saml2.profile.config.BrowserSSOProfileConfiguration).PROFILE_ID" />
+
+</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-flow.xml
new file mode 100644
index 0000000..9c7c89a
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-flow.xml
@@ -0,0 +1,35 @@
+<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/initiator">
+
+    <action-state id="SAML2SessionInitiator">
+        <evaluate expression="PrepareOutboundMessageContext" />
+        <evaluate expression="SAMLProtocolAndRole" />
+        <evaluate expression="SAMLMetadataLookup" />
+        <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
+        <evaluate expression="'proceed'" />
+
+        <transition on="proceed" to="proceed" />
+        <transition on="UnknownAuthority" to="AfterMetadataLookup" />
+    </action-state>
+    
+    <action-state id="AfterMetadataLookup">
+        <evaluate expression="SelectRelyingPartyConfiguration" />
+        <evaluate expression="SelectProfileConfiguration" />
+        
+        <evaluate expression="PopulateBindingAndEndpointContexts" />
+
+        <evaluate expression="PopulateRequestSignatureSigningParameters" />
+        <evaluate expression="PopulateRequestEncryptionParameters" />
+        
+        <evaluate expression="AddAuthnRequest" />
+        <evaluate expression="'proceed'" />
+    
+        <transition on="proceed" to="proceed" />
+    </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/initiator/saml2/saml2-beans.xml" />
+
+</flow>
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/agents.xml b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/agents.xml
index e04cfc9..f194ecc 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/agents.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/agents.xml
@@ -53,7 +53,7 @@
     <bean id="shibboleth.UnverifiedRelyingParty" parent="RelyingParty">
         <property name="profileConfigurations">
             <list>
-            <!-- <bean parent="SAML2.SSO" /> -->
+            <!-- <ref bean="SAML2.SSO" /> -->
             </list>
         </property>
     </bean>
@@ -69,6 +69,7 @@
         <property name="profileConfigurations">
             <list>
                 <ref bean="SAML2.SSO" />
+                <ref bean="SAML2.ECP" />
                 <ref bean="SAML2.Logout" />
             </list>
         </property>
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/relying-party-system.xml b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/relying-party-system.xml
index 4f32b54..37dca5b 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/relying-party-system.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/relying-party-system.xml
@@ -76,6 +76,10 @@
     <util:constant id="SAML2.SSO.FEATURE_FORCEAUTHN"
         static-field="net.shibboleth.saml.saml2.profile.config.BrowserSSOProfileConfiguration.FEATURE_FORCEAUTHN"/>
 
+    <bean id="SAML2.ECP" lazy-init="true"
+        class="net.shibboleth.sp.saml.saml2.profile.config.impl.ECPProfileConfiguration"
+        p:encryptionOptionalPredicate="%{sp.encryption.optional:false}" />
+        
     <bean id="SAML2.Logout" lazy-init="true"
         class="net.shibboleth.sp.saml.saml2.profile.config.impl.SingleLogoutProfileConfiguration"
         p:encryptionOptionalPredicate="%{sp.encryption.optional:false}"
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/ECPProfileConfiguration.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/ECPProfileConfiguration.java
new file mode 100644
index 0000000..6b921b0
--- /dev/null
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/ECPProfileConfiguration.java
@@ -0,0 +1,21 @@
+/*
+ * 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.saml.saml2.profile.config;
+
+/** Configuration support for SP SAML 2.0 ECP. */
+public interface ECPProfileConfiguration
+    extends BrowserSSOProfileConfiguration, net.shibboleth.saml.saml2.profile.config.ECPProfileConfiguration {
+
+}
\ No newline at end of file
diff --git a/sp-saml-impl/pom.xml b/sp-saml-impl/pom.xml
index 9a3a5a4..83b5a12 100644
--- a/sp-saml-impl/pom.xml
+++ b/sp-saml-impl/pom.xml
@@ -28,6 +28,12 @@
         </dependency>
 
         <!-- Provided dependencies -->
+        <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-profile-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
         <dependency>
             <groupId>${shib-profile.groupId}</groupId>
             <artifactId>shib-profile-api</artifactId>
@@ -50,7 +56,24 @@
             <scope>provided</scope>
         </dependency>
         
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-saml-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        
         <!-- Test Dependencies -->
+        <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-testing</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/ECPProfileConfiguration.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/ECPProfileConfiguration.java
new file mode 100644
index 0000000..ba720f2
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/ECPProfileConfiguration.java
@@ -0,0 +1,21 @@
+/*
+ * 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.saml.saml2.profile.config.impl;
+
+/** Configuration support for SP SAML 2.0 ECP. */
+public class ECPProfileConfiguration extends BrowserSSOProfileConfiguration
+        implements net.shibboleth.sp.saml.saml2.profile.config.ECPProfileConfiguration {
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContext.java
similarity index 52%
copy from sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java
copy to sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContext.java
index 53dcab1..645ce3d 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContext.java
@@ -12,105 +12,102 @@
  * limitations under the License.
  */
 
-package net.shibboleth.sp.profile.impl;
+package net.shibboleth.sp.saml.saml2.profile.impl;
 
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
 import org.slf4j.Logger;
 import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.Application;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
 import net.shibboleth.sp.profile.InitiatorConstants;
+import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * Action that creates a {@link RelyingPartyContext} based on the identity of the eventual authenticating
- * authority (the IdP/OP/etc.).  
- *
- * <p>The authority ID is potentially supplied by the agent in the request, or may be defaulted
- * based on the {@link Application}.</p>
- * 
- * <p>In rare cases (mainly SAML ECP), it is not strictly required that the ID be known in advance.</p>
+ * Action that adds an outbound {@link MessageContext} and a {@link SAMLPeerEntityContext} to the
+ * {@link ProfileRequestContext} based on the identity of the eventual authenticating authority
+ * (the IdP/OP/etc.).  
+ *  *
+ * <p>The authority ID is pulled from a {@link RelyingPartyContext}. In rare cases (namely SAML ECP),
+ * it is not strictly required that the ID be known in advance, but in its absence the outbound
+ * message context is still created but with a different event returned.</p>
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_PROFILE_CTX}
  * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
+ * @event {@link InitiatorConstants#UNKNOWN_AUTHORITY}
  */
-public class InitializeRelyingPartyContext extends AbstractApplicationAction {
+public class PrepareOutboundMessageContext extends AbstractProfileAction {
 
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(InitializeRelyingPartyContext.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareOutboundMessageContext.class);
 
-    /** Creation strategy for {@link RelyingPartyContext}. */
-    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextCreationStrategy;
+    /** Lookup strategy for {@link RelyingPartyContext}. */
+    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
     
     /** The relying party name to base the inbound context on. */
     @Nullable private String relyingPartyId;
-
+    
     /** Constructor. */
-    public InitializeRelyingPartyContext() {
-        relyingPartyContextCreationStrategy = new ChildContextLookup<>(RelyingPartyContext.class, true);
+    public PrepareOutboundMessageContext() {
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
     }
     
     /**
-     * Set an optional lookup strategy to identify the relying party name, as a substitute for the session/logout
-     * assumptions made by the action otherwise.
+     * Set an optional lookup strategy for the {@link RelyingPartyContext}.
      * 
      * @param strategy lookup strategy
      */
-    public void setRelyingPartyContextCreationStrategy(
+    public void setRelyingPartyContextLookupStrategy(
             @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
         checkSetterPreconditions();
         
-        relyingPartyContextCreationStrategy =
+        relyingPartyContextLookupStrategy =
                 Constraint.isNotNull(strategy, "RelyingPartyContext creation strategy cannot be null");
     }
     
     /** {@inheritDoc} */
     @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
+    protected boolean doPreExecute(@Nonnull ProfileRequestContext profileRequestContext) {
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
         
-        final DDF input = ensureAgentRequestContext().getInput();
-        relyingPartyId = input != null ? input.getmember(InitiatorConstants.AUTHORITY).string() : null;
-        if (relyingPartyId == null) {
-            log.debug("{} Request did not specify authority to use, falling back to Application default",
-                    getLogPrefix());
-            relyingPartyId = ensureApplication().getAuthenticatingAuthority(profileRequestContext);
-        }
-
-        if (relyingPartyId == null) {
-            log.debug("{} No authority identified", getLogPrefix());
+        final RelyingPartyContext rpContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+        if (rpContext == null) {
+            log.error("{} No RelyingPartyContext found", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+            return false;
         }
         
+        relyingPartyId = rpContext.getRelyingPartyId();
         return true;
     }
-    
+
     /** {@inheritDoc} */
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        final RelyingPartyContext rpContext = relyingPartyContextCreationStrategy.apply(profileRequestContext);
-        if (rpContext == null) {
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
-            log.error("{} Unable to create dRelyingPartyContext", getLogPrefix());
-            return;
+        final MessageContext msgCtx = new MessageContext();
+        profileRequestContext.setOutboundMessageContext(msgCtx);
+
+        if (relyingPartyId != null) {
+            final SAMLPeerEntityContext peerContext = msgCtx.ensureSubcontext(SAMLPeerEntityContext.class);
+            peerContext.setEntityId(relyingPartyId);
+            log.debug("{} Initialized outbound context for message to {}", getLogPrefix(), relyingPartyId);
+        } else {
+            ActionSupport.buildEvent(profileRequestContext, InitiatorConstants.UNKNOWN_AUTHORITY);
+            log.debug("{} Initialized outbound context for message to unidentified authority", getLogPrefix());
         }
-        
-        rpContext.setRelyingPartyId(relyingPartyId);
-        log.debug("{} Initialized RelyingPartyContext for {}", getLogPrefix(), relyingPartyId);
     }
     
 }
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/package-info.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/package-info.java
new file mode 100644
index 0000000..f8d7d8e
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Profile actions and classes for SAML 2.0 profile flows.
+ */
+package net.shibboleth.sp.saml.saml2.profile.impl;
\ No newline at end of file
diff --git a/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContextTest.java b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContextTest.java
new file mode 100644
index 0000000..fa16c5c
--- /dev/null
+++ b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContextTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.saml.saml2.profile.impl;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.profile.InitiatorConstants;
+
+/**
+ * Unit test for {@link PrepareOutboundMessageContext} action.
+ */
+ at SuppressWarnings("javadoc")
+public class PrepareOutboundMessageContextTest {
+
+    private RequestContext src;
+    private ProfileRequestContext prc;
+    
+    private PrepareOutboundMessageContext action;
+
+    @BeforeMethod
+    public void beforeMethod() throws ComponentInitializationException {
+        src = new RequestContextBuilder().buildRequestContext();
+        prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+        prc.setInboundMessageContext(null);
+        prc.setOutboundMessageContext(null);
+        
+        action = new PrepareOutboundMessageContext();
+        action.initialize();
+    }
+
+    @Test
+    public void testNoContext() {
+        prc.removeSubcontext(RelyingPartyContext.class);
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertEvent(event, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+        
+        Assert.assertNull(prc.getOutboundMessageContext());
+    }
+ 
+    @Test
+    public void testNoAuthority() {
+        prc.ensureSubcontext(RelyingPartyContext.class).setRelyingPartyId(null);
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertEvent(event, InitiatorConstants.UNKNOWN_AUTHORITY);
+        
+        final MessageContext mc = prc.getOutboundMessageContext();
+        assert mc != null;
+        Assert.assertFalse(mc.containsSubcontext(SAMLPeerEntityContext.class));
+    }
+
+    @Test
+    public void testWithAuthority() {
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        final MessageContext mc = prc.getOutboundMessageContext();
+        assert mc != null;
+        Assert.assertEquals(mc.ensureSubcontext(SAMLPeerEntityContext.class).getEntityId(),
+                prc.ensureSubcontext(RelyingPartyContext.class).getRelyingPartyId());
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-api/pom.xml b/sp-server-api/pom.xml
index 8a44599..f642e18 100644
--- a/sp-server-api/pom.xml
+++ b/sp-server-api/pom.xml
@@ -28,6 +28,12 @@
             <artifactId>idp-profile-api</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-saml-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
                 
         <dependency>
             <groupId>${shib-profile.groupId}</groupId>
@@ -50,6 +56,11 @@
             <artifactId>shib-metadata-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>${shib-metadata.groupId}</groupId>
+            <artifactId>shib-metadata-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>${shib-shared.groupId}</groupId>
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java
new file mode 100644
index 0000000..5f49c0d
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java
@@ -0,0 +1,37 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * Constants for session initiator operations.
+ */
+public final class InitiatorConstants {
+
+    /** Authority input parameter. */
+    @Nonnull @NotEmpty public static final String AUTHORITY = "authority";
+
+    /** Target input parameter. */
+    @Nonnull @NotEmpty public static final String TARGET = "target";
+
+    /** Event indicating no authority identified. */
+    @Nonnull @NotEmpty public static final String UNKNOWN_AUTHORITY = "UnknownAuthority";
+
+    /** Private constructor. */
+    private InitiatorConstants() {}
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/AbstractAgentRequestLookupFunction.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/AbstractAgentRequestLookupFunction.java
new file mode 100644
index 0000000..67001a9
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/AbstractAgentRequestLookupFunction.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.context.navigate.messaging;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ParentContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.sp.context.AgentRequestContext;
+
+/**
+ * Base class for functions that need access to an {@link AgentRequestContext}.
+ * 
+ * @param <T> return type 
+ */
+public abstract class AbstractAgentRequestLookupFunction<T> implements Function<MessageContext,T> {
+
+    /** Lookup strategy for {@link AgentRequestContext}. */
+    @Nonnull private Function<MessageContext,AgentRequestContext> agentRequestContextLookupStrategy;
+
+    /** Constructor. */
+    @SuppressWarnings("null")
+    public AbstractAgentRequestLookupFunction() {
+        agentRequestContextLookupStrategy = new ChildContextLookup<>(AgentRequestContext.class).compose(
+                new ParentContextLookup<>(ProfileRequestContext.class));
+    }
+    
+    /**
+     * Sets the lookup strategy for the {@link AgentRequestContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setAgentRequestContextLookupStrategy(
+            @Nonnull final Function<MessageContext,AgentRequestContext> strategy) {
+        agentRequestContextLookupStrategy =
+                Constraint.isNotNull(strategy, "AgentRequestContext lookup strategy cannot be null");
+    }
+    
+    /**
+     * Gets the {@link AgentRequestContext} using the provided lookup strategy.
+     * 
+     * @param messageContext message context
+     * 
+     * @return the desired context or null
+     */
+    @Nullable public AgentRequestContext getAgentRequestContext(@Nullable final MessageContext messageContext) {
+        return agentRequestContextLookupStrategy.apply(messageContext);
+    }
+    
+    /**
+     * Gets the {@link AgentRequestContext} using the provided lookup strategy, raising an unchecked exceotion
+     * if it is absent.
+     * 
+     * @param messageContext message context
+     * 
+     * @return the desired context
+     */
+    @Nullable public AgentRequestContext ensureAgentRequestContext(@Nullable final MessageContext messageContext) {
+        final AgentRequestContext arc = agentRequestContextLookupStrategy.apply(messageContext);
+        if (arc == null) {
+            throw new IllegalStateException("AgentRequestContext was missing");
+        }
+        
+        return arc;
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/ApplicationMetadataResolverLookupFunction.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/ApplicationMetadataResolverLookupFunction.java
new file mode 100644
index 0000000..5b482c8
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/ApplicationMetadataResolverLookupFunction.java
@@ -0,0 +1,49 @@
+/*
+ * 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.context.navigate.messaging;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.saml.metadata.resolver.MetadataResolver;
+import org.opensaml.saml.metadata.resolver.RoleDescriptorResolver;
+import org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver;
+
+import net.shibboleth.idp.saml.metadata.impl.ReloadableMetadataResolver;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.context.AgentRequestContext;
+
+/**
+ * Locates the {@link MetadataResolver} associated with the {@link Application} making an agent request,
+ * and wraps it in a {@link RoleDescriptorResolver}.
+ */
+public class ApplicationMetadataResolverLookupFunction
+        extends AbstractAgentRequestLookupFunction<RoleDescriptorResolver> {
+
+    /** {@inheritDoc} */
+    @Nullable public RoleDescriptorResolver apply(@Nullable final MessageContext input) {
+        final AgentRequestContext arc = getAgentRequestContext(input);
+        if (arc != null) {
+            final Application application = arc.getApplication();
+            if (application != null) {
+                return new PredicateRoleDescriptorResolver(
+                        new ReloadableMetadataResolver(application.getMetadataResolver()));
+            }
+        }
+        
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/package-info.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/package-info.java
new file mode 100644
index 0000000..6566d4b
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Functions for navigating contexts and data on behalf of messaging objects.
+ */
+package net.shibboleth.sp.profile.context.navigate.messaging;
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestSupplier.java b/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestSupplier.java
new file mode 100644
index 0000000..3fd579d
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestSupplier.java
@@ -0,0 +1,61 @@
+/*
+ * 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.messaging.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.opensaml.profile.action.ProfileRequestContextAware;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.annotation.Prototype;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.sp.context.AgentRequestContext;
+
+/**
+ * An implementation of {@link NonnullSupplier} which relies on an injected {@link ProfileRequestContext}
+ * to locate a tunneled/remoted instance of {@link HttpServletRequest} from an agent request.
+ */
+ at Prototype
+ at NotThreadSafe
+public class RemotedHttpServletRequestSupplier implements NonnullSupplier<HttpServletRequest>, ProfileRequestContextAware {
+
+    /** Current profile request context. */
+    @Nullable private ProfileRequestContext profileRequestContext;
+    
+    /** {@inheritDoc} */
+    @Override
+    public void setProfileRequestContext(@Nullable ProfileRequestContext context) {
+        profileRequestContext = context;
+    }
+
+    /** {@inheritDoc} */
+    @Nonnull public HttpServletRequest get() {
+        final AgentRequestContext agentRequestContext = profileRequestContext != null
+                ? profileRequestContext.ensureSubcontext(AgentRequestContext.class)
+                        : null;
+        
+        final HttpServletRequest req =
+                agentRequestContext != null ? agentRequestContext.getWrappedHttpServletRequest() : null;
+        if (req == null) {
+            throw new IllegalStateException("HttpServletRequest not available in context tree");
+        }
+        
+        return req;
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedlHttpServletResponseSupplier.java b/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedlHttpServletResponseSupplier.java
new file mode 100644
index 0000000..309cea2
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedlHttpServletResponseSupplier.java
@@ -0,0 +1,61 @@
+/*
+ * 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.messaging.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.opensaml.profile.action.ProfileRequestContextAware;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import jakarta.servlet.http.HttpServletResponse;
+import net.shibboleth.shared.annotation.Prototype;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.sp.context.AgentRequestContext;
+
+/**
+ * An implementation of {@link NonnullSupplier} which relies on an injected {@link ProfileRequestContext}
+ * to locate a tunneled/remoted instance of {@link HttpServletResponse} from an agent request.
+ */
+ at Prototype
+ at NotThreadSafe
+public class RemotedlHttpServletResponseSupplier implements NonnullSupplier<HttpServletResponse>, ProfileRequestContextAware {
+
+    /** Current profile request context. */
+    @Nullable private ProfileRequestContext profileRequestContext;
+    
+    /** {@inheritDoc} */
+    @Override
+    public void setProfileRequestContext(@Nullable ProfileRequestContext context) {
+        profileRequestContext = context;
+    }
+
+    /** {@inheritDoc} */
+    @Nonnull public HttpServletResponse get() {
+        final AgentRequestContext agentRequestContext = profileRequestContext != null
+                ? profileRequestContext.ensureSubcontext(AgentRequestContext.class)
+                        : null;
+        
+        final HttpServletResponse req =
+                agentRequestContext != null ? agentRequestContext.getWrappedHttpServletResponse() : null;
+        if (req == null) {
+            throw new IllegalStateException("HttpServletResponse not available in context tree");
+        }
+        
+        return req;
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java
index 53dcab1..1abfb71 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java
@@ -105,12 +105,13 @@ public class InitializeRelyingPartyContext extends AbstractApplicationAction {
         final RelyingPartyContext rpContext = relyingPartyContextCreationStrategy.apply(profileRequestContext);
         if (rpContext == null) {
             ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
-            log.error("{} Unable to create dRelyingPartyContext", getLogPrefix());
+            log.error("{} Unable to create RelyingPartyContext", getLogPrefix());
             return;
         }
-        
+
         rpContext.setRelyingPartyId(relyingPartyId);
-        log.debug("{} Initialized RelyingPartyContext for {}", getLogPrefix(), relyingPartyId);
+        log.debug("{} Initialized RelyingPartyContext for {}", getLogPrefix(),
+                relyingPartyId != null ? relyingPartyId : "unidentified authority");
     }
     
 }
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectRelyingPartyConfiguration.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectRelyingPartyConfiguration.java
new file mode 100644
index 0000000..52630f5
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectRelyingPartyConfiguration.java
@@ -0,0 +1,141 @@
+/*
+ * 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.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfigurationResolver;
+import net.shibboleth.profile.relyingparty.VerifiedProfileCriterion;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+
+/**
+ * This action attempts to resolve a {@link RelyingPartyConfiguration} and adds it to a {@link RelyingPartyContext},
+ * using the requesting {@link Application} as the source of configurations rather than the generic resolver used
+ * by the IdP.
+ * 
+ * <p>It also handles the special case of there being no identified relying party, which is confined to a subset
+ * of scenarios in which the authority isn't known ahead of time. To avoid contaminating the inbound side,
+ * this is handled specially as a verified case.</p>
+ * 
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
+ * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CONFIG}
+ * 
+ * @post If a {@link RelyingPartyContext} is located, it will be populated with a non-null result of applying
+ * the applicaton's {@link RelyingPartyConfigurationResolver} to the {@link ProfileRequestContext}.
+ */
+public final class SelectRelyingPartyConfiguration extends AbstractApplicationAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(SelectRelyingPartyConfiguration.class);
+
+    /**
+     * Strategy used to locate the {@link RelyingPartyContext} associated with a given {@link ProfileRequestContext}.
+     */
+    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+    /** The {@link RelyingPartyContext} to manipulate. */
+    @NonnullBeforeExec private RelyingPartyContext relyingPartyCtx;
+    
+    /** Constructor. */
+    public SelectRelyingPartyConfiguration() {
+        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+    }
+    
+    /**
+     * Set the strategy used to locate the {@link RelyingPartyContext} associated with a given
+     * {@link ProfileRequestContext}.
+     * 
+     * @param strategy strategy used to locate the {@link RelyingPartyContext} associated with a given
+     *            {@link ProfileRequestContext}
+     */
+    public void setRelyingPartyContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+        checkSetterPreconditions();
+        
+        relyingPartyContextLookupStrategy =
+                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+    }
+    
+    /** Null safe getter.
+     * @return Returns the relyingPartyCtx.
+     */
+    @SuppressWarnings("null")
+    @Nonnull private RelyingPartyContext getRelyingPartyCtx() {
+        assert isPreExecuteCalled();
+        return relyingPartyCtx;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        relyingPartyCtx = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+        if (relyingPartyCtx == null) {
+            log.debug("{} No relying party context available", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    public void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        try {
+            final CriteriaSet criteria = new CriteriaSet();
+            if (getRelyingPartyCtx().isVerified() || getRelyingPartyCtx().getRelyingPartyId() == null) {
+                criteria.add(new VerifiedProfileCriterion(true));
+            }
+            criteria.add(new ProfileRequestContextCriterion(profileRequestContext));
+
+            final RelyingPartyConfiguration config = ensureApplication().resolveSingle(criteria);
+            if (config == null) {
+                log.debug("{} No relying party configuration applies to this request", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+                return;
+            }
+
+            log.debug("{} Found relying party configuration {} for request", getLogPrefix(), config.getId());
+            getRelyingPartyCtx().setConfiguration(config);
+        } catch (final ResolverException e) {
+            log.error("{} Error trying to resolve relying party configuration", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+        }
+    }
+    
+}
\ 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