[java-plugin-shibd-saml] branch main updated: Draft logout consumer flows, unit testing ongoing.

Codeberg noreply at shibboleth.net
Mon May 25 16:31:52 UTC 2026


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

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

View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-saml/commit/7fc06f0b0e89b604b1ca616cd13c4f8173851e35

The following commit(s) were added to refs/heads/main by this push:
     new 7fc06f0  Draft logout consumer flows, unit testing ongoing.
7fc06f0 is described below

commit 7fc06f0b0e89b604b1ca616cd13c4f8173851e35
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Mon May 25 12:31:35 2026 -0400

    Draft logout consumer flows, unit testing ongoing.
---
 .../idp/flows/sp/consumer/saml2/saml2-flow.xml     |   1 -
 .../consumer/saml2/artifact/artifact-beans.xml     |  32 ++
 .../consumer/saml2/artifact/artifact-flow.xml      |   9 +
 .../post-simplesign/post-simplesign-beans.xml      |  23 ++
 .../saml2/post-simplesign/post-simplesign-flow.xml |   9 +
 .../sp/logout/consumer/saml2/post/post-beans.xml   |  23 ++
 .../sp/logout/consumer/saml2/post/post-flow.xml    |   9 +
 .../consumer/saml2/redirect/redirect-beans.xml     |  23 ++
 .../consumer/saml2/redirect/redirect-flow.xml      |   9 +
 .../flows/sp/logout/consumer/saml2/saml2-beans.xml | 174 ++++++++
 .../sp/{ => logout}/consumer/saml2/saml2-flow.xml  |  63 ++-
 .../flows/saml2/SAML2LogoutConsumerFlowTest.java   | 460 +++++++++++++++++++++
 .../flows/saml2/SAML2TokenConsumerFlowTest.java    |   2 +-
 .../saml2/profile/impl/ProcessLogoutRequest.java   |   6 +-
 14 files changed, 820 insertions(+), 23 deletions(-)

diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-flow.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-flow.xml
index 158d578..fc320f9 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-flow.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-flow.xml
@@ -4,7 +4,6 @@
     abstract="true" parent="sp/consumer">
 
     <action-state id="DoProfileWork">
