[java-plugin-shibd] branch main updated: Work in progress on SAML session initiator flow.
Scott Cantor
cantor.2 at osu.edu
Wed Jul 24 17:02:05 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=2e8f4ee6fcff9b59dbddf8794cefd1562e3c9151
The following commit(s) were added to refs/heads/main by this push:
new 2e8f4ee Work in progress on SAML session initiator flow.
2e8f4ee is described below
commit 2e8f4ee6fcff9b59dbddf8794cefd1562e3c9151
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Jul 24 13:02:01 2024 -0400
Work in progress on SAML session initiator flow.
---
.../META-INF/net.shibboleth.idp/postconfig.xml | 3 +
.../idp/flows/sp/initiator/initiator-flow.xml | 2 +
.../idp/flows/sp/initiator/saml2/saml2-beans.xml | 151 ++++++-
.../idp/flows/sp/initiator/saml2/saml2-flow.xml | 24 +-
.../session-initiator/session-initiator-beans.xml | 3 -
.../session-initiator/session-initiator-flow.xml | 1 -
.../saml/saml2/profile/impl/AddAuthnRequest.java | 461 +++++++++++++++++++++
.../impl/PrepareOutboundMessageContext.java | 113 -----
.../impl/PrepareOutboundMessageContextTest.java | 89 ----
.../main/java/net/shibboleth/sp/Application.java | 4 +-
.../shibboleth/sp/context/AgentRequestContext.java | 16 +-
.../sp/messaging}/RemotedHttpServletRequest.java | 2 +-
.../sp/messaging}/RemotedHttpServletResponse.java | 2 +-
.../net/shibboleth/sp/messaging/package-info.java | 18 +
.../AbstractAgentRequestLookupFunction.java | 18 +-
.../context/navigate/IssuerLookupFunction.java | 55 +++
.../AbstractAgentRequestLookupFunction.java | 2 +-
.../sp/profile/context/navigate/package-info.java | 18 +
sp-server-impl/pom.xml | 5 +
.../net/shibboleth/sp/impl/BasicApplication.java | 2 +-
.../RemotedHttpServletRequestResponseContext.java | 80 ++++
.../impl/RemotedHttpServletRequestSupplier.java | 31 +-
.../impl/RemotedlHttpServletResponseSupplier.java | 36 +-
.../sp/profile/impl/DecodeAgentRequest.java | 6 +-
.../shibboleth/sp/profile/impl/EncodeMessage.java | 72 ++++
.../impl/InitializeRelyingPartyContext.java | 34 +-
.../impl/SelectRelyingPartyConfiguration.java | 7 +-
.../impl/RemotedHttpServletRequestTest.java | 2 +-
.../impl/RemotedHttpServletResponseTest.java | 2 +-
.../impl/InitializeRelyingPartyContextTest.java | 16 +-
30 files changed, 960 insertions(+), 315 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 513bf41..2622eab 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
@@ -38,4 +38,7 @@
</property>
</bean>
+ <bean id="shibboleth.RemotedHttpServletRequestSupplier" class="net.shibboleth.sp.messaging.impl.RemotedHttpServletRequestSupplier" />
+ <bean id="shibboleth.RemotedHttpServletResponseSupplier" class="net.shibboleth.sp.messaging.impl.RemotedHttpServletResponseSupplier" />
+
</beans>
\ No newline at end of file
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
index 1d4e083..3227a5b 100644
--- 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
@@ -15,6 +15,7 @@
<end-state id="InvalidEvent" />
<end-state id="InvalidProfileContext" />
<end-state id="InvalidRelyingPartyContext" />
+ <end-state id="InvalidRelyingPartyConfiguration" />
<end-state id="ReselectFlow" />
<end-state id="RuntimeException" />
@@ -31,6 +32,7 @@
<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>
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
index d22705d..d2022fa 100644
--- 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
@@ -7,13 +7,14 @@
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="InitializeRelyingPartyContext"
+ class="net.shibboleth.sp.profile.impl.InitializeRelyingPartyContext" scope="prototype" />
<bean id="WebFlowMessageHandlerAdaptor" abstract="true"
- class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype" />
+ class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+ c:executionDirection="INBOUND" />
- <bean id="SAMLProtocolAndRole" parent="WebFlowMessageHandlerAdaptor" c:executionDirection="OUTBOUND">
+ <bean id="SAMLProtocolAndRole" parent="WebFlowMessageHandlerAdaptor">
<constructor-arg name="messageHandler">
<bean class="org.opensaml.saml.common.binding.impl.SAMLProtocolAndRoleHandler" scope="prototype"
p:protocol-ref="shibboleth.MetadataLookup.Protocol"
@@ -27,7 +28,7 @@
<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">
+ <bean id="SAMLMetadataLookup" parent="WebFlowMessageHandlerAdaptor">
<constructor-arg name="messageHandler">
<bean class="org.opensaml.saml.common.binding.impl.SAMLMetadataLookupHandler" scope="prototype">
<property name="roleDescriptorResolverLookupStrategy">
@@ -38,12 +39,7 @@
</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" />
+ class="net.shibboleth.idp.saml.profile.impl.InitializeRelyingPartyContextFromSAMLPeer" scope="prototype" />
<bean id="SelectRelyingPartyConfiguration"
class="net.shibboleth.sp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype" />
@@ -52,4 +48,137 @@
class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
p:profileId="T(net.shibboleth.saml.saml2.profile.config.BrowserSSOProfileConfiguration).PROFILE_ID" />
+ <bean id="AppAwareIssuerLookupFunction" class="net.shibboleth.sp.profile.context.navigate.IssuerLookupFunction" />
+
+ <bean id="InitializeOutboundMessageContext"
+ class="net.shibboleth.idp.saml.profile.impl.InitializeOutboundMessageContext" scope="prototype"
+ p:issuerLookupStrategy="AppAwareIssuerLookupFunction" />
+
+ <bean id="InitializeMessageChannelSecurityContext" parent="WebFlowProfileActionAdaptor" scope="prototype">
+ <constructor-arg>
+ <bean class="org.opensaml.profile.action.impl.StaticMessageChannelSecurity"
+ p:confidentialityActive="false" p:integrityActive="false" />
+ </constructor-arg>
+ </bean>
+
+ <util:constant id="shibboleth.EndpointType"
+ static-field="org.opensaml.saml.saml2.metadata.SingleSignOnService.DEFAULT_ELEMENT_NAME" />
+
+ <util:list id="OutgoingSAML2SPRequestBindings">
+ <ref bean="shibboleth.Binding.SAML2Redirect" />
+ <ref bean="shibboleth.Binding.SAML2POST" />
+ <ref bean="shibboleth.Binding.SAML2POSTSimpleSign" />
+ </util:list>
+
+ <bean id="OutgoingSAML2SPRequestBindingsStrategy" parent="shibboleth.Functions.Constant"
+ c:_0-ref="OutgoingSAML2SPRequestBindings" />
+
+ <bean id="PopulateBindingAndEndpointContexts"
+ class="net.shibboleth.idp.saml.profile.impl.PopulateBindingAndEndpointContexts" scope="prototype"
+ p:endpointResolver-ref="shibboleth.EndpointResolver"
+ p:endpointType-ref="shibboleth.EndpointType"
+ p:bindingDescriptorsLookupStrategy-ref="OutgoingSAML2SPRequestBindingsStrategy"
+ p:artifactImpliesSecureChannel="%{sp.artifact.secureChannel:true}" />
+
+ <bean id="PopulateRequestSignatureSigningParameters"
+ class="org.opensaml.saml.common.profile.impl.PopulateSignatureSigningParameters" scope="prototype"
+ p:configurationLookupStrategy-ref="shibboleth.SignatureSigningConfigurationLookup"
+ p:signatureSigningParametersResolver-ref="shibboleth.SignatureSigningParametersResolver"
+ p:noResultIsError="false">
+ <property name="activationCondition">
+ <bean class="net.shibboleth.saml.profile.config.logic.SignRequestsPredicate"
+ p:honorMetadata="%{sp.saml.honorWantAuthnRequestsSigned:true}" />
+ </property>
+ </bean>
+
+ <bean id="PopulateEncryptionParameters"
+ class="net.shibboleth.idp.saml.saml2.profile.impl.PopulateEncryptionParameters" scope="prototype"
+ p:configurationLookupStrategy-ref="shibboleth.EncryptionConfigurationLookup"
+ p:encryptionParametersResolver-ref="shibboleth.EncryptionParametersResolver"
+ p:protocol-ref="shibboleth.MetadataLookup.Protocol"
+ p:role-ref="shibboleth.MetadataLookup.Role" />
+
+ <bean id="AddAuthnRequest"
+ class="net.shibboleth.sp.saml.saml2.profile.impl.AddAuthnRequest" scope="prototype"
+ p:overwriteExisting="true"
+ p:convertUnknownRequestedPrincipals="%{sp.authn.SAML.convertUnknownRequestedPrincipals:false}"
+ p:issuerLookupStrategy-ref="AppAwareIssuerLookupFunction"
+ p:nameIDLookupStrategy="#{getObject('%{sp.authn.SAML.NameIDLookupStrategy:}'.trim())}">
+ <property name="identifierGeneratorLookupStrategy">
+ <bean class="net.shibboleth.profile.config.navigate.IdentifierGenerationStrategyLookupFunction"
+ p:defaultIdentifierGenerationStrategy-ref="shibboleth.DefaultIdentifierGenerationStrategy" />
+ </property>
+ </bean>
+
+ <!-- Default formats not to encrypt. -->
+ <util:set id="DefaultPlaintextNameIDFormats">
+ <util:constant static-field="org.opensaml.saml.saml2.core.NameIDType.ENTITY" />
+ </util:set>
+
+ <bean id="EncryptNameIDs"
+ class="org.opensaml.saml.saml2.profile.impl.EncryptNameIDs" scope="prototype"
+ p:excludedFormats="#{getObject('shibboleth.PlaintextNameIDFormats') ?: getObject('DefaultPlaintextNameIDFormats')}"
+ p:recipientLookupStrategy-ref="shibboleth.RelyingPartyIdLookup.Simple">
+ <property name="encryptionContextLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.EncryptionParameters"
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+ </property>
+ </bean>
+
+ <bean id="HandleOutboundMessage" parent="WebFlowMessageHandlerAdaptor"
+ c:messageHandler-ref="shibboleth.PreEncodeMessageHandler"
+ c:executionDirection="OUTBOUND">
+ <property name="errorEvent">
+ <util:constant static-field="org.opensaml.profile.action.EventIds.MESSAGE_PROC_ERROR" />
+ </property>
+ </bean>
+
+ <bean id="PreEncodeMessageHandler"
+ class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain" scope="prototype">
+ <property name="handlers">
+ <list>
+ <bean class="org.opensaml.saml.common.binding.impl.SAMLOutboundDestinationHandler" scope="prototype"/>
+ <bean class="org.opensaml.saml.common.binding.security.impl.EndpointURLSchemeSecurityHandler" scope="prototype"/>
+ <bean class="org.opensaml.messaging.handler.impl.FunctionMessageHandler" scope="prototype"
+ p:function="#{getObject('%{sp.SAML.outboundMessageHandlerFunction:}'.trim())}" />
+ <bean class="org.opensaml.saml.common.binding.security.impl.SAMLOutboundProtocolMessageSigningHandler" scope="prototype">
+ <property name="activationCondition">
+ <bean parent="shibboleth.Conditions.NOT">
+ <constructor-arg>
+ <bean class="org.opensaml.saml.common.messaging.logic.SignatureCapableBindingPredicate" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="messageEncoderFactory" class="net.shibboleth.idp.saml.profile.impl.SpringAwareMessageEncoderFactory" />
+
+ <bean id="EncodeMessage" class="net.shibboleth.sp.profile.impl.EncodeMessage" scope="prototype"
+ p:createOutputObjects="true"
+ p:messageEncoderFactory-ref="messageEncoderFactory" />
+
+ <!-- Override IdP's encoders to supply the DDF-backed servlet supplier. -->
+
+ <bean id="shibboleth.Encoders.SAML2RedirectEncoder"
+ class="org.opensaml.saml.saml2.binding.encoding.impl.HTTPRedirectDeflateEncoder" scope="prototype" init-method=""
+ p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier" />
+
+ <bean id="shibboleth.Encoders.SAML2PostEncoder"
+ class="org.opensaml.saml.saml2.binding.encoding.impl.HTTPPostEncoder" scope="prototype" init-method=""
+ p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier"
+ p:velocityEngine-ref="shibboleth.VelocityEngine"
+ p:cSPDigester="#{%{sp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPDigester') : null}"
+ p:cSPNonceGenerator="#{%{sp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPNonce') : null}" />
+
+ <bean id="shibboleth.Encoders.SAML2PostSimpleSignEncoder"
+ class="org.opensaml.saml.saml2.binding.encoding.impl.HTTPPostSimpleSignEncoder" scope="prototype" init-method=""
+ p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier"
+ p:velocityEngine-ref="shibboleth.VelocityEngine"
+ p:cSPDigester="#{%{sp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPDigester') : null}"
+ p:cSPNonceGenerator="#{%{sp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPNonce') : null}" />
+
</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
index 9c7c89a..12a2dcb 100644
--- 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
@@ -4,29 +4,33 @@
parent="sp/initiator">
<action-state id="SAML2SessionInitiator">
- <evaluate expression="PrepareOutboundMessageContext" />
+ <evaluate expression="InitializeRelyingPartyContext" />
+ <evaluate expression="PrepareInboundMessageContext" />
<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="InitializeOutboundMessageContext" />
+ <evaluate expression="InitializeMessageChannelSecurityContext" />
+ <!-- TODO: RelayState handling so it's populated on the inbound side... -->
<evaluate expression="PopulateBindingAndEndpointContexts" />
<evaluate expression="PopulateRequestSignatureSigningParameters" />
- <evaluate expression="PopulateRequestEncryptionParameters" />
+ <evaluate expression="PopulateEncryptionParameters" />
<evaluate expression="AddAuthnRequest" />
+ <evaluate expression="EncryptNameIDs" />
+
+ <evaluate expression="HandleOutboundMessage" />
+ <evaluate expression="EncodeMessage" />
<evaluate expression="'proceed'" />
-
+
<transition on="proceed" to="proceed" />
+ <!-- Remap any other events into a fall-through to the next flow. -->
+ <transition to="ReselectFlow" />
</action-state>
<!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
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 3611a02..5a13abe 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
@@ -10,7 +10,4 @@
<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="InitializeRelyingPartyContext"
- class="net.shibboleth.sp.profile.impl.InitializeRelyingPartyContext" scope="prototype" />
-
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
index 9b21c44..de61b5a 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
@@ -16,7 +16,6 @@
<on-entry>
<evaluate expression="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getApplication().getSessionInitiators(opensamlProfileRequestContext).iterator()" result="flowScope.SessionInitiatorIterator" />
</on-entry>
- <evaluate expression="InitializeRelyingPartyContext" />
<evaluate expression="'proceed'" />
<!-- Branch to child flow for actual work. -->
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddAuthnRequest.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddAuthnRequest.java
new file mode 100644
index 0000000..dffdda0
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddAuthnRequest.java
@@ -0,0 +1,461 @@
+/*
+ * 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 java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.profile.config.navigate.IdentifierGenerationStrategyLookupFunction;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.saml.saml2.profile.config.BrowserSSOProfileConfiguration;
+
+import org.opensaml.core.xml.XMLObjectBuilderFactory;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.SAMLObjectBuilder;
+import org.opensaml.saml.common.SAMLVersion;
+import org.opensaml.saml.ext.reqattr.RequestedAttributes;
+import org.opensaml.saml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.Extensions;
+import org.opensaml.saml.saml2.core.IDPEntry;
+import org.opensaml.saml.saml2.core.IDPList;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.NameIDPolicy;
+import org.opensaml.saml.saml2.core.RequestedAuthnContext;
+import org.opensaml.saml.saml2.core.RequesterID;
+import org.opensaml.saml.saml2.core.Scoping;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.metadata.RequestedAttribute;
+import org.slf4j.Logger;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Action that creates an {@link AuthnRequest} and sets it as the message returned by
+ * {@link ProfileRequestContext#getOutboundMessageContext()}.
+ *
+ * <p>If an issuer value is returned via a lookup strategy, then it's set as the Issuer of the message.</p>
+ *
+ * <p>Various other values are derived from the active configuration such as {@link RequestedAuthnContext},
+ * {@link NameIDPolicy}, and {@link Scoping}. This process is much more complex than the IdP, as it requires
+ * marrying together inputs from the agent against the profile configuration, including enforcing limits
+ * on what the agent can override/supply.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ *
+ * @post ProfileRequestContext.getOutboundMessageContext().getMessage() != null
+ */
+public class AddAuthnRequest extends AbstractApplicationAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(AddAuthnRequest.class);
+
+ /** Overwrite an existing message? */
+ private boolean overwriteExisting;
+
+ /** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
+ @Nonnull private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
+
+ /** Strategy used to obtain the request issuer value. */
+ @Nullable private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Optional strategy to populate request with a {@link NameID}. */
+ @Nullable private Function<ProfileRequestContext,NameID> nameIDLookupStrategy;
+
+ /** The generator to use. */
+ @NonnullBeforeExec private IdentifierGenerationStrategy idGenerator;
+
+ /** Applicable profile configuration. */
+ @NonnullBeforeExec private BrowserSSOProfileConfiguration profileConfiguration;
+
+ /** EntityID to populate into Issuer element. */
+ @Nullable private String issuerId;
+
+ /** Constructor. */
+ public AddAuthnRequest() {
+ // Default strategy is a 16-byte secure random source.
+ idGeneratorLookupStrategy = new IdentifierGenerationStrategyLookupFunction();
+ }
+
+ /**
+ * Set whether to overwrite an existing message.
+ *
+ * @param flag flag to set
+ */
+ public void setOverwriteExisting(final boolean flag) {
+ checkSetterPreconditions();
+ overwriteExisting = flag;
+ }
+
+ /**
+ * Set the strategy used to locate the {@link IdentifierGenerationStrategy} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIdentifierGeneratorLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,IdentifierGenerationStrategy> strategy) {
+ checkSetterPreconditions();
+ idGeneratorLookupStrategy =
+ Constraint.isNotNull(strategy, "IdentifierGenerationStrategy lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the issuer value to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+ checkSetterPreconditions();
+ issuerLookupStrategy = strategy;
+ }
+
+ /**
+ * Set optional strategy to derive a {@link NameID} to populate into the {@link AuthnRequest}'s
+ * {@link Subject} element.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setNameIDLookupStrategy(@Nullable final Function<ProfileRequestContext,NameID> strategy) {
+ checkSetterPreconditions();
+ nameIDLookupStrategy = strategy;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
+ if (rpCtx != null && rpCtx.getProfileConfig() instanceof BrowserSSOProfileConfiguration sso) {
+ profileConfiguration = sso;
+ }
+ if (profileConfiguration == null) {
+ log.error("{} BrowserSSOProfileConfiguration not found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return false;
+ }
+
+ final MessageContext outboundMessageCtx = profileRequestContext.getOutboundMessageContext();
+ if (outboundMessageCtx == null) {
+ log.debug("{} No outbound message context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ } else if (!overwriteExisting && outboundMessageCtx.getMessage() != null) {
+ log.debug("{} Outbound message context already contains a message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ idGenerator = idGeneratorLookupStrategy.apply(profileRequestContext);
+ if (idGenerator == null) {
+ log.debug("{} No identifier generation strategy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ if (issuerLookupStrategy != null) {
+ issuerId = issuerLookupStrategy.apply(profileRequestContext);
+ }
+
+ outboundMessageCtx.setMessage(null);
+
+ return true;
+ }
+
+// Checkstyle: MethodLength OFF
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+ final SAMLObjectBuilder<AuthnRequest> requestBuilder =
+ (SAMLObjectBuilder<AuthnRequest>) bf.<AuthnRequest>ensureBuilder(
+ AuthnRequest.DEFAULT_ELEMENT_NAME);
+ final SAMLObjectBuilder<NameIDPolicy> nipBuilder =
+ (SAMLObjectBuilder<NameIDPolicy>) bf.<NameIDPolicy>ensureBuilder(
+ NameIDPolicy.DEFAULT_ELEMENT_NAME);
+
+ final AuthnRequest object = requestBuilder.buildObject();
+ object.setID(idGenerator.generateIdentifier());
+ object.setIssueInstant(Instant.now());
+ object.setVersion(SAMLVersion.VERSION_20);
+
+ // TODO: agent override
+ final Integer index = profileConfiguration.getAttributeIndex(profileRequestContext);
+ if (index != null) {
+ log.debug("{} Setting AttributeConsumingServiceIndex to '{}' for SAML AuthnRequest", getLogPrefix(),
+ index);
+ object.setAttributeConsumingServiceIndex(index);
+ }
+
+ if (issuerId != null) {
+ log.debug("{} Setting Issuer to {}", getLogPrefix(), issuerId);
+ final SAMLObjectBuilder<Issuer> issuerBuilder =
+ (SAMLObjectBuilder<Issuer>) bf.<Issuer>ensureBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+ final Issuer issuer = issuerBuilder.buildObject();
+ issuer.setValue(issuerId);
+ object.setIssuer(issuer);
+ } else {
+ log.debug("{} No issuer value available, leaving Issuer unset", getLogPrefix());
+ }
+
+ // TODO: agent override
+ if (profileConfiguration.isForceAuthn(profileRequestContext)) {
+ log.debug("{} Setting ForceAuthn for SAML AuthnRequest", getLogPrefix());
+ object.setForceAuthn(true);
+ }
+
+ // TODO: Only set passive based on agent request.
+ if (false) {
+ log.debug("{} Setting IsPassive for SAML AuthnRequest", getLogPrefix());
+ object.setIsPassive(true);
+ }
+
+ // TODO: agent override
+ final NameIDPolicy nip = nipBuilder.buildObject();
+ nip.setAllowCreate(true);
+ final String qualifier = profileConfiguration.getSPNameQualifier(profileRequestContext);
+ if (qualifier != null) {
+ log.debug("{} Setting NameIDPolicy SPNameQualifier to '{}' for SAML AuthnRequest", getLogPrefix(),
+ qualifier);
+ nip.setSPNameQualifier(qualifier);
+ }
+
+ // TODO: agent override, use metadata for NameID Formats too?
+ final String format = profileConfiguration.getNameIDFormat(profileRequestContext);
+ if (format != null) {
+ log.debug("{} Setting NameIDPolicy Format to '{}' for SAML AuthnRequest", getLogPrefix(), format);
+ nip.setFormat(format);
+ }
+
+ object.setNameIDPolicy(nip);
+
+ final RequestedAuthnContext rac = buildRequestedAuthnContext(profileRequestContext);
+ if (rac != null) {
+ final AuthnContextComparisonTypeEnumeration operator =
+ profileConfiguration.getAuthnContextComparison(profileRequestContext);
+ if (operator != null) {
+ log.debug("{} Setting RequestedAuthnContext comparison to {}", getLogPrefix(), operator);
+ rac.setComparison(operator);
+ }
+ object.setRequestedAuthnContext(rac);
+ }
+
+ object.setSubject(buildSubject(profileRequestContext));
+ object.setScoping(buildScoping(profileRequestContext));
+ object.setExtensions(buildExtensions(profileRequestContext));
+
+ final MessageContext omc = profileRequestContext.getOutboundMessageContext();
+ assert omc != null;
+ omc.setMessage(object);
+ }
+
+ /**
+ * Build a {@link RequestedAuthnContext} if warranted.
+ *
+ * @param profileRequestContext current profile request context
+ *
+ * @return the object to include in the request, or null
+ */
+ @Nullable private RequestedAuthnContext buildRequestedAuthnContext(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+
+ // RequestedAuthnContext also based on profile configuration.
+ assert profileConfiguration!=null;
+ final List<String> classrefs = profileConfiguration.getAuthnContextClassRefs(profileRequestContext);
+ if (classrefs.isEmpty()) {
+ return null;
+ }
+
+ log.debug("{} Setting RequestedAuthnContext class refs to {}", getLogPrefix(), classrefs);
+
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+
+ final SAMLObjectBuilder<RequestedAuthnContext> builder =
+ (SAMLObjectBuilder<RequestedAuthnContext>) bf.<RequestedAuthnContext>ensureBuilder(
+ RequestedAuthnContext.DEFAULT_ELEMENT_NAME);
+
+ final SAMLObjectBuilder<AuthnContextClassRef> acBuilder =
+ (SAMLObjectBuilder<AuthnContextClassRef>) bf.<AuthnContextClassRef>ensureBuilder(
+ AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
+
+ final RequestedAuthnContext rac = builder.buildObject();
+
+ classrefs.forEach(ref -> {
+ final AuthnContextClassRef obj = acBuilder.buildObject();
+ obj.setURI(ref);
+ rac.getAuthnContextClassRefs().add(obj);
+ });
+
+ return rac;
+ }
+// Checkstyle: MethodLength ON
+
+ /**
+ * Build a {@link Subject} element if necessary.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return the {@link Subject} element to include
+ */
+ @Nullable private Subject buildSubject(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final NameID nameID = nameIDLookupStrategy != null
+ ? nameIDLookupStrategy.apply(profileRequestContext) : null;
+ if (nameID == null) {
+ return null;
+ }
+
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+ final SAMLObjectBuilder<Subject> subjectBuilder =
+ (SAMLObjectBuilder<Subject>) bf.<Subject>ensureBuilder(Subject.DEFAULT_ELEMENT_NAME);
+
+ final Subject subject = subjectBuilder.buildObject();
+ subject.setNameID(nameID);
+
+ log.debug("{} Populating request with NameID '{}' and Format '{}'", getLogPrefix(),
+ nameID.getValue(), nameID.getFormat());
+
+ return subject;
+ }
+
+ /**
+ * Build a {@link Scoping} element, decrementing the proxy count if set.
+ *
+ * @param profileRequestContext current profile request context
+ * @param count proxy count
+ * @param idplist list of IdP entityIDs
+ *
+ * @return populated {@link Scoping}
+ */
+ @Nullable private Scoping buildScoping(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ boolean include = false;
+
+ // TODO: may need to add this back to PC
+ // assert profileConfiguration != null;
+ //if (profileConfiguration.isIgnoreScoping(profileRequestContext)) {
+ // log.warn("{} Skipping generation of Scoping element in violation of standard", getLogPrefix());
+ // return null;
+ //}
+
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+
+ final SAMLObjectBuilder<Scoping> scopingBuilder =
+ (SAMLObjectBuilder<Scoping>) bf.<Scoping>ensureBuilder(Scoping.DEFAULT_ELEMENT_NAME);
+ final Scoping scoping = scopingBuilder.buildObject();
+
+ // TODO: from agent
+ if (false) {
+ //scoping.setProxyCount(Integer.max(0, count - 1));
+ include = true;
+ }
+
+ // TODO: from agent
+ if (false) {
+ final SAMLObjectBuilder<IDPList> idpListBuilder =
+ (SAMLObjectBuilder<IDPList>) bf.<IDPList>ensureBuilder(IDPList.DEFAULT_ELEMENT_NAME);
+ final SAMLObjectBuilder<IDPEntry> idpBuilder =
+ (SAMLObjectBuilder<IDPEntry>) bf.<IDPEntry>ensureBuilder(IDPEntry.DEFAULT_ELEMENT_NAME);
+
+ final IDPList idps = idpListBuilder.buildObject();
+ for (final String idp : CollectionSupport.<String>emptyList()) {
+ final IDPEntry entry = idpBuilder.buildObject();
+ entry.setProviderID(idp);
+ idps.getIDPEntrys().add(entry);
+ }
+ scoping.setIDPList(idps);
+ include = true;
+ }
+
+ final SAMLObjectBuilder<RequesterID> requesterIdBuilder =
+ (SAMLObjectBuilder<RequesterID>) bf.<RequesterID>ensureBuilder(RequesterID.DEFAULT_ELEMENT_NAME);
+
+ // TODO: from agent
+ final String immediateRequester = null;
+ if (immediateRequester != null) {
+ final RequesterID requesterId = requesterIdBuilder.buildObject();
+ requesterId.setURI(immediateRequester);
+ scoping.getRequesterIDs().add(requesterId);
+ include = true;
+ }
+
+ return include ? scoping : null;
+ }
+
+ /**
+ * Build {@link RequestedAttributes} extension if required.
+ *
+ * @param profileRequestContext current profile request context
+ *
+ * @return extension or null
+ */
+ @Nullable private Extensions buildExtensions(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ // TODO: agent override?
+
+ assert profileConfiguration!=null;
+ final Collection<RequestedAttribute> attrs = profileConfiguration.getRequestedAttributes(profileRequestContext);
+ if (!attrs.isEmpty()) {
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+ final SAMLObjectBuilder<Extensions> extBuilder =
+ (SAMLObjectBuilder<Extensions>) bf.<Extensions>ensureBuilder(
+ Extensions.DEFAULT_ELEMENT_NAME);
+ final SAMLObjectBuilder<RequestedAttributes> reqExtBuilder =
+ (SAMLObjectBuilder<RequestedAttributes>) bf.<RequestedAttributes>ensureBuilder(
+ RequestedAttributes.DEFAULT_ELEMENT_NAME);
+ final RequestedAttributes reqExt = reqExtBuilder.buildObject();
+ attrs.forEach(attr -> {
+ try {
+ assert attr != null;
+ reqExt.getRequestedAttributes().add(XMLObjectSupport.cloneXMLObject(attr));
+ } catch (final MarshallingException|UnmarshallingException e) {
+ log.error("{} Error cloning RequestedAttribute from profile configuration", getLogPrefix(), e);
+ }
+ });
+ final Extensions ext = extBuilder.buildObject();
+ ext.getUnknownXMLObjects().add(reqExt);
+ return ext;
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContext.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContext.java
deleted file mode 100644
index 645ce3d..0000000
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContext.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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 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.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 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 PrepareOutboundMessageContext extends AbstractProfileAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareOutboundMessageContext.class);
-
- /** 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 PrepareOutboundMessageContext() {
- relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
- }
-
- /**
- * Set an optional lookup strategy for the {@link RelyingPartyContext}.
- *
- * @param strategy lookup strategy
- */
- public void setRelyingPartyContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
- checkSetterPreconditions();
-
- relyingPartyContextLookupStrategy =
- Constraint.isNotNull(strategy, "RelyingPartyContext creation strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- 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 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());
- }
- }
-
-}
\ 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
deleted file mode 100644
index fa16c5c..0000000
--- a/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareOutboundMessageContextTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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/src/main/java/net/shibboleth/sp/Application.java b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
index 1e34236..0a13a70 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
@@ -57,7 +57,7 @@ public interface Application extends RelyingPartyConfigurationResolver {
*
* @return default issue value to use
*/
- @Nullable @NotEmpty String getIssuer(@Nonnull final ProfileRequestContext profileRequestContext);
+ @Nullable @NotEmpty String getIssuer(@Nullable final ProfileRequestContext profileRequestContext);
/**
* Gets the identifier of the authenticating authority (IdP, OP, etc.) to use for this application
@@ -67,7 +67,7 @@ public interface Application extends RelyingPartyConfigurationResolver {
*
* @return authenticating authority
*/
- @Nullable @NotEmpty String getAuthenticatingAuthority(@Nonnull final ProfileRequestContext profileRequestContext);
+ @Nullable @NotEmpty String getAuthenticatingAuthority(@Nullable final ProfileRequestContext profileRequestContext);
/**
* Get an ordered list of session initiator flows to attempt for this application.
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/context/AgentRequestContext.java b/sp-server-api/src/main/java/net/shibboleth/sp/context/AgentRequestContext.java
index c799fb9..d8fcc80 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/context/AgentRequestContext.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/context/AgentRequestContext.java
@@ -19,11 +19,11 @@ import javax.annotation.Nullable;
import org.opensaml.messaging.context.BaseContext;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
import net.shibboleth.sp.Agent;
import net.shibboleth.sp.Application;
import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
/**
* General context for an agent request tracking basic information about the request, the messages
@@ -47,10 +47,10 @@ public class AgentRequestContext extends BaseContext {
@Nullable private DDF output;
/** Wrapped servlet request backed by input. */
- @Nullable private HttpServletRequest wrappedHttpServletRequest;
+ @Nullable private RemotedHttpServletRequest wrappedHttpServletRequest;
/** Wrapped servlet response backed by output. */
- @Nullable private HttpServletResponse wrappedHttpServletResponse;
+ @Nullable private RemotedHttpServletResponse wrappedHttpServletResponse;
/**
* Get the agent making the request.
@@ -143,7 +143,7 @@ public class AgentRequestContext extends BaseContext {
*
* @return wrapped servlet request
*/
- @Nullable public HttpServletRequest getWrappedHttpServletRequest() {
+ @Nullable public RemotedHttpServletRequest getRemotedHttpServletRequest() {
return wrappedHttpServletRequest;
}
@@ -154,7 +154,7 @@ public class AgentRequestContext extends BaseContext {
*
* @return this context
*/
- @Nonnull public AgentRequestContext setWrappedHttpServletRequest(@Nonnull final HttpServletRequest wrapped) {
+ @Nonnull public AgentRequestContext setRemotedHttpServletRequest(@Nonnull final RemotedHttpServletRequest wrapped) {
wrappedHttpServletRequest = wrapped;
return this;
@@ -165,7 +165,7 @@ public class AgentRequestContext extends BaseContext {
*
* @return wrapped servlet response
*/
- @Nullable public HttpServletResponse getWrappedHttpServletResponse() {
+ @Nullable public RemotedHttpServletResponse getRemotedHttpServletResponse() {
return wrappedHttpServletResponse;
}
@@ -176,7 +176,7 @@ public class AgentRequestContext extends BaseContext {
*
* @return this context
*/
- @Nonnull public AgentRequestContext setWrappedHttpServletResponse(@Nonnull final HttpServletResponse wrapped) {
+ @Nonnull public AgentRequestContext setRemotedHttpServletResponse(@Nonnull final RemotedHttpServletResponse wrapped) {
wrappedHttpServletResponse = wrapped;
return this;
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequest.java b/sp-server-api/src/main/java/net/shibboleth/sp/messaging/RemotedHttpServletRequest.java
similarity index 99%
rename from sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequest.java
rename to sp-server-api/src/main/java/net/shibboleth/sp/messaging/RemotedHttpServletRequest.java
index 8e0a92c..bc6d41c 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequest.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/messaging/RemotedHttpServletRequest.java
@@ -12,7 +12,7 @@
* limitations under the License.
*/
-package net.shibboleth.sp.messaging.impl;
+package net.shibboleth.sp.messaging;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletResponse.java b/sp-server-api/src/main/java/net/shibboleth/sp/messaging/RemotedHttpServletResponse.java
similarity index 99%
rename from sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletResponse.java
rename to sp-server-api/src/main/java/net/shibboleth/sp/messaging/RemotedHttpServletResponse.java
index 39672c0..9ad537f 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletResponse.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/messaging/RemotedHttpServletResponse.java
@@ -12,7 +12,7 @@
* limitations under the License.
*/
-package net.shibboleth.sp.messaging.impl;
+package net.shibboleth.sp.messaging;
import java.io.IOException;
import java.io.PrintWriter;
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/messaging/package-info.java b/sp-server-api/src/main/java/net/shibboleth/sp/messaging/package-info.java
new file mode 100644
index 0000000..ff9085e
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/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.
+ */
+
+/**
+ * APIs regarding messaging.
+ */
+package net.shibboleth.sp.messaging;
\ 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/AbstractAgentRequestLookupFunction.java
similarity index 77%
copy from sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/messaging/AbstractAgentRequestLookupFunction.java
copy to sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/AbstractAgentRequestLookupFunction.java
index 67001a9..dda0bfb 100644
--- 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/AbstractAgentRequestLookupFunction.java
@@ -12,16 +12,14 @@
* limitations under the License.
*/
-package net.shibboleth.sp.profile.context.navigate.messaging;
+package net.shibboleth.sp.profile.context.navigate;
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;
@@ -32,16 +30,14 @@ import net.shibboleth.sp.context.AgentRequestContext;
*
* @param <T> return type
*/
-public abstract class AbstractAgentRequestLookupFunction<T> implements Function<MessageContext,T> {
+public abstract class AbstractAgentRequestLookupFunction<T> implements Function<ProfileRequestContext,T> {
/** Lookup strategy for {@link AgentRequestContext}. */
- @Nonnull private Function<MessageContext,AgentRequestContext> agentRequestContextLookupStrategy;
+ @Nonnull private Function<ProfileRequestContext,AgentRequestContext> agentRequestContextLookupStrategy;
/** Constructor. */
- @SuppressWarnings("null")
public AbstractAgentRequestLookupFunction() {
- agentRequestContextLookupStrategy = new ChildContextLookup<>(AgentRequestContext.class).compose(
- new ParentContextLookup<>(ProfileRequestContext.class));
+ agentRequestContextLookupStrategy = new ChildContextLookup<>(AgentRequestContext.class);
}
/**
@@ -50,7 +46,7 @@ public abstract class AbstractAgentRequestLookupFunction<T> implements Function<
* @param strategy lookup strategy
*/
public void setAgentRequestContextLookupStrategy(
- @Nonnull final Function<MessageContext,AgentRequestContext> strategy) {
+ @Nonnull final Function<ProfileRequestContext,AgentRequestContext> strategy) {
agentRequestContextLookupStrategy =
Constraint.isNotNull(strategy, "AgentRequestContext lookup strategy cannot be null");
}
@@ -62,7 +58,7 @@ public abstract class AbstractAgentRequestLookupFunction<T> implements Function<
*
* @return the desired context or null
*/
- @Nullable public AgentRequestContext getAgentRequestContext(@Nullable final MessageContext messageContext) {
+ @Nullable public AgentRequestContext getAgentRequestContext(@Nullable final ProfileRequestContext messageContext) {
return agentRequestContextLookupStrategy.apply(messageContext);
}
@@ -74,7 +70,7 @@ public abstract class AbstractAgentRequestLookupFunction<T> implements Function<
*
* @return the desired context
*/
- @Nullable public AgentRequestContext ensureAgentRequestContext(@Nullable final MessageContext messageContext) {
+ @Nonnull public AgentRequestContext ensureAgentRequestContext(@Nullable final ProfileRequestContext messageContext) {
final AgentRequestContext arc = agentRequestContextLookupStrategy.apply(messageContext);
if (arc == null) {
throw new IllegalStateException("AgentRequestContext was missing");
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/IssuerLookupFunction.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/IssuerLookupFunction.java
new file mode 100644
index 0000000..d7b899e
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/IssuerLookupFunction.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.sp.Application;
+
+/**
+ * Extension of standard lookup function for deriving a message issuer value that
+ * adds SP awareness of the requesting {@link Application}.
+ */
+public class IssuerLookupFunction extends AbstractAgentRequestLookupFunction<String> {
+
+ /** Called before applying logic within this class. */
+ @Nonnull private final Function<ProfileRequestContext,String> primarySource;
+
+ /** Constructor. */
+ public IssuerLookupFunction() {
+ primarySource = new net.shibboleth.profile.context.navigate.IssuerLookupFunction();
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nullable final ProfileRequestContext profileRequestContext) {
+ final String issuer = primarySource.apply(profileRequestContext);
+ if (issuer != null) {
+ return issuer;
+ }
+
+ final Application app = ensureAgentRequestContext(profileRequestContext).getApplication();
+ if (app != null) {
+ return app.getIssuer(profileRequestContext);
+ }
+
+ return null;
+ }
+
+}
\ 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
index 67001a9..049cd18 100644
--- 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
@@ -74,7 +74,7 @@ public abstract class AbstractAgentRequestLookupFunction<T> implements Function<
*
* @return the desired context
*/
- @Nullable public AgentRequestContext ensureAgentRequestContext(@Nullable final MessageContext messageContext) {
+ @Nonnull public AgentRequestContext ensureAgentRequestContext(@Nullable final MessageContext messageContext) {
final AgentRequestContext arc = agentRequestContextLookupStrategy.apply(messageContext);
if (arc == null) {
throw new IllegalStateException("AgentRequestContext was missing");
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/package-info.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/package-info.java
new file mode 100644
index 0000000..d025c23
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/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.
+ */
+
+/**
+ * SP context navigation/lookup functions.
+ */
+package net.shibboleth.sp.profile.context.navigate;
\ No newline at end of file
diff --git a/sp-server-impl/pom.xml b/sp-server-impl/pom.xml
index 2163cd7..9b12b47 100644
--- a/sp-server-impl/pom.xml
+++ b/sp-server-impl/pom.xml
@@ -86,6 +86,11 @@
<artifactId>opensaml-profile-api</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-profile-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-saml-api</artifactId>
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 1e59d6d..38bd005 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
@@ -133,7 +133,7 @@ public class BasicApplication extends DefaultRelyingPartyConfigurationResolver i
}
/** {@inheritDoc} */
- @Nullable @NotEmpty public String getAuthenticatingAuthority(@Nonnull ProfileRequestContext profileRequestContext) {
+ @Nullable @NotEmpty public String getAuthenticatingAuthority(@Nullable ProfileRequestContext profileRequestContext) {
return authenticatingAuthorityLookupStrategy.apply(profileRequestContext);
}
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestResponseContext.java b/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestResponseContext.java
new file mode 100644
index 0000000..875df96
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestResponseContext.java
@@ -0,0 +1,80 @@
+/*
+ * 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 net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
+
+/**
+ * Class which holds and makes available the indirected HTTP servlet request and response via ThreadLocal storage.
+ *
+ * <p>Unlike the IdP-which uses a filter to stash the actual container-supplied interfaces, these objects
+ * are backed by the DDF objects in an SP agent request/response. It also allows them to be individually null.</p>
+ */
+public final class RemotedHttpServletRequestResponseContext {
+
+ /** ThreadLocal storage for request. */
+ @Nonnull private static final ThreadLocal<RemotedHttpServletRequest> currentRequest = new ThreadLocal<>();
+
+ /** ThreadLocal storage for response. */
+ @Nonnull private static final ThreadLocal<RemotedHttpServletResponse> currentResponse = new ThreadLocal<>();
+
+ /** Constructor. */
+ private RemotedHttpServletRequestResponseContext() {
+ };
+
+ /**
+ * Load the thread-local storage with the current request and response.
+ *
+ * @param request the current {@link RemotedHttpServletResponse}
+ * @param response the current {@link RemotedHttpServletResponse}
+ */
+ public static void loadCurrent(@Nullable final RemotedHttpServletRequest request,
+ @Nullable final RemotedHttpServletResponse response) {
+
+ currentRequest.set(request);
+ currentResponse.set(response);
+ }
+
+ /**
+ * Clear the current thread-local context instances.
+ */
+ public static void clearCurrent() {
+ currentRequest.remove();
+ currentResponse.remove();
+ }
+
+ /**
+ * Get the current {@link RemotedHttpServletRequest} being serviced by the current thread.
+ *
+ * @return the current request
+ */
+ @Nullable public static RemotedHttpServletRequest getRequest() {
+ return currentRequest.get();
+ }
+
+ /**
+ * Get the current {@link RemotedHttpServletResponse} being serviced by the current thread.
+ *
+ * @return the current response
+ */
+ @Nullable public static RemotedHttpServletResponse getResponse() {
+ return currentResponse.get();
+ }
+
+}
\ 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
index 3fd579d..b57940d 100644
--- 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
@@ -15,44 +15,29 @@
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;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
/**
- * 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.
+ * An implementation of {@link NonnullSupplier} which looks up the current thread-local
+ * servlet request obtained from {@link RemotedHttpServletRequestResponseContext}, raising an
+ * {@link IllegalStateException} if null.
*/
@Prototype
@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;
- }
+public class RemotedHttpServletRequestSupplier implements NonnullSupplier<HttpServletRequest> {
/** {@inheritDoc} */
@Nonnull public HttpServletRequest get() {
- final AgentRequestContext agentRequestContext = profileRequestContext != null
- ? profileRequestContext.ensureSubcontext(AgentRequestContext.class)
- : null;
- final HttpServletRequest req =
- agentRequestContext != null ? agentRequestContext.getWrappedHttpServletRequest() : null;
+ final RemotedHttpServletRequest req = RemotedHttpServletRequestResponseContext.getRequest();
if (req == null) {
- throw new IllegalStateException("HttpServletRequest not available in context tree");
+ throw new IllegalStateException(
+ "Wrapped HttpServletRequest has not been loaded via RemotedHttpServletRequestResponseContext");
}
return req;
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
index 309cea2..7db271d 100644
--- 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
@@ -15,47 +15,31 @@
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;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
/**
- * 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.
+ * An implementation of {@link NonnullSupplier} which looks up the current thread-local
+ * servlet response obtained from {@link RemotedHttpServletRequestResponseContext}, raising an
+ * {@link IllegalStateException} if null.
*/
@Prototype
@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;
- }
+public class RemotedlHttpServletResponseSupplier implements NonnullSupplier<HttpServletResponse> {
/** {@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");
+ final RemotedHttpServletResponse response = RemotedHttpServletRequestResponseContext.getResponse();
+ if (response == null) {
+ throw new IllegalStateException(
+ "Wrapped HttpServletResponse has not been loaded via RemotedHttpServletRequestResponseContext");
}
- return req;
+ return response;
}
}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java
index b178687..99e645d 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java
@@ -28,14 +28,14 @@ import jakarta.servlet.http.HttpServletRequest;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.impl.RemotedHttpServletRequest;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.profile.AbstractAgentRequestAction;
/**
* A profile action to decode an agent request and stash the resulting {@link DDF} in the
* {@link AgentRequestContext}.
*
- * <p>The MIME type in the request is used to deermine what form of parsing and deserialization
+ * <p>The MIME type in the request is used to determine what form of parsing and deserialization
* to use. The only supported type at present is "text/plain" representing a record-oriented
* syntax.</p>
*
@@ -84,7 +84,7 @@ public class DecodeAgentRequest extends AbstractAgentRequestAction {
final DDF httpreq = msg.getmember("http");
if (httpreq.isstruct()) {
log.debug("{} Wrapping tunnelled HTTP request", getLogPrefix());
- agentContext.setWrappedHttpServletRequest(new RemotedHttpServletRequest(httpreq));
+ agentContext.setRemotedHttpServletRequest(new RemotedHttpServletRequest(httpreq));
}
}
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/EncodeMessage.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/EncodeMessage.java
new file mode 100644
index 0000000..ad60f7a
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/EncodeMessage.java
@@ -0,0 +1,72 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import org.opensaml.messaging.encoder.MessageEncoder;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
+import net.shibboleth.sp.messaging.impl.RemotedHttpServletRequestResponseContext;
+
+/**
+ * Subclass of OpenSAML {@link EncodeMessage} action that wraps the execute step to
+ * stash off the {@link RemotedHttpServletResponse} from the {@link AgentRequestContext}
+ * for access by the {@link MessageEncoder}.
+ *
+ * <p>A flag controls whether to create the output objects.</p>
+ */
+public class EncodeMessage extends org.opensaml.profile.action.impl.EncodeMessage {
+
+ /** Whether to create the output objects into which the message will be encoded. */
+ private boolean createOutputObjects;
+
+ /**
+ * Sets whether to create the output {@link DDF} and {@link RemotedHttpServletResponse}.
+ *
+ * <p>Defaults to false.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setCreateOutputObjects(final boolean flag) {
+ checkSetterPreconditions();
+
+ createOutputObjects = flag;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final AgentRequestContext agentRequestContext =
+ profileRequestContext.ensureSubcontext(AgentRequestContext.class);
+ if (createOutputObjects) {
+ final DDF output = new DDF(null);
+ agentRequestContext.setOutput(output);
+ agentRequestContext.setRemotedHttpServletResponse(new RemotedHttpServletResponse(output));
+ }
+
+ try {
+ RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
+ agentRequestContext.getRemotedHttpServletResponse());
+ super.doExecute(profileRequestContext);
+ } finally {
+ RemotedHttpServletRequestResponseContext.clearCurrent();
+ }
+ }
+
+}
\ 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 1abfb71..878fd38 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
@@ -29,6 +29,7 @@ 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.authn.AuthnEventIds;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.shared.logic.Constraint;
@@ -40,11 +41,15 @@ import net.shibboleth.shared.logic.Constraint;
* <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>
+ * <p>In rare cases (mainly SAML ECP), it is not strictly required that the ID be known in advance,
+ * so this is confgurable to allow session initiator flows to indicate via wiring how to handle
+ * this case. If required but absent, the action will return the {@link AuthnEventIds#RESELECT_FLOW}
+ * event.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
* @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
+ * @event {@link AuthnEventIds#RESELECT_FLOW}
*/
public class InitializeRelyingPartyContext extends AbstractApplicationAction {
@@ -54,12 +59,16 @@ public class InitializeRelyingPartyContext extends AbstractApplicationAction {
/** Creation strategy for {@link RelyingPartyContext}. */
@Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextCreationStrategy;
+ /** Whether to require a relying party ID to exist. */
+ private boolean requireRelyingPartyId;
+
/** The relying party name to base the inbound context on. */
@Nullable private String relyingPartyId;
/** Constructor. */
public InitializeRelyingPartyContext() {
relyingPartyContextCreationStrategy = new ChildContextLookup<>(RelyingPartyContext.class, true);
+ requireRelyingPartyId = true;
}
/**
@@ -76,6 +85,19 @@ public class InitializeRelyingPartyContext extends AbstractApplicationAction {
Constraint.isNotNull(strategy, "RelyingPartyContext creation strategy cannot be null");
}
+ /**
+ * Sets whether to require that a relying party ID be available.
+ *
+ * <p>Defaults to true.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setRequireRelyingPartyId(final boolean flag) {
+ checkSetterPreconditions();
+
+ requireRelyingPartyId = flag;
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -91,10 +113,6 @@ public class InitializeRelyingPartyContext extends AbstractApplicationAction {
getLogPrefix());
relyingPartyId = ensureApplication().getAuthenticatingAuthority(profileRequestContext);
}
-
- if (relyingPartyId == null) {
- log.debug("{} No authority identified", getLogPrefix());
- }
return true;
}
@@ -102,6 +120,12 @@ public class InitializeRelyingPartyContext extends AbstractApplicationAction {
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (relyingPartyId == null && requireRelyingPartyId) {
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.RESELECT_FLOW);
+ log.error("{} Authenticating authority unknown", getLogPrefix());
+ return;
+ }
+
final RelyingPartyContext rpContext = relyingPartyContextCreationStrategy.apply(profileRequestContext);
if (rpContext == null) {
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
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
index 52630f5..289248c 100644
--- 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
@@ -43,8 +43,9 @@ import net.shibboleth.sp.profile.AbstractApplicationAction;
* 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>
+ * of scenarios in which the authority isn't known ahead of time. This is handled specially as a verified case
+ * so that it isn't required to enable the SSO profile for the unverified relying party configuration, which would
+ * impact inbound response handling by the SP in later operations.</p>
*
* @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
* @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
@@ -118,7 +119,7 @@ public final class SelectRelyingPartyConfiguration extends AbstractApplicationAc
try {
final CriteriaSet criteria = new CriteriaSet();
- if (getRelyingPartyCtx().isVerified() || getRelyingPartyCtx().getRelyingPartyId() == null) {
+ if (getRelyingPartyCtx().isVerified()) {
criteria.add(new VerifiedProfileCriterion(true));
}
criteria.add(new ProfileRequestContextCriterion(profileRequestContext));
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestTest.java
index 3559fa9..c2d50ac 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletRequestTest.java
@@ -21,7 +21,7 @@ import java.util.List;
import jakarta.servlet.http.Cookie;
import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.impl.RemotedHttpServletRequest;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletResponseTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletResponseTest.java
index 79dc3eb..ef7884f 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletResponseTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/messaging/impl/RemotedHttpServletResponseTest.java
@@ -24,7 +24,7 @@ import java.util.Set;
import jakarta.servlet.http.Cookie;
import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.impl.RemotedHttpServletResponse;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContextTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContextTest.java
index 214fdef..f2e667c 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContextTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContextTest.java
@@ -22,6 +22,7 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -49,6 +50,8 @@ public class InitializeRelyingPartyContextTest extends BaseAgplicationActionTest
public void setUp() throws ComponentInitializationException {
super.beforeMethod();
+ prc.removeSubcontext(RelyingPartyContext.class);
+
action = new InitializeRelyingPartyContext();
action.initialize();
}
@@ -62,7 +65,18 @@ public class InitializeRelyingPartyContextTest extends BaseAgplicationActionTest
}
@Test
- public void testNoAuthority() {
+ public void testNoAuthorityWhenRequired() {
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.RESELECT_FLOW);
+ Assert.assertNull(prc.getSubcontext(RelyingPartyContext.class));
+ }
+
+ @Test
+ public void testNoAuthorityWhenNotRequired() throws ComponentInitializationException {
+ action = new InitializeRelyingPartyContext();
+ action.setRequireRelyingPartyId(false);
+ action.initialize();
+
final Event event = action.execute(src);
ActionTestingSupport.assertProceedEvent(event);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list