[java-plugin-shibd-saml] branch main updated: WIP on ECP with handlers, profile config, machinery for detecting use.

Codeberg noreply at shibboleth.net
Tue Aug 4 23:30:45 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/c5d4103d142143bb610e9d02a7d64c1c2a7d35c6

The following commit(s) were added to refs/heads/main by this push:
     new c5d4103  WIP on ECP with handlers, profile config, machinery for detecting use.
c5d4103 is described below

commit c5d4103d142143bb610e9d02a7d64c1c2a7d35c6
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Aug 4 19:30:32 2026 -0400

    WIP on ECP with handlers, profile config, machinery for detecting use.
---
 .../shibboleth/sp/liberty/paos/package-info.java   |   5 +-
 .../config/BrowserSSOProfileConfiguration.java     |   3 +-
 .../profile/config/ECPProfileConfiguration.java    |  27 +++++
 .../sp/saml/saml2/profile/config/package-info.java |   5 +-
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  14 ++-
 .../net/shibboleth/sp/service/agent/postconfig.xml |   6 +-
 .../sp/liberty/paos/impl/package-info.java         |   5 +-
 .../saml2/binding/decoding/impl}/package-info.java |   4 +-
 .../binding/encoding/impl/HTTPPAOSEncoder.java     |  42 +++++++
 .../saml2/binding/encoding/impl}/package-info.java |   4 +-
 .../binding/impl/AddECPRequestHeaderHandler.java   | 128 +++++++++++++++++++++
 .../binding/impl/AddPAOSRequestHeaderHandler.java  |  86 ++++++++++++++
 .../impl/ECPHttpServletRequestValidator.java       |  95 +++++++++++++++
 .../sp/saml/saml2/binding/impl}/package-info.java  |   4 +-
 .../config/impl/ECPProfileConfiguration.java       |  72 +++++++++++-
 .../saml2/profile/config/impl/package-info.java    |   5 +-
 16 files changed, 486 insertions(+), 19 deletions(-)

diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java b/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
index 48237e8..168cb11 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
@@ -15,4 +15,7 @@
 /**
  * PAOS XML APIs.
  */
-package net.shibboleth.sp.liberty.paos;
\ No newline at end of file
+ at NonnullElements
+package net.shibboleth.sp.liberty.paos;
+
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
index a0c9bf2..bd664a9 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
@@ -24,7 +24,6 @@ import javax.annotation.Nullable;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.saml.profile.config.SAMLArtifactConsumerProfileConfiguration;
 import net.shibboleth.shared.annotation.ConfigurationSetting;
-import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.sp.state.StateManager;
@@ -90,7 +89,7 @@ public interface BrowserSSOProfileConfiguration extends SAMLArtifactConsumerProf
      * @return list of class references
      */
     @ConfigurationSetting(name="authnContextClassRefs")