-<!--        <evaluate expression="PopulateMetricContext" />-->
         <evaluate expression="ValidateHttpServletRequest" />
         <evaluate expression="DecodeMessage" />
 <!--        <evaluate expression="PostDecodePopulateAuditContext" />-->
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/artifact/artifact-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/artifact/artifact-beans.xml
new file mode 100644
index 0000000..0f6e430
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/artifact/artifact-beans.xml
@@ -0,0 +1,32 @@
+<?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">
+
+    <alias alias="shibboleth.HttpServletRequestValidator" name="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.Artifact" />
+
+    <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.LogoutMessage">
+        <constructor-arg>
+            <bean class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPArtifactDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:parserPool-ref="shibboleth.ParserPool"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                p:bindingDescriptor-ref="shibboleth.Binding.SAML2Artifact"
+                p:peerEntityRole="#{T(org.opensaml.saml.saml2.metadata.IDPSSODescriptor).DEFAULT_ELEMENT_NAME}"
+                p:roleDescriptorResolver-ref="shibboleth.RoleDescriptorResolver"
+                p:identifierGenerationStrategy-ref="shibboleth.DefaultIdentifierGenerationStrategy"
+                p:selfEntityIDResolver-ref="ArtifactSupport.SelfEntityIDResolver"
+                p:artifactEndpointResolver-ref="shibboleth.EndpointResolver"
+                p:SOAPClient-ref="shibboleth.SOAPClient.SAML"
+                p:SOAPPipelineName="SP.SAML2.ArtifactDecoder"
+                p:SOAPClientSecurityConfigurationProfileId="#{T(net.shibboleth.saml.saml2.profile.config.SingleLogoutProfileConfiguration).PROFILE_ID}"
+                />
+        </constructor-arg>
+    </bean>
+
+</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/artifact/artifact-flow.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/artifact/artifact-flow.xml
new file mode 100644
index 0000000..8dd243e
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/artifact/artifact-flow.xml
@@ -0,0 +1,9 @@
+<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/logout/consumer/saml2">
+
+    <!-- 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/logout/consumer/saml2/artifact/artifact-beans.xml" />
+
+</flow>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post-simplesign/post-simplesign-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post-simplesign/post-simplesign-beans.xml
new file mode 100644
index 0000000..47b3261
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post-simplesign/post-simplesign-beans.xml
@@ -0,0 +1,23 @@
+<?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">
+
+    <alias alias="shibboleth.HttpServletRequestValidator" name="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.POSTSimpleSign.RequestOrResponse" />
+
+    <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.LogoutMessage">
+        <constructor-arg>
+            <bean class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPPostSimpleSignDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:parserPool-ref="shibboleth.ParserPool"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                p:bindingDescriptor-ref="shibboleth.Binding.SAML2POSTSimpleSign" />
+        </constructor-arg>
+    </bean>
+
+</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post-simplesign/post-simplesign-flow.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post-simplesign/post-simplesign-flow.xml
new file mode 100644
index 0000000..d39f8cf
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post-simplesign/post-simplesign-flow.xml
@@ -0,0 +1,9 @@
+<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/logout/consumer/saml2">
+
+    <!-- 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/logout/consumer/saml2/post-simplesign/post-simplesign-beans.xml" />
+
+</flow>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post/post-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post/post-beans.xml
new file mode 100644
index 0000000..497da9d
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post/post-beans.xml
@@ -0,0 +1,23 @@
+<?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">
+
+    <alias alias="shibboleth.HttpServletRequestValidator" name="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.POST.RequestOrResponse" />
+
+    <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.LogoutMessage">
+        <constructor-arg>
+            <bean class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPPostDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:parserPool-ref="shibboleth.ParserPool"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                p:bindingDescriptor-ref="shibboleth.Binding.SAML2POST" />
+        </constructor-arg>
+    </bean>
+
+</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post/post-flow.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post/post-flow.xml
new file mode 100644
index 0000000..393255d
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/post/post-flow.xml
@@ -0,0 +1,9 @@
+<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/logout/consumer/saml2">
+
+    <!-- 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/logout/consumer/saml2/post/post-beans.xml" />
+
+</flow>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/redirect/redirect-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/redirect/redirect-beans.xml
new file mode 100644
index 0000000..729013a
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/redirect/redirect-beans.xml
@@ -0,0 +1,23 @@
+<?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">
+
+    <alias alias="shibboleth.HttpServletRequestValidator" name="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.Redirect.RequestOrResponse" />
+
+    <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.LogoutMessage">
+        <constructor-arg>
+            <bean class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPRedirectDeflateDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:parserPool-ref="shibboleth.ParserPool"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                p:bindingDescriptor-ref="shibboleth.Binding.SAML2Redirect" />
+        </constructor-arg>
+    </bean>
+
+</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/redirect/redirect-flow.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/redirect/redirect-flow.xml
new file mode 100644
index 0000000..483ea4f
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/redirect/redirect-flow.xml
@@ -0,0 +1,9 @@
+<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/logout/consumer/saml2">
+
+    <!-- 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/logout/consumer/saml2/redirect/redirect-beans.xml" />
+
+</flow>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-beans.xml
new file mode 100644
index 0000000..6311e9a
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-beans.xml
@@ -0,0 +1,174 @@
+<?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">
+
+    <util:constant id="shiibboleth.sp.ProfileId"
+        static-field="net.shibboleth.saml.saml2.profile.config.SingleLogoutProfileConfiguration.PROFILE_ID" />
+
+    <import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/saml2-common-beans.xml" />
+
+    <bean id="RecoverStateData"
+        class="net.shibboleth.sp.profile.impl.RecoverStateData" scope="prototype"
+        p:stateTokenLookupStrategy-ref="RelayStateLookup"
+        p:stateDataClass="net.shibboleth.sp.saml.saml2.SAMLStateData" />
+
+    <bean id="RelayStateLookup" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <bean parent="shibboleth.Functions.Expression"
+                c:_0="#input != null ? T(org.opensaml.saml.common.binding.SAMLBindingSupport).getRelayState(#input) : null" />
+        </constructor-arg>
+        <constructor-arg name="f" ref="shibboleth.MessageContextLookup.Inbound"/>
+    </bean>
+
+    <bean id="HandleNoPassive" class="net.shibboleth.sp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+            c:executionDirection="INBOUND">
+        <constructor-arg>
+            <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+                <property name="handlers">
+                    <list>
+                        <bean class="org.opensaml.saml.saml2.binding.impl.CheckNoPassiveHandler" scope="prototype" />
+                    </list>
+                </property>
+             </bean>
+        </constructor-arg>
+        <property name="errorEvent">
+            <util:constant static-field="net.shibboleth.idp.authn.AuthnEventIds.NO_PASSIVE" />
+        </property>
+    </bean>
+
+    <bean id="CheckDestinationAndIssuerHandler" class="net.shibboleth.sp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+            c:executionDirection="INBOUND">
+        <constructor-arg>
+            <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+                <property name="handlers">
+                    <list>
+                        <bean class="net.shibboleth.sp.saml.saml2.messaging.impl.CheckDestinationAndIssuerHandler" scope="prototype"
+                            p:checkDuringInit="false"
+                            p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier" />
+                    </list>
+                </property>
+             </bean>
+        </constructor-arg>
+        <property name="errorEvent">
+            <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MESSAGE" />
+        </property>
+    </bean>
+
+    <bean id="InboundEntityIDLookup" parent="shibboleth.Functions.Compose">
+        <constructor-arg name="g">
+            <bean class="org.opensaml.saml.common.messaging.context.navigate.SAMLEntityIDFunction" />
+        </constructor-arg>
+        <constructor-arg name="f">
+            <bean class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+                  c:type="#{ T(org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext) }" />
+        </constructor-arg>
+    </bean>
+
+    <bean id="NotIgnoreRequestSignaturesPredicate" parent="shibboleth.Conditions.NOT">
+        <constructor-arg>
+            <bean class="net.shibboleth.saml.saml2.profile.config.logic.IgnoreRequestSignaturesPredicate" />
+        </constructor-arg>
+    </bean>
+
+    <bean id="HandleMessageEarly" class="net.shibboleth.sp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+            c:executionDirection="INBOUND">
+        <constructor-arg>
+            <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+                <property name="handlers">
+                    <list>
+                        <bean class="org.opensaml.messaging.handler.impl.FunctionMessageHandler" scope="prototype">
+                            <property name="functionLookupStrategy">
+                                <bean class="net.shibboleth.saml.profile.config.navigate.messaging.MessageHandlerLookupFunction" />
+                            </property>
+                        </bean>
+                        <bean class="org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler" scope="prototype"
+                            p:checkDuringInit="false"
+                            p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier" />
+                        <bean class="org.opensaml.saml.common.binding.security.impl.MessageReplaySecurityHandler" scope="prototype"
+                            p:replayCache-ref="shibboleth.ReplayCache"
+                            p:expires="%{sp.policy.messageLifetime:PT3M}" />
+                        <bean class="org.opensaml.saml.common.binding.security.impl.MessageLifetimeSecurityHandler" scope="prototype"
+                            p:messageLifetime="%{sp.policy.messageLifetime:PT3M}"
+                            p:clockSkew="%{sp.policy.clockSkew:PT3M}" />
+                    </list>
+                </property>
+             </bean>
+        </constructor-arg>
+        <property name="errorEvent">
+            <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MESSAGE" />
+        </property>
+    </bean>
+
+    <bean id="CheckSignature" class="net.shibboleth.sp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+            c:executionDirection="INBOUND"
+            p:activationCondition-ref="NotIgnoreRequestSignaturesPredicate">
+        <constructor-arg>
+            <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+                <property name="handlers">
+                    <list>
+                        <bean class="org.opensaml.saml.saml2.binding.security.impl.SAML2HTTPRedirectDeflateSignatureSecurityHandler" scope="prototype"
+                            p:checkDuringInit="false"
+                            p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier" />
+                        <bean class="org.opensaml.saml.common.binding.security.impl.SAMLProtocolMessageXMLSignatureSecurityHandler" scope="prototype" />
+                        <bean class="org.opensaml.saml.saml2.binding.security.impl.SAML2HTTPPostSimpleSignSecurityHandler" scope="prototype"
+                            p:checkDuringInit="false"
+                            p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                            p:parser-ref="shibboleth.ParserPool"
+                            p:keyInfoResolver-ref="shibboleth.KeyInfoCredentialResolver" />
+                    </list>
+                </property>
+             </bean>
+        </constructor-arg>
+        <property name="errorEvent">
+            <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MESSAGE" />
+        </property>
+    </bean>
+
+    <bean id="HandleMessageLate" class="net.shibboleth.sp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+            c:executionDirection="INBOUND">
+        <constructor-arg>
+            <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+                <property name="handlers">
+                    <list>
+                        <bean class="org.opensaml.messaging.handler.impl.CheckMandatoryIssuer" scope="prototype"
+                            p:issuerLookupStrategy-ref="InboundEntityIDLookup" />
+                        <bean class="org.opensaml.messaging.handler.impl.CheckMandatoryAuthentication" scope="prototype"
+                                p:activationCondition="%{idp.logout.authenticated:true}">
+                            <property name="authenticationLookupStrategy">
+                                <bean class="org.opensaml.saml.common.messaging.context.navigate.SAMLMessageContextAuthenticationFunction" />
+                            </property>
+                        </bean>
+                    </list>
+                </property>
+             </bean>
+        </constructor-arg>
+        <property name="errorEvent">
+            <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MESSAGE" />
+        </property>
+    </bean>
+
+    <bean id="PopulateDecryptionParameters"
+        class="org.opensaml.profile.action.impl.PopulateDecryptionParameters" scope="prototype"
+        p:recipientLookupStrategy-ref="shibboleth.IssuerLookup.Simple"
+        p:configurationLookupStrategy-ref="shibboleth.DecryptionConfigurationLookup"
+        p:decryptionParametersResolver-ref="shibboleth.DecryptionParametersResolver" />
+
+    <bean id="DecryptNameIDs" class="org.opensaml.saml.saml2.profile.impl.DecryptNameIDs" scope="prototype" />
+    
+    <bean id="PassthroughStateManager"
+        class="net.shibboleth.sp.state.impl.PassthroughStateManager"
+        p:dataSealer-ref="shibboleth.DataSealer"
+        p:objectMapper-ref="shibboleth.JSONObjectMapper"
+        p:expiration="PT30M" />
+    
+    <bean id="ProcessLogoutRequest"
+        class="net.shibboleth.sp.saml.saml2.profile.impl.ProcessLogoutRequest" scope="prototype"
+        p:parserPool-ref="shibboleth.ParserPool"
+        p:stateManager-ref="PassthroughStateManager" />
+    
+</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-flow.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-flow.xml
similarity index 50%
copy from sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-flow.xml
copy to sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-flow.xml
index 158d578..5561eac 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-flow.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-flow.xml
@@ -1,18 +1,21 @@
 <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" parent="sp/consumer">
