[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