-    @Nonnull @NonnullElements @NotLive @Unmodifiable List<String> getAuthnContextClassRefs(
+    @Nonnull @NotLive @Unmodifiable List<String> getAuthnContextClassRefs(
             @Nullable final ProfileRequestContext profileRequestContext);
     
     /**
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/ECPProfileConfiguration.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/ECPProfileConfiguration.java
index 6b921b0..3d96290 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/ECPProfileConfiguration.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/ECPProfileConfiguration.java
@@ -14,8 +14,35 @@
 
 package net.shibboleth.sp.saml.saml2.profile.config;
 
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.IDPList;
+
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+
 /** Configuration support for SP SAML 2.0 ECP. */
 public interface ECPProfileConfiguration
     extends BrowserSSOProfileConfiguration, net.shibboleth.saml.saml2.profile.config.ECPProfileConfiguration {
 
+    /**
+     * Get the provider name to signal to the ECP client.
+     * 
+     * @param profileRequestContext current profile request context
+     * 
+     * @return provider name
+     */
+    @ConfigurationSetting(name="providerName")
+    @Nullable String getProviderName(@Nullable final ProfileRequestContext profileRequestContext);
+
+    /**
+     * Get the {@link IDPList} object to signal to the ECP client.
+     * 
+     * @param profileRequestContext current profile request context
+     * 
+     * @return the IdP list
+     */
+    @ConfigurationSetting(name="iDPList")
+    @Nullable IDPList getIDPList(@Nullable final ProfileRequestContext profileRequestContext);
+
 }
\ No newline at end of file
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/package-info.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/package-info.java
index e97e075..51a8d7e 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/package-info.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/package-info.java
@@ -15,4 +15,7 @@
 /**
  * Configuration of SAML support in SP.
  */
-package net.shibboleth.sp.saml.saml2.profile.config;
\ No newline at end of file
+ at NonnullElements
+package net.shibboleth.sp.saml.saml2.profile.config;
+
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
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 770d5d2..a321b72 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
@@ -15,10 +15,16 @@
 
     <!-- Session initiator flows. -->
 
-    <!--
-    <bean p:id="sp/initiator/ecp" class="net.shibboleth.sp.profile.BasicSessionInitiatorFlowDescriptor"
-        p:activationCondition-ref="" />
-    -->
+    <bean p:id="sp/initiator/ecp" class="net.shibboleth.sp.profile.BasicSessionInitiatorFlowDescriptor">
+        <property name="activationCondition">
+            <bean class="net.shibboleth.sp.profile.context.logic.HttpServletRequestPredicate"
+                    p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
+                <property name="httpServletRequestValidator">
+                    <bean class="net.shibboleth.sp.saml.saml2.binding.impl.ECPHttpServletRequestValidator" />
+                </property>
+            </bean>
+        </property>
+    </bean>
     
     <bean p:id="sp/initiator/saml2" class="net.shibboleth.sp.profile.BasicSessionInitiatorFlowDescriptor" />
 
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 1a25896..26178ab 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
@@ -21,7 +21,7 @@
     <bean class="net.shibboleth.sp.saml.saml2.BasicSAML2ProtocolSupportService"
             p:order="%{sp.saml.relativeOrder:1}"
             p:metadataResolver-ref="shibboleth.MetadataResolverService"
-            p:sessionInitiators="saml2"
+            p:sessionInitiators="#{{ 'ecp', 'saml2' }}"
             p:logoutInitiators="saml2"
             p:tokenConsumers="#{{ 'saml2/post', 'saml2/post-simplesign', 'saml2/artifact' }}"
             p:logoutConsumers="#{{ 'saml2/redirect', 'saml2/post', 'saml2/post-simplesign', 'saml2/artifact' }}">
@@ -31,6 +31,7 @@
         <property name="defaultProfileConfigurations">
             <list>
                 <ref bean="SAML2.SSO" />
+                <ref bean="SAML2.ECP" />
                 <ref bean="SAML2.Logout" />
             </list>
         </property>
@@ -76,7 +77,8 @@
         static-field="net.shibboleth.saml.saml2.profile.config.BrowserSSOProfileConfiguration.FEATURE_FORCEAUTHN"/>
 
     <bean id="SAML2.ECP" parent="SAML2.SSO" lazy-init="true"
-        class="net.shibboleth.sp.saml.saml2.profile.config.impl.ECPProfileConfiguration" />
+        class="net.shibboleth.sp.saml.saml2.profile.config.impl.ECPProfileConfiguration"
+        p:providerName="%{sp.saml.providerName:}" />
         
     <bean id="SAML2.Logout" lazy-init="true"
         class="net.shibboleth.sp.saml.saml2.profile.config.impl.SingleLogoutProfileConfiguration"
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/liberty/paos/impl/package-info.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/liberty/paos/impl/package-info.java
index 268f5e4..6d27561 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/liberty/paos/impl/package-info.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/liberty/paos/impl/package-info.java
@@ -15,4 +15,7 @@
 /**
  * Liberty PAOS XML implementation.
  */
-package net.shibboleth.sp.liberty.paos.impl;
\ No newline at end of file
+ at NonnullElements
+package net.shibboleth.sp.liberty.paos.impl;
+
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/decoding/impl/package-info.java
similarity index 84%
copy from sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
copy to sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/decoding/impl/package-info.java
index 48237e8..2e973d5 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/decoding/impl/package-info.java
@@ -13,6 +13,6 @@
  */
 
 /**
- * PAOS XML APIs.
+ * SAML 2.0 MessageDecoders specific to the SP.
  */
-package net.shibboleth.sp.liberty.paos;
\ No newline at end of file
+package net.shibboleth.sp.saml.saml2.binding.decoding.impl;
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/encoding/impl/HTTPPAOSEncoder.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/encoding/impl/HTTPPAOSEncoder.java
new file mode 100644
index 0000000..6d676e3
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/encoding/impl/HTTPPAOSEncoder.java
@@ -0,0 +1,42 @@
+/*
+ * 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.encoding.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.saml.common.binding.encoding.SAMLMessageEncoder;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.binding.encoding.impl.HTTPSOAP11Encoder;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * SAML 2.0 PAOS Encoder.
+ */
+public class HTTPPAOSEncoder extends HTTPSOAP11Encoder
+        implements SAMLMessageEncoder {
+
+    /** Constructor. */
+    public HTTPPAOSEncoder() {
+        setProtocolMessageLoggerSubCategory("SAML");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @NotEmpty public String getBindingURI() {
+        return SAMLConstants.SAML2_PAOS_BINDING_URI;
+    }
+        
+}
\ No newline at end of file
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/encoding/impl/package-info.java
similarity index 84%
copy from sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
copy to sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/encoding/impl/package-info.java
index 48237e8..1881006 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/encoding/impl/package-info.java
@@ -13,6 +13,6 @@
  */
 
 /**
- * PAOS XML APIs.
+ * SAML 2.0 MessageEncoders specific to the SP.
  */
-package net.shibboleth.sp.liberty.paos;
\ No newline at end of file
+package net.shibboleth.sp.saml.saml2.binding.encoding.impl;
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/AddECPRequestHeaderHandler.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/AddECPRequestHeaderHandler.java
new file mode 100644
index 0000000..b2e2644
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/AddECPRequestHeaderHandler.java
@@ -0,0 +1,128 @@
+/*
+ * 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.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.SAMLObjectBuilder;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.IDPList;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.ecp.Request;
+import org.opensaml.soap.messaging.SOAPMessagingSupport;
+import org.opensaml.soap.soap11.ActorBearing;
+import org.opensaml.soap.util.SOAPSupport;
+import org.slf4j.Logger;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.saml.saml2.profile.config.ECPProfileConfiguration;
+
+/**
+ * MessageHandler to add the ECP {@link Request} header to an outgoing PAOS/SOAP envelope.
+ * 
+ * <p>The {@link ECPProfileConfiguration} is located via a {@link RelyingPartyContext} located
+ * under the {@link ProfileRequestContext} parent of the {@link MessageContext}.</p>
+ */
+public class AddECPRequestHeaderHandler extends AbstractMessageHandler {
+   
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddECPRequestHeaderHandler.class);
+
+    /** The SAML request to copy from. */
+    @NonnullBeforeExec private AuthnRequest authnRequest;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        if (!super.doPreInvoke(messageContext)) {
+            return false;
+        }
+        
+        if (messageContext.getMessage() instanceof AuthnRequest areq) {
+            authnRequest = areq;
+        } else {
+            log.error("{} No AuthnRequest or AssertionConsumerServiceURL available in message context", getLogPrefix());
+            throw new MessageHandlerException("No AuthnRequest available.");
+        }
+        
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        final SAMLObjectBuilder<Request> requestBuilder = (SAMLObjectBuilder<Request>)
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<Request>ensureBuilder(
+                        Request.DEFAULT_ELEMENT_NAME);
+        
+        final Request header = requestBuilder.buildObject(Request.DEFAULT_ELEMENT_NAME);
+        
+        // IsPassive in this header block defaults to true if absent (no idea why we did that).
+        final Boolean passive = authnRequest.isPassive();
+        if (passive == null || !passive) {
+            header.setPassive(false);
+        }
+        
+        final Issuer issuer = authnRequest.getIssuer();
+        if (issuer != null) {
+            try {
+                header.setIssuer(XMLObjectSupport.cloneXMLObject(issuer));
+            } catch (final MarshallingException | UnmarshallingException e) {
+                throw new MessageHandlerException("Exception cloning Issuer from request", e);
+            }
+        }
+
+        final ProfileRequestContext prc =
+                new RecursiveTypedParentContextLookup<>(ProfileRequestContext.class).apply(messageContext);
+        if (prc != null) {
+            final RelyingPartyContext relyingPartyContext = prc.getSubcontext(RelyingPartyContext.class);
+            if (relyingPartyContext != null &&
+                    relyingPartyContext.getProfileConfig() instanceof ECPProfileConfiguration ecp) {
+                header.setProviderName(ecp.getProviderName(prc));
+                
+                final IDPList idpList = ecp.getIDPList(prc);
+                if (idpList != null) {
+                    try {
+                        header.setIDPList(XMLObjectSupport.cloneXMLObject(idpList));
+                    } catch (final MarshallingException | UnmarshallingException e) {
+                        throw new MessageHandlerException("Exception cloning IDPList from profile configuration", e);
+                    }
+                }
+            }
+        }
+        
+        SOAPSupport.addSOAP11MustUnderstandAttribute(header, true);
+        SOAPSupport.addSOAP11ActorAttribute(header, ActorBearing.SOAP11_ACTOR_NEXT);
+        
+        try {
+            SOAPMessagingSupport.addHeaderBlock(messageContext, header);
+        } catch (final Exception e) {
+            throw new MessageHandlerException(e);
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/AddPAOSRequestHeaderHandler.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/AddPAOSRequestHeaderHandler.java
new file mode 100644
index 0000000..8071846
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/AddPAOSRequestHeaderHandler.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.saml.saml2.binding.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.xml.XMLObjectBuilder;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.soap.messaging.SOAPMessagingSupport;
+import org.opensaml.soap.soap11.ActorBearing;
+import org.opensaml.soap.util.SOAPSupport;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.liberty.paos.Request;
+
+/**
+ * MessageHandler to add the PAOS {@link Request} header to an outgoing PAOS/SOAP envelope.
+ */
+public class AddPAOSRequestHeaderHandler extends AbstractMessageHandler {
+   
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddPAOSRequestHeaderHandler.class);
+
+    /** The SAML request to copy from. */
+    @NonnullBeforeExec private AuthnRequest authnRequest;
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        
+        if (!super.doPreInvoke(messageContext)) {
+            return false;
+        }
+        
+        if (messageContext.getMessage() instanceof AuthnRequest areq && areq.getAssertionConsumerServiceURL() != null) {
+            authnRequest = areq;
+        } else {
+            log.error("{} No AuthnRequest or AssertionConsumerServiceURL available in message context", getLogPrefix());
+            throw new MessageHandlerException("No AuthnRequest or AssertionConsumerServiceURL available.");
+        }
+        
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        final XMLObjectBuilder<Request> requestBuilder = 
+                XMLObjectProviderRegistrySupport.getBuilderFactory().<Request>ensureBuilder(
+                        Request.DEFAULT_ELEMENT_NAME);
+        
+        final Request header = requestBuilder.buildObject(Request.DEFAULT_ELEMENT_NAME);
+        header.setService(SAMLConstants.SAML20ECP_NS);
+        header.setMessageID(authnRequest.getID());
+        header.setResponseConsumerURL(authnRequest.getAssertionConsumerServiceURL());
+        
+        SOAPSupport.addSOAP11MustUnderstandAttribute(header, true);
+        SOAPSupport.addSOAP11ActorAttribute(header, ActorBearing.SOAP11_ACTOR_NEXT);
+        
+        try {
+            SOAPMessagingSupport.addHeaderBlock(messageContext, header);
+        } catch (final Exception e) {
+            throw new MessageHandlerException(e);
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/ECPHttpServletRequestValidator.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/ECPHttpServletRequestValidator.java
new file mode 100644
index 0000000..f5972b4
--- /dev/null
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/ECPHttpServletRequestValidator.java
@@ -0,0 +1,95 @@
+/*
+ * 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.impl;
+
+import java.util.Enumeration;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.springframework.http.MediaType;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.servlet.HttpServletRequestValidator;
+
+/**
+ * A request validator that detects the requirements of an ECP client request:
+ * 
+ * <ol>
+ * 
+ * <li>Accept header indicating acceptance of the MIME type <pre>application/vnd.paos+xml</pre></li>
+ * 
+ * <li>PAOS header specifying the PAOS version with a value, at minimum, of
+ *  <pre>urn:liberty:paos:2003-08</pre> and a supported service value of
+ *  <pre>urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp</pre></li>
+ * 
+ * </ol>
+ */
+public class ECPHttpServletRequestValidator implements HttpServletRequestValidator {
+
+    /** Required start of PAOS header. */
+    @Nonnull private static final String PAOS_VERSION = "ver=\"urn:liberty:paos:2003-08\";";
+    
+    /** Media type for PAOS. */
+    @Nonnull private final MediaType paosType;
+    
+    /** Constructor. */
+    @SuppressWarnings("null")
+    public ECPHttpServletRequestValidator() {
+        paosType = MediaType.parseMediaType("application/vnd.paos+xml");
+    }
+    
+    /** {@inheritDoc} */
+    public void validate(@Nonnull final HttpServletRequest request) throws ServletException {
+        
+        boolean sawType = false;
+        
+        final Enumeration<String> acceptHeaders = request.getHeaders("Accept");
+        while (!sawType && acceptHeaders.hasMoreElements()) {
+            try {
+                sawType = MediaType.parseMediaTypes(acceptHeaders.nextElement()).stream().anyMatch(paosType::equals);
+            } catch (final Exception e) {
+                throw new ServletException(e);
+            }
+        }
+        
+        if (!sawType) {
+            throw new ServletException("Accept header did not contain PAOS media type.");
+        }
+        
+        final String paosHeader = request.getHeader("PAOS");
+        if (paosHeader == null) {
+            throw new ServletException("No PAOS header found in request.");
+        }
+        
+        if (!paosHeader.startsWith(PAOS_VERSION)) {
+            throw new ServletException("PAOS header does not indicate expected version support.");
+        }
+        
+        final String paosService = paosHeader.substring(PAOS_VERSION.length());
+        if (paosService != null) {
+            final String[] options = paosService.split(",");
+            if (options != null && options.length > 0 &&
+                    SAMLConstants.SAML20ECP_NS.equals(StringSupport.trimOrNull(options[0]))) {
+                return;
+            }
+        }
+        
+        throw new ServletException("PAOS header missing required ECP service indicator.");
+    }
+
+}
\ No newline at end of file
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/package-info.java
similarity index 83%
copy from sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
copy to sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/package-info.java
index 48237e8..30e664a 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/liberty/paos/package-info.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/binding/impl/package-info.java
@@ -13,6 +13,6 @@
  */
 
 /**
- * PAOS XML APIs.
+ * SAML 2.0 Binding-related MessageHandler implementations.
  */
-package net.shibboleth.sp.liberty.paos;
\ No newline at end of file
+package net.shibboleth.sp.saml.saml2.binding.impl;
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/ECPProfileConfiguration.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/ECPProfileConfiguration.java
index c9f3022..a4b8deb 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/ECPProfileConfiguration.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/ECPProfileConfiguration.java
@@ -14,13 +14,83 @@
 
 package net.shibboleth.sp.saml.saml2.profile.config.impl;
 
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.IDPList;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.primitive.StringSupport;
+
 /** Configuration support for SP SAML 2.0 ECP. */
 public class ECPProfileConfiguration extends BrowserSSOProfileConfiguration
         implements net.shibboleth.sp.saml.saml2.profile.config.ECPProfileConfiguration {
 
+    /** Lookup function to supply provider name. */
+    @Nonnull private Function<ProfileRequestContext,String> providerNameLookupStrategy;
+    
+    /** Lookup function to supply IdP list. */
+    @Nonnull private Function<ProfileRequestContext,IDPList> idpListLookupStrategy;
+    
     /** Constructor. */
     public ECPProfileConfiguration() {
-        super(net.shibboleth.saml.saml2.profile.config.ECPProfileConfiguration.PROFILE_ID);        
+        super(net.shibboleth.saml.saml2.profile.config.ECPProfileConfiguration.PROFILE_ID);
+        
+        setResponseBinding(SAMLConstants.SAML2_PAOS_BINDING_URI);
+
+        providerNameLookupStrategy = FunctionSupport.constant(null);
+        idpListLookupStrategy = FunctionSupport.constant(null);
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public String getProviderName(@Nullable ProfileRequestContext profileRequestContext) {
+        return providerNameLookupStrategy.apply(profileRequestContext);
+    }
+    
+    /**
+     * Set the provider name to signal to the ECP client.
+     * 
+     * @param name provider name
+     */
+    public void setProviderName(@Nullable final String name) {
+        providerNameLookupStrategy = FunctionSupport.constant(StringSupport.trimOrNull(name));
+    }
+    
+    /**
+     * Set the lookup strategy for the provider name to signal to the ECP client.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setProviderNameLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        providerNameLookupStrategy = Constraint.isNotNull(strategy, "ProviderName lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public IDPList getIDPList(@Nullable ProfileRequestContext profileRequestContext) {
+        return idpListLookupStrategy.apply(profileRequestContext);
+    }
+    
+    /**
+     * Set the {@link IDPList} object to signal to the ECP client.
+     * 
+     * @param list IdP list object
+     */
+    public void setIDPList(@Nullable final IDPList list) {
+        idpListLookupStrategy = FunctionSupport.constant(list);
+    }
+    
+    /**
+     * Set the lookup strategy for the {@link IDPList} object to signal to the ECP client.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setIDPListLookupStrategy(@Nonnull final Function<ProfileRequestContext,IDPList> strategy) {
+        idpListLookupStrategy = Constraint.isNotNull(strategy, "IDPList lookup strategy cannot be null");
     }
     
 }
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/package-info.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/package-info.java
index 5b08f92..75d1651 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/package-info.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/package-info.java
@@ -15,4 +15,7 @@
 /**
  * SAML 2.0 profile configuration classes.
  */
-package net.shibboleth.sp.saml.saml2.profile.config.impl;
\ No newline at end of file
+ at NonnullElements
+package net.shibboleth.sp.saml.saml2.profile.config.impl;
+
+import net.shibboleth.shared.annotation.constraint.NonnullElements;

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


More information about the commits mailing list