+    abstract="true" parent="sp/logout/consumer">
 
-    <action-state id="DoProfileWork">
-<!--        <evaluate expression="PopulateMetricContext" />-->
+    <decision-state id="CheckForToken">
+        <if test="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getInput().getmember('http').isstruct()"
+            then="DoMessage"
+            else="IssueResponse" />
+    </decision-state>
+
+    <!-- This branch consumes a SAML message. -->
+    <action-state id="DoMessage">
         <evaluate expression="ValidateHttpServletRequest" />
         <evaluate expression="DecodeMessage" />
 <!--        <evaluate expression="PostDecodePopulateAuditContext" />-->
-
-        <evaluate expression="RecoverStateData" />
         
         <evaluate expression="CheckMessageVersion" />
-        <evaluate expression="HandleNoPassive" />
         
         <evaluate expression="SAMLProtocolAndRole" />
         <evaluate expression="CheckDestinationAndIssuerHandler" />
@@ -25,21 +28,45 @@
 
         <evaluate expression="PopulateSignatureValidationParameters" />
         <evaluate expression="PopulateClientTLSValidationParameters" />
-        <evaluate expression="PopulateInboundMessageContextWithSAMLSelf" />
         
-        <evaluate expression="HandleResponse" />
+        <evaluate expression="HandleMessageEarly" />
+        <evaluate expression="CheckSignature" />
+        <evaluate expression="HandleMessageLate" />
+<!--        <evaluate expression="PostResponsePopulateAuditContext" />-->        
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="PreProcessLogoutMessage" />
+    </action-state>
+    
+    <!-- Dispatch by message type. -->
+    <decision-state id="PreProcessLogoutMessage">
+        <if test="opensamlProfileRequestContext.ensureInboundMessageContext().getMessage() instanceof T(org.opensaml.saml.saml2.core.LogoutRequest)"
+            then="DoLogoutRequest"
+            else="DoLogoutResponse"/>
+    </decision-state>
+    
+    <!-- For a LogoutRequest, we process against the "session.opaque" input and produce a "token" for the Agent. -->
+    <action-state id="DoLogoutRequest">
         <evaluate expression="PopulateDecryptionParameters" />
