[java-plugin-shibd-saml] branch main updated: Lightly tested ECP consumer flow.
Codeberg
noreply at shibboleth.net
Mon Aug 10 19:01:02 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/ae7f020818a0ba54a37568f711ef6ad029a6a550
The following commit(s) were added to refs/heads/main by this push:
new ae7f020 Lightly tested ECP consumer flow.
ae7f020 is described below
commit ae7f020818a0ba54a37568f711ef6ad029a6a550
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Mon Aug 10 15:00:43 2026 -0400
Lightly tested ECP consumer flow.
---
sp-saml-conf-impl/pom.xml | 10 ++
.../META-INF/net.shibboleth.idp/postconfig.xml | 20 +++
.../idp/flows/sp/consumer/saml2/ecp/ecp-beans.xml | 23 +++
.../idp/flows/sp/consumer/saml2/ecp/ecp-flow.xml | 9 ++
.../idp/flows/sp/initiator/ecp/ecp-beans.xml | 11 --
.../shibboleth/idp/flows/sp/saml2-common-beans.xml | 13 +-
.../net/shibboleth/sp/service/agent/postconfig.xml | 2 +-
.../flows/saml2/SAML2TokenConsumerFlowTest.java | 112 +++++++++++++-
.../binding/decoding/impl/HTTPECPDecoder.java | 165 +++++++++++++++++++++
9 files changed, 350 insertions(+), 15 deletions(-)
diff --git a/sp-saml-conf-impl/pom.xml b/sp-saml-conf-impl/pom.xml
index a298232..644b0f5 100644
--- a/sp-saml-conf-impl/pom.xml
+++ b/sp-saml-conf-impl/pom.xml
@@ -116,6 +116,16 @@
<artifactId>opensaml-saml-impl</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-soap-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-soap-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-storage-impl</artifactId>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index a321b72..f2f1450 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -30,6 +30,26 @@
<!-- Token consumer descriptors for each SAML binding. -->
+ <bean p:id="sp/consumer/saml2/ecp" class="net.shibboleth.sp.profile.BasicTokenConsumerFlowDescriptor">
+ <property name="activationCondition">
+ <bean class="net.shibboleth.sp.profile.context.logic.HttpServletRequestPredicate"
+ p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
+ <property name="httpServletRequestValidator">
+ <bean class="net.shibboleth.shared.servlet.impl.ChainingHttpServletRequestValidator">
+ <property name="validators">
+ <list>
+ <bean class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestMethodValidator"
+ p:allowedMethods="POST" />
+ <bean class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestContentTypeValidator"
+ p:allowedContentTypes="application/vnd.paos+xml" />
+ </list>
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
<bean p:id="sp/consumer/saml2/post" class="net.shibboleth.sp.profile.BasicTokenConsumerFlowDescriptor">
<property name="activationCondition">
<bean class="net.shibboleth.sp.profile.context.logic.HttpServletRequestPredicate"
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/ecp/ecp-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/ecp/ecp-beans.xml
new file mode 100644
index 0000000..3b91103
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/ecp/ecp-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">
+
+ <bean id="shibboleth.HttpServletRequestValidator" class="net.shibboleth.shared.servlet.impl.NoOpHttpServletRequestValidator" />
+
+ <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+ p:messageType="org.opensaml.saml.saml2.core.Response">
+ <constructor-arg>
+ <bean class="net.shibboleth.sp.saml.saml2.binding.decoding.impl.HTTPECPDecoder" scope="prototype"
+ p:checkDuringInit="false"
+ p:parserPool-ref="shibboleth.ParserPool"
+ p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+ p:bindingDescriptor-ref="shibboleth.Binding.SAML2PAOS" />
+ </constructor-arg>
+ </bean>
+
+</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/ecp/ecp-flow.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/ecp/ecp-flow.xml
new file mode 100644
index 0000000..9bed995
--- /dev/null
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/ecp/ecp-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/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/consumer/saml2/ecp/ecp-beans.xml" />
+
+</flow>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/ecp/ecp-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/ecp/ecp-beans.xml
index 1cbd99a..904bd66 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/ecp/ecp-beans.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/ecp/ecp-beans.xml
@@ -133,15 +133,4 @@
<bean id="EncodeSOAPFault" class="net.shibboleth.sp.profile.impl.EncodeMessage" scope="prototype"
p:messageEncoderFactory-ref="messageEncoderFactory" />
- <!-- Binding objects (not in IdP at present). -->
-
- <bean parent="shibboleth.BindingDescriptor" id="shibboleth.Binding.SAML2PAOS"
- p:id="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
- p:shortName="PAOS"
- p:encoderBeanId="shibboleth.Encoders.SAML2PAOSEncoder" />
-
- <bean id="shibboleth.Encoders.SAML2PAOSEncoder"
- class="net.shibboleth.sp.saml.saml2.binding.encoding.impl.HTTPPAOSEncoder" scope="prototype" init-method=""
- p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier" />
-
</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/saml2-common-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/saml2-common-beans.xml
index b564d24..09ae6c2 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/saml2-common-beans.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/saml2-common-beans.xml
@@ -10,7 +10,7 @@
<bean id="ValidateHttpServletRequest" class="net.shibboleth.sp.profile.impl.ValidateHttpServletRequest" scope="prototype"
p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
p:checkDuringInit="false"
- p:validator="#{getObject('shibboleth.HttpServletRequestValidator') ?: null}" />
+ p:validator="#{getObject('shibboleth.HttpServletRequestValidator')}" />
<bean id="InboundResponsePopulateAuditContext" parent="shibboleth.sp.AbstractPopulateAuditContext"
p:fieldExtractors="#{getObject('shibboleth.sp.saml.InboundResponseAuditExtractors') ?: getObject('shibboleth.sp.saml.DefaultInboundResponseAuditExtractors')}">
@@ -239,5 +239,16 @@
p:velocityEngine-ref="shibboleth.VelocityEngine"
p:cSPDigester="#{%{sp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPDigester') : null}"
p:cSPNonceGenerator="#{%{sp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPNonce') : null}" />
+
+ <!-- Add PAOS binding material for ECP. -->
+
+ <bean id="shibboleth.Encoders.SAML2PAOSEncoder"
+ class="net.shibboleth.sp.saml.saml2.binding.encoding.impl.HTTPPAOSEncoder" scope="prototype" init-method=""
+ p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier" />
+
+ <bean parent="shibboleth.BindingDescriptor" id="shibboleth.Binding.SAML2PAOS"
+ p:id="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
+ p:shortName="PAOS"
+ p:encoderBeanId="shibboleth.Encoders.SAML2PAOSEncoder" />
</beans>
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
index 26178ab..3b6fa25 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
@@ -23,7 +23,7 @@
p:metadataResolver-ref="shibboleth.MetadataResolverService"
p:sessionInitiators="#{{ 'ecp', 'saml2' }}"
p:logoutInitiators="saml2"
- p:tokenConsumers="#{{ 'saml2/post', 'saml2/post-simplesign', 'saml2/artifact' }}"
+ p:tokenConsumers="#{{'saml2/ecp', 'saml2/post', 'saml2/post-simplesign', 'saml2/artifact' }}"
p:logoutConsumers="#{{ 'saml2/redirect', 'saml2/post', 'saml2/post-simplesign', 'saml2/artifact' }}">
<property name="id">
<util:constant static-field="net.shibboleth.sp.saml.saml2.SAML2ProtocolSupportService.PROTOCOL_ID" />
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 d298efb..d4bc520 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
@@ -24,6 +24,8 @@ import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.core.xml.XMLObjectBuilder;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.core.xml.io.UnmarshallingException;
import org.opensaml.core.xml.util.XMLObjectSupport;
@@ -44,9 +46,13 @@ import org.opensaml.saml.saml2.core.Subject;
import org.opensaml.saml.saml2.core.SubjectConfirmation;
import org.opensaml.saml.saml2.core.SubjectConfirmationData;
import org.opensaml.saml.saml2.core.SubjectLocality;
+import org.opensaml.saml.saml2.ecp.RelayState;
import org.opensaml.saml.saml2.testing.SAML2ActionTestingSupport;
import org.opensaml.security.SecurityException;
import org.opensaml.security.credential.Credential;
+import org.opensaml.soap.soap11.Body;
+import org.opensaml.soap.soap11.Envelope;
+import org.opensaml.soap.soap11.Header;
import org.opensaml.xmlsec.SignatureSigningParameters;
import org.opensaml.xmlsec.signature.SignableXMLObject;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
@@ -104,6 +110,7 @@ import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
import net.shibboleth.sp.profile.ConsumerConstants;
import net.shibboleth.sp.saml.saml2.SAMLStateData;
import net.shibboleth.sp.saml.saml2.SessionDataSupport;
+import net.shibboleth.sp.saml.saml2.profile.SAML2InitiatorConstants;
import net.shibboleth.sp.saml.saml2.profile.impl.PrepareAgentResponse;
import net.shibboleth.sp.state.StateData;
import net.shibboleth.sp.state.impl.CookieStateManager;
@@ -135,7 +142,7 @@ public class SAML2TokenConsumerFlowTest extends AbstractSPFlowTest {
@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/POST";
+ @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/Validate";
/** Target resource. */
@Nonnull public static final String RESOURCE_URL = "https://sp.example.org/secure";
@@ -767,6 +774,29 @@ public class SAML2TokenConsumerFlowTest extends AbstractSPFlowTest {
RESOURCE_URL, response.getAssertions().get(0).getAuthnStatements().get(0).getSessionIndex());
}
+ /**
+ * Test successful ECP flow.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testSuccessECP() throws Exception {
+ final Response response = buildSAMLResponse(ISSUER, StatusCode.SUCCESS, null);
+ sign(response);
+
+ final DDF input = buildRemotedECPResponse(buildECPResponse(response, null), null);
+ setApplicationRequest(APPLICATION_ID, input);
+
+ 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());
+ validateOutputMessage(result, CollectionSupport.singleton("mail"),
+ RESOURCE_URL, response.getAssertions().get(0).getAuthnStatements().get(0).getSessionIndex());
+ }
+
/**
* Decode an encoded response and run sanity checks against it.
*
@@ -920,6 +950,43 @@ public class SAML2TokenConsumerFlowTest extends AbstractSPFlowTest {
return response;
}
+ /**
+ * Constructs a SOAP Envelope for ECP around a response and state token.
+ *
+ * @param response the response to embed
+ * @param relayState state token
+ *
+ * @return SOAP envelope
+ */
+ @Nonnull private Envelope buildECPResponse(@Nonnull final Response response, @Nullable final String relayState) {
+ final XMLObjectBuilder<Envelope> envBuilder =
+ XMLObjectProviderRegistrySupport.getBuilderFactory().<Envelope>ensureBuilder(Envelope.DEFAULT_ELEMENT_NAME);
+ final XMLObjectBuilder<Body> bodyBuilder =
+ XMLObjectProviderRegistrySupport.getBuilderFactory().<Body>ensureBuilder(Body.DEFAULT_ELEMENT_NAME);
+
+ final Envelope env = envBuilder.buildObject(Envelope.DEFAULT_ELEMENT_NAME);
+
+ final Body body = bodyBuilder.buildObject(Body.DEFAULT_ELEMENT_NAME);
+ body.getUnknownXMLObjects().add(response);
+ env.setBody(body);
+
+ if (relayState != null) {
+ final XMLObjectBuilder<Header> headerBuilder =
+ XMLObjectProviderRegistrySupport.getBuilderFactory().<Header>ensureBuilder(Header.DEFAULT_ELEMENT_NAME);
+ final XMLObjectBuilder<RelayState> stateBuilder =
+ XMLObjectProviderRegistrySupport.getBuilderFactory().<RelayState>ensureBuilder(RelayState.DEFAULT_ELEMENT_NAME);
+
+ final Header header = headerBuilder.buildObject(Header.DEFAULT_ELEMENT_NAME);
+ env.setHeader(header);
+
+ final RelayState stateHeader = stateBuilder.buildObject(RelayState.DEFAULT_ELEMENT_NAME);
+ stateHeader.setValue(relayState);
+ header.getUnknownXMLObjects().add(stateHeader);
+ }
+
+ return env;
+ }
+
/**
* Encodes a SAML response into a form POST embedded in a remoted message.
*
@@ -948,7 +1015,7 @@ public class SAML2TokenConsumerFlowTest extends AbstractSPFlowTest {
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_URI).unsafe_string("/Shibboleth.sso/SAML2/POST".getBytes(StandardCharsets.UTF_8));
+ http.addmember(RemotedHttpServletRequest.REQUEST_URI).unsafe_string("/Shibboleth.sso/Validate".getBytes(StandardCharsets.UTF_8));
if (stateCookie != null) {
http.addmember("headers").structure().addmember("Cookie")
@@ -972,6 +1039,47 @@ public class SAML2TokenConsumerFlowTest extends AbstractSPFlowTest {
}
}
+ /**
+ * Encodes a SAML response into a form POST embedded in a remoted message.
+ *
+ * @param envelope SOAP envelope
+ * @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 buildRemotedECPResponse(@Nonnull final Envelope envelope, @Nullable Cookie stateCookie)
+ throws IOException {
+
+ try (final ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
+ XMLObjectSupport.marshallToOutputStream(envelope, sink);
+
+ final DDF obj = new DDF(null).structure();
+ obj.addmember(ConsumerConstants.HOME_URL).unsafe_string(RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
+ final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+
+ http.addmember(RemotedHttpServletRequest.SCHEME).string("https");
+ http.addmember(RemotedHttpServletRequest.PORT).integer(443);
+ http.addmember(RemotedHttpServletRequest.HOSTNAME).unsafe_string("sp.example.org".getBytes(StandardCharsets.UTF_8));
+ http.addmember(RemotedHttpServletRequest.METHOD).string("POST");
+ http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
+ http.addmember(RemotedHttpServletRequest.CONTENT_TYPE).string(SAML2InitiatorConstants.PAOS_MEDIA_TYPE);
+ http.addmember(RemotedHttpServletRequest.REQUEST_URI).unsafe_string("/Shibboleth.sso/Validate".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));
+ }
+
+ http.addmember(RemotedHttpServletRequest.BODY).unsafe_string(sink.toByteArray());
+
+ return obj;
+ } catch (final MarshallingException e) {
+ throw new IOException(e);
+ }
+ }
+
/**
* Sign object.
*
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/decoding/impl/HTTPECPDecoder.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/decoding/impl/HTTPECPDecoder.java
new file mode 100644
index 0000000..47eb00f
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/decoding/impl/HTTPECPDecoder.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.saml.saml2.binding.decoding.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.decoder.MessageDecodingException;
+import org.opensaml.messaging.handler.MessageHandler;
+import org.opensaml.saml.common.SAMLObject;
+import org.opensaml.saml.common.binding.BindingDescriptor;
+import org.opensaml.saml.common.binding.SAMLBindingSupport;
+import org.opensaml.saml.common.binding.decoding.SAMLMessageDecoder;
+import org.opensaml.saml.common.binding.impl.SAMLSOAPDecoderBodyHandler;
+import org.opensaml.saml.common.messaging.context.SAMLBindingContext;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.ecp.RelayState;
+import org.opensaml.soap.messaging.context.SOAP11Context;
+import org.opensaml.soap.soap11.Envelope;
+import org.opensaml.soap.soap11.Header;
+import org.slf4j.Logger;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.saml.saml2.profile.SAML2InitiatorConstants;
+
+/**
+ * SAML 2.0 ECP/PAOS Decoder.
+ */
+public class HTTPECPDecoder extends org.opensaml.soap.soap11.decoder.http.impl.HTTPSOAP11Decoder
+ implements SAMLMessageDecoder {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(HTTPECPDecoder.class);
+
+ /** Optional {@link BindingDescriptor} to inject into {@link SAMLBindingContext} created. */
+ @Nullable private BindingDescriptor bindingDescriptor;
+
+ /** Constructor. */
+ public HTTPECPDecoder() {
+ setProtocolMessageLoggerSubCategory("SAML");
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull @NotEmpty public String getBindingURI() {
+ return SAMLConstants.SAML2_PAOS_BINDING_URI;
+ }
+
+ /**
+ * Get an optional {@link BindingDescriptor} to inject into {@link SAMLBindingContext} created.
+ *
+ * @return binding descriptor
+ */
+ @Nullable public BindingDescriptor getBindingDescriptor() {
+ return bindingDescriptor;
+ }
+
+ /**
+ * Set an optional {@link BindingDescriptor} to inject into {@link SAMLBindingContext} created.
+ *
+ * @param descriptor a binding descriptor
+ */
+ public void setBindingDescriptor(@Nullable final BindingDescriptor descriptor) {
+ bindingDescriptor = descriptor;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+
+ // Need to set this before calling base class.
+ if (getBodyHandler() == null) {
+ final MessageHandler handler = new SAMLSOAPDecoderBodyHandler();
+ handler.initialize();
+ setBodyHandler(handler);
+ }
+
+ super.doInitialize();
+ }
+
+ /** {@inheritDoc} */
+ protected void doDecode() throws MessageDecodingException {
+ super.doDecode();
+
+ final MessageContext msgCtx = getMessageContext();
+ assert msgCtx != null;
+
+ populateBindingContext(msgCtx);
+
+ processSOAPHeaders(msgCtx);
+
+ final Object samlMessage = msgCtx.getMessage();
+ if (samlMessage instanceof SAMLObject) {
+ log.debug("Decoded SOAP message which included SAML message of type {}",
+ ((SAMLObject) samlMessage).getElementQName());
+ } else {
+ throw new MessageDecodingException("Decoded SOAP message did not contain SAMLObject");
+ }
+ }
+
+ /**
+ * Populate the context which carries information specific to this binding.
+ *
+ * @param messageContext the current message context
+ */
+ protected void populateBindingContext(@Nonnull final MessageContext messageContext) {
+ final SAMLBindingContext bindingContext = messageContext.ensureSubcontext(SAMLBindingContext.class);
+ bindingContext.setBindingUri(getBindingURI());
+ bindingContext.setBindingDescriptor(bindingDescriptor);
+ bindingContext.setHasBindingSignature(false);
+ bindingContext.setIntendedDestinationEndpointURIRequired(true);
+ }
+
+ /**
+ * Process ECP/PAOS headers.
+ *
+ * @param messageContext message context
+ */
+ protected void processSOAPHeaders(@Nonnull final MessageContext messageContext) {
+ final SOAP11Context soapContext = messageContext.ensureSubcontext(SOAP11Context.class);
+ final Envelope env = soapContext.getEnvelope();
+ final Header header = env != null ? env.getHeader() : null;
+ if (header != null) {
+ final List<XMLObject> relayState = header.getUnknownXMLObjects(RelayState.DEFAULT_ELEMENT_NAME);
+ if (relayState != null && relayState.size() > 0) {
+ SAMLBindingSupport.setRelayState(messageContext, ((RelayState) relayState.get(0)).getValue());
+ }
+
+ // We're ignoring the PAOS Response header if it exists, as the correlation at the SAML layer is
+ // applied later.
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void validateHttpRequest(@Nonnull final HttpServletRequest request) throws MessageDecodingException {
+
+ // Replace usual SOAP type check.
+ if (!SAML2InitiatorConstants.PAOS_MEDIA_TYPE.equals(request.getContentType())) {
+ log.warn("Saw unsupported request Content-Type: {}", request.getContentType());
+ throw new MessageDecodingException(
+ String.format("Content-Type '%s' was not a supported media type", request.getContentType()));
+ }
+
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list