-        <evaluate expression="DecryptAssertions" />
-        <evaluate expression="ValidateAssertions" />
-        <evaluate expression="ProcessAssertionsForAuthentication" />
         <evaluate expression="DecryptNameIDs" />
-        <evaluate expression="DecryptAttributes" />
-        <evaluate expression="ExtractSAMLAttributes" />
-        <evaluate expression="RecoverPostData" />
-        <evaluate expression="PrepareAgentResponse" />
-<!--        <evaluate expression="PostAssertionPopulateAuditContext" />-->
-<!--        <evaluate expression="PostResponsePopulateAuditContext" />-->
+        <evaluate expression="ProcessLogoutRequest" />
+        <evaluate expression="'proceed'" />
         
+        <transition on="proceed" to="proceed" />
+    </action-state>
+
+    <!-- For a LogoutResponse, we just extract some info for the Agent, simplest case by far. -->
+    <action-state id="DoLogoutResponse">
+        <!-- TODO -->
+        <evaluate expression="RecoverStateData" />
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="proceed" />
+    </action-state>
+    
+    <!-- This branch uses the "token" from the Agent to generate a LogoutResponse outbound. -->
+    <action-state id="IssueResponse">
+        <!-- TODO -->
         <evaluate expression="'proceed'" />
         
         <transition on="proceed" to="proceed" />
@@ -52,6 +79,6 @@
     </global-transitions>
     
     <!-- 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/consumer/saml2/saml2-beans.xml" />
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/logout/consumer/saml2/saml2-beans.xml" />
 
 </flow>
diff --git a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerFlowTest.java b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerFlowTest.java
new file mode 100644
index 0000000..dee9bee
--- /dev/null
+++ b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2LogoutConsumerFlowTest.java
@@ -0,0 +1,460 @@
+/*
+ * 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.flows.saml2;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.SAMLObject;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.LogoutRequest;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.NameIDType;
+import org.opensaml.saml.saml2.testing.SAML2ActionTestingSupport;
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+import org.opensaml.xmlsec.signature.SignableXMLObject;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.opensaml.xmlsec.signature.support.SignatureException;
+import org.opensaml.xmlsec.signature.support.SignatureSupport;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
+import net.shibboleth.idp.test.PreferFileSystemApplicationContextInitializer;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.net.CookieManager;
+import net.shibboleth.shared.net.URISupport;
+import net.shibboleth.shared.net.CookieManager.SameSiteValue;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceableComponent;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.AgentCriterion;
+import net.shibboleth.sp.AgentResolver;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.flows.AbstractSPFlowTest;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.profile.ConsumerConstants;
+import net.shibboleth.sp.saml.saml2.SAMLStateData;
+import net.shibboleth.sp.saml.saml2.profile.impl.PrepareAgentResponse;
+import net.shibboleth.sp.saml.saml2.profile.impl.ProcessLogoutRequest;
+import net.shibboleth.sp.state.StateData;
+import net.shibboleth.sp.state.impl.CookieStateManager;
+import net.shibboleth.sp.state.impl.PassthroughStateManager;
+
+/**
+ * Unit test for the SP session-initiator flow.
+ */
+ at ContextConfiguration(
+        locations = {
+                "classpath:/net/shibboleth/sp/saml-test-beans.xml",
+                },
+        initializers = {
+                TestSPSAMLEnvironmentApplicationContextInitializer.class,
+                PreferFileSystemApplicationContextInitializer.class,
+                IdPPropertiesApplicationContextInitializer.class
+                },
+        inheritInitializers = false
+        )
+ at WebAppConfiguration
+public class SAML2LogoutConsumerFlowTest extends AbstractSPFlowTest {
+    
+    /** Flow ID. */
+    @Nonnull public static final String FLOW_ID = "sp/logout-consumer";
+
+    /** Issuer. */
+    @Nonnull public static final String ISSUER = "https://idp.example.org";
+
+    /** Audience. */
+    @Nonnull public static final String AUDIENCE = "https://testsp.example.org";
+
+    /** ACS URL. */
+    @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/SAML2/SLO/POST";
+    
+    @Autowired
+    @Qualifier("shibboleth.DataSealer")
+    protected DataSealer dataSealer;
+    
+    @Autowired
+    @Qualifier("shibboleth.sp.AgentResolver")
+    protected ReloadableService<AgentResolver> agentResolver;
+    
+    @Autowired
+    @Qualifier("shibboleth.SessionIDGenerator")
+    protected IdentifierGenerationStrategy idGenerator;
+    
+    /** Dummy signing key. */
+    @Autowired
+    @Qualifier("dummy.idp.Credential")
+    protected Credential idpCredential;
+
+    // Used to create state cookies for subsequent inclusion in mock requests to flow.
+    
+    private CookieManager cookieManager;
+    private CookieStateManager stateManager;
+    // Renamed to avoid stomping on base class objects.
+    private MockHttpServletRequest request2;
+    private MockHttpServletResponse response2;
+    
+    // Used to recover "token" produced by flow.
+    private PassthroughStateManager dummyStateManager;
+    
+    /** Constructor. */
+    public SAML2LogoutConsumerFlowTest() {
+        super(FLOW_ID);
+    }
+    
+    /**
+     * Set up state manager.
+     * 
+     * @throws ComponentInitializationException
+     */
+    @BeforeClass
+    public void beforeClass() throws ComponentInitializationException {
+        cookieManager = new CookieManager();
+        cookieManager.setCookiePath("/");
+        cookieManager.setSameSite(SameSiteValue.None);
+        cookieManager.setCookieLimit(10);
+        cookieManager.setHttpServletRequestSupplier(new NonnullSupplier<HttpServletRequest>() {
+            @Nonnull public HttpServletRequest get() {
+                assert request2 != null;
+                return request2;
+            }
+        });
+        cookieManager.setHttpServletResponseSupplier(new NonnullSupplier<HttpServletResponse>() {
+            @Nonnull public HttpServletResponse get() {
+                assert response2 != null;
+                return response2;
+            }
+        });
+        cookieManager.initialize();
+        
+        stateManager = new CookieStateManager();
+        stateManager.setId("test");
+        stateManager.setCookiePrefix("__Host-shibsp_state");
+
+        final ObjectMapper mapper = new ObjectMapper();
+        mapper.registerModule(new JavaTimeModule());
+        stateManager.setObjectMapper(mapper);
+        
+        stateManager.setHttpServletRequestSupplier(new NonnullSupplier<HttpServletRequest>() {
+            @Nonnull public HttpServletRequest get() {
+                assert request2 != null;
+                return request2;
+            }
+        });
+        
+        stateManager.setCookieManager(cookieManager);
+        stateManager.initialize();
+        
+        dummyStateManager = new PassthroughStateManager();
+        dummyStateManager.setId("test");
+        dummyStateManager.setObjectMapper(mapper);
+        dummyStateManager.setDataSealer(dataSealer);
+        dummyStateManager.initialize();
+    }
+    
+    /**
+     * Tear down state manager.
+     */
+    @AfterClass
+    public void tearDown() {
+        stateManager.destroy();
+        cookieManager.destroy();
+    }
+    
+    /** Pre-test work. */
+    @BeforeMethod
+    public void beforeMethod() {
+        setDefaultAuth();
+    }
+
+    /**
+     * Test flow without proper input.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testNoInput() throws IOException {
+        setApplicationRequest(APPLICATION_ID, null);
+
+        validateError(AuthnEventIds.NO_POTENTIAL_FLOW);
+    }
+    
+    /**
+     * Test flow with unverified (no metadata) IdP specified.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testUnverified() throws IOException {
+        final DDF input = buildRemotedPOSTMessage(buildLogoutRequest(ISSUER + "/bad"), null, null);
+        setApplicationRequest(APPLICATION_ID, input);
+
+        validateError(IdPEventIds.INVALID_PROFILE_CONFIG);
+    }
+        
+    /**
+     * Test successful flow.
+     * 
+     * @throws Exception 
+     */
+    @Test
+    public void testSuccess() throws Exception {
+        final LogoutRequest request = buildLogoutRequest(ISSUER);
+        sign(request);
+        final DDF input = buildRemotedPOSTMessage(request, null, null);
+        input.addmember(ConsumerConstants.SESSION_OPAQUE).addmember(PrepareAgentResponse.NAMEID_PARAM).string(
+                "<NameID xmlns='urn:oasis:names:tc:SAML:2.0:assertion' Format='"
+                        + NameIDType.EMAIL + "' SPProvidedID='" + ISSUER + "'>jdoe at example.org</NameID>");
+        setApplicationRequest(APPLICATION_ID, input);        
+        
+        validateLogoutRequestResult(true);
+    }
+
+    /**
+     * Examine a flow response to a valid LogoutRequest for accuracy.
+     * 
+     * @param matched whether the result should indicate a session match
+     * 
+     * @return the output object
+     * @throws ResolverException 
+     * @throws IOException 
+     */
+    @Nonnull private DDF validateLogoutRequestResult(final boolean matched) throws ResolverException, IOException {
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        final DDF output = assertOutputMessageSuccess(result);
+        assert output != null;
+        System.out.println("testSuccess output: " + output.toString());
+        
+        Assert.assertTrue(output.isstruct());
+        
+        final DDF http = output.getmember(RemotedHttpServletRequest.STRUCTURE_NAME);
+        Assert.assertTrue(http.isnull());
+
+        Assert.assertEquals(output.getmember(ProcessLogoutRequest.MATCHED_PARAM).integer() == 1, matched);
+        
+        final String token = output.getmember(ProcessLogoutRequest.TOKEN_PARAM).string();
+        assert token != null;
+
+        final SAMLStateData stateData;
+        try (final ServiceableComponent<AgentResolver> resolver = agentResolver.getServiceableComponent()) {
+            final Agent agent = resolver.getComponent().resolveSingle(
+                    new CriteriaSet(new AgentCriterion(AGENT_ID)));
+            assert agent != null;
+            final Application app = agent.getApplication(APPLICATION_ID);
+            assert app != null;
+            stateData = dummyStateManager.recoverFromStateToken(agent, app, token, SAMLStateData.class);
+        }
+        
+        assert stateData != null;
+        
+        final ProfileRequestContext prc = retrieveProfileRequestContext(result);
+        assert prc != null;
+        
+        final LogoutRequest request = (LogoutRequest) prc.ensureInboundMessageContext().getMessage();
+        assert request != null;
+        final Issuer issuer = request.getIssuer();
+        assert issuer != null;
+        Assert.assertEquals(stateData.getAuthenticationAuthority(), issuer.getValue());
+        Assert.assertEquals(stateData.getRequestID(), request.getID());
+        
+        return output;
+    }
+    
+    /**
+     * Run the flow and verify an event is signalled.
+     * 
+     * @param event event to check for
+     */
+    private void validateError(@Nonnull final String event) {
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        assertOutputMessageEvent(result, event);
+    }
+    
+    /**
+     * Builds a SAML logout request with some tailored data.
+     * 
+     * @param issuer issuer value
+     * 
+     * @return input message suitable for logout consumer flow
+     */
+    @Nonnull private LogoutRequest buildLogoutRequest(@Nonnull final String issuer) {
+        
+        final NameID nameID = SAML2ActionTestingSupport.buildNameID("jdoe at example.org");
+        assert nameID != null;
+        nameID.setFormat(NameIDType.EMAIL);
+
+        final LogoutRequest request = SAML2ActionTestingSupport.buildLogoutRequest(nameID);
+        request.setID(idGenerator.generateIdentifier());
+        request.setIssueInstant(Instant.now());
+        request.setDestination(RESPONSE_URL);
+        request.setIssuer(SAML2ActionTestingSupport.buildIssuer(issuer));
+        
+        return request;
+    }
+    
+    /**
+     * Encodes a SAML message into a form POST embedded in a remoted message.
+     * 
+     * @param message SAML response to encode
+     * @param relayState relay state if any
+     * @param stateCookie the state cookie to attack to the input message if any
+     * 
+     * @return input object suitable for token consumer flow
+     * 
+     * @throws IOException on error 
+     */
+    @Nonnull private DDF buildRemotedPOSTMessage(@Nonnull final SAMLObject message, @Nullable final String relayState,
+            @Nullable Cookie stateCookie) throws IOException {
+                
+        try (final ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
+            XMLObjectSupport.marshallToOutputStream(message, sink);
+            final String base64 = Base64Support.encode(sink.toByteArray(), true);
+            final DDF obj = new DDF(null).structure();
+            final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+            
+            http.addmember(RemotedHttpServletRequest.METHOD).string("POST");
+            http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
+            http.addmember(RemotedHttpServletRequest.CONTENT_TYPE).string("application/x-www-form-urlencoded");
+            http.addmember(RemotedHttpServletRequest.REQUEST_URL).unsafe_string(RESPONSE_URL.getBytes(StandardCharsets.UTF_8));
+            
+            if (stateCookie != null) {
+                http.addmember("headers").structure().addmember("Cookie")
+                    .unsafe_string(new String(stateCookie.getName() + '=' + stateCookie.getValue()).getBytes(StandardCharsets.UTF_8));
+            }
+            
+            final String paramName = message instanceof LogoutRequest ? "SAMLRequest" : "SAMLResponse";
+            
+            final List<Pair<String,String>> params;
+            if (relayState != null) {
+                params = CollectionSupport.listOf(new Pair<>(paramName, base64),
+                        new Pair<>("RelayState", relayState));
+            } else {
+                params = CollectionSupport.singletonList(new Pair<>(paramName, base64));
+            }
+            
+            http.addmember(RemotedHttpServletRequest.BODY).unsafe_string(
+                    Constraint.isNotNull(URISupport.buildQuery(params), "Query string is null").getBytes(StandardCharsets.UTF_8));
+            
+            return obj;
+        } catch (final MarshallingException | EncodingException e) {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Sign object.
+     * 
+     * @param signable object to sign
+     * 
+     * @throws IOException 
+     */
+    public void sign(@Nonnull final SignableXMLObject signable) throws IOException {
+
+        final SignatureSigningParameters signingParameters = new SignatureSigningParameters();
+        signingParameters.setSigningCredential(idpCredential);
+        signingParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
+        signingParameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
+
+        try {
+            SignatureSupport.signObject(signable, signingParameters);
+        } catch (final SecurityException | MarshallingException | SignatureException e) {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Create state object.
+     * 
+     * @param requestID message ID to include
+     * 
+     * @return state object
+     */
+    @Nonnull private SAMLStateData buildStateData(@Nonnull final String requestID) {
+        final SAMLStateData data = new SAMLStateData();
+        data.setRequestID(requestID);
+        data.setAuthenticationAuthority(ISSUER);
+        data.setResponseLocation(RESPONSE_URL);
+        return data;
+    }
+
+    /**
+     * Generate a state token for the supplied data.
+     * 
+     * @param data state to preserve
+     * 
+     * @return the token to use as RelayState
+     * 
+     * @throws ResolverException
+     * @throws IOException
+     */
+    @Nonnull private String getStateToken(@Nonnull final StateData data) throws ResolverException, IOException {
+        try (final ServiceableComponent<AgentResolver> resolver = agentResolver.getServiceableComponent()) {
+            final Agent agent = resolver.getComponent().resolveSingle(
+                    new CriteriaSet(new AgentCriterion(AGENT_ID)));
+            assert agent != null;
+            final Application app = agent.getApplication(APPLICATION_ID);
+            assert app != null;
+            request2 = new MockHttpServletRequest();
+            response2 = new MockHttpServletResponse();
+            return stateManager.preserveToStateToken(agent, app, data);
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java
index b493276..7b0d688 100644
--- a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java
+++ b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java
@@ -139,7 +139,7 @@ public class SAML2TokenConsumerFlowTest extends AbstractSPFlowTest {
     /** ACS URL. */
     @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/SAML2/POST";
 
-    /** ACS URL. */
+    /** Target resource. */
     @Nonnull public static final String RESOURCE_URL = "https://sp.example.org/secure";
 
     @Autowired
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java
index b426951..ead2a3b 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ProcessLogoutRequest.java
@@ -305,11 +305,11 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
             if (!SAML2ObjectSupport.areNameIDsEquivalent(sessionNameID, requestedNameID,
                     assertingPartyLookupStrategy.apply(profileRequestContext),
                     relyingPartyLookupStrategy.apply(profileRequestContext))) {
-                log.warn("{} LogoutRequest's NameID did not strongly match session", getLogPrefix());
+                log.info("{} LogoutRequest's NameID did not strongly match session", getLogPrefix());
                 return false;
             }
         } else if (!SAML2ObjectSupport.areNameIDsEquivalent(sessionNameID, requestedNameID)) {
-            log.warn("{} LogoutRequest's NameID did not strongly match session", getLogPrefix());
+            log.info("{} LogoutRequest's NameID did not strongly match session", getLogPrefix());
             return false;
         }
         
@@ -326,7 +326,7 @@ public class ProcessLogoutRequest extends AbstractApplicationAction {
             }
         }
         
-        log.warn("{} LogoutRequest SessionIndexes did not match session's recorded index ({})", getLogPrefix(),
+        log.info("{} LogoutRequest SessionIndexes did not match session's recorded index ({})", getLogPrefix(),
                 sessionIndex);
         return false;
     }

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


More information about the commits mailing list