[java-idp-integration-tests] branch main updated: Initial test for consent with isPassive=true

Codeberg noreply at shibboleth.net
Sat Nov 15 00:09:29 UTC 2025


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

codeberg pushed a commit to branch main
in repository java-idp-integration-tests.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-integration-tests/commit/bed8820e5a8c47186bea1792669434d58b13bd41

The following commit(s) were added to refs/heads/main by this push:
     new bed8820  Initial test for consent with isPassive=true
bed8820 is described below

commit bed8820e5a8c47186bea1792669434d58b13bd41
Author: Tom Zeller <tzeller at dragonacea.biz>
AuthorDate: Fri Nov 14 18:09:01 2025 -0600

    Initial test for consent with isPassive=true
    
    https://shibboleth.atlassian.net/browse/IDP-2409
---
 src/test/docker/shib-tests-sp/Dockerfile           |  68 +++++
 src/test/docker/shib-tests-sp/docker-compose.yml   |  27 ++
 .../shib-tests-sp/etc/httpd/conf.d/shib.conf       |  59 ++++
 .../shib-tests-sp/etc/pki/tls/certs/.gitignore     |   2 +
 .../shib-tests-sp/etc/pki/tls/private/.gitignore   |   2 +
 .../shib-tests-sp/etc/shibboleth/idp-metadata.xml  |  79 ++++++
 .../shib-tests-sp/etc/shibboleth/shibboleth2.xml   | 116 ++++++++
 .../etc/shibboleth/sp-encrypt-cert.pem             |  25 ++
 .../etc/shibboleth/sp-encrypt-key.pem              |  40 +++
 .../shib-tests-sp/etc/shibboleth/sp-metadata.xml   | 120 ++++++++
 .../etc/shibboleth/sp-signing-cert.pem             |  25 ++
 .../etc/shibboleth/sp-signing-key.pem              |  40 +++
 src/test/docker/shib-tests-sp/etc/supervisord.conf |  32 +++
 .../shib-tests-sp/etc/yum.repos.d/shibboleth.repo  |   9 +
 .../docker/shib-tests-sp/var/www/cgi-bin/printenv  |  26 ++
 .../docker/shib-tests-sp/var/www/html/index.html   |  10 +
 .../shib-tests-sp/var/www/html/secure/index.html   |  10 +
 .../tests/consent/PassiveConsentTest.java          | 225 +++++++++++++++
 .../idp/integration/tests/consent/SPContainer.java | 304 +++++++++++++++++++++
 19 files changed, 1219 insertions(+)

diff --git a/src/test/docker/shib-tests-sp/Dockerfile b/src/test/docker/shib-tests-sp/Dockerfile
new file mode 100644
index 0000000..bec090f
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/Dockerfile
@@ -0,0 +1,68 @@
+# Run httpd + shibboleth on Rocky Linux.
+#
+# Provides URLs which display the following :
+#  /                  : "Hello world"
+#  /secure/           : "Secure"
+#  /cgi-bin/printenv/ : environment variables
+#
+# Only the /secure/ path is protected by the Shibboleth SP.
+
+# Use Rocky Linux 9 base image.
+FROM rockylinux:9
+
+# Enable Shibboleth repository
+COPY etc/yum.repos.d/shibboleth.repo /etc/yum.repos.d/shibboleth.repo
+
+# Install Apache with mod_auth_openidc.
+# Include perl for /cgi-bin/printenv.
+RUN dnf clean all && \
+    dnf install -y epel-release && \
+    dnf update -y && \
+    dnf install -y  \
+    openssl  \
+    httpd  \
+    mod_ssl  \
+    shibboleth \
+    perl \
+    supervisor \
+    libfaketime \
+    && dnf clean all \
+    && rm -rf /var/cache/yum
+
+# Replace ServerName with environment variable or the default 'sp.tests.shibboleth.net'.
+ENV ServerName=sp.tests.shibboleth.net
+
+RUN sed -i -e 's/#ServerName www.example.com:80/ServerName ${ServerName}:80/'   /etc/httpd/conf/httpd.conf
+RUN sed -i -e 's/#ServerName www.example.com:443/ServerName ${ServerName}:443/' /etc/httpd/conf.d/ssl.conf
+
+# Enable httpd debug logging.
+# RUN sed -i -e 's/LogLevel warn/LogLevel debug/' /etc/httpd/conf.d/ssl.conf
+
+# Copy demo web pages, non-secure displays "Hello world" while secure displays "Secure".
+COPY var/www/html/index.html        /var/www/html/index.html
+COPY var/www/html/secure/index.html /var/www/html/secure/index.html
+
+# Enable printenv
+COPY var/www/cgi-bin/printenv /var/www/cgi-bin/printenv
+RUN chmod ugo+x /var/www/cgi-bin/printenv
+
+# Copy Shibboleth SP configuration files
+COPY etc/shibboleth/ /etc/shibboleth/
+
+# Load Shibboleth module in Apache 
+COPY etc/httpd/conf.d/shib.conf /etc/httpd/conf.d/shib.conf
+
+# Create temporary directory and chown to shibd since
+# supervisord runs shibd as the shibd user.
+RUN mkdir /var/run/shibboleth
+RUN chown shibd:shibd /var/run/shibboleth
+
+# Expose http and https ports.
+EXPOSE 80 443
+
+# Run httpd as apache user.
+RUN chown -R apache:apache /var/log/httpd
+RUN chown -R apache:apache /run/httpd
+
+COPY etc/supervisord.conf /etc/supervisord.conf
+CMD ["/usr/bin/supervisord", "--configuration=/etc/supervisord.conf", "--silent"]
diff --git a/src/test/docker/shib-tests-sp/docker-compose.yml b/src/test/docker/shib-tests-sp/docker-compose.yml
new file mode 100644
index 0000000..ba2faff
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/docker-compose.yml
@@ -0,0 +1,27 @@
+# Example Docker Compose file to run the test SP container.
+#
+# Docker Compose is not used by the integration tests (they use TestContainers.com).
+# Note that the ServerName is defined as an environment variable.
+#
+# TLS cert and key should be copied to etc/pki/tls/.
+#
+services:
+  shib-tests-sp:
+    container_name: shib-tests-sp
+    image: shib-tests-sp
+    build: .
+    environment:
+      - ServerName=${ServerName:-sp.tests.shibboleth.net}
+    extra_hosts:
+      - "idp.tests.shibboleth.net:host-gateway"
+    hostname: ${ServerName:-sp.tests.shibboleth.net}
+    ports:
+      - "30080:80"
+      - "30443:443"
+    volumes:
+      - ${tlsCert:-./etc/pki/tls/certs/fullchain.cer}:/etc/pki/tls/certs/localhost.crt
+      - ${tlsKey:-./etc/pki/tls/private/tests.shibboleth.net.key}:/etc/pki/tls/private/localhost.key
+
+networks:
+  default:
+    name: shib-tests-network
diff --git a/src/test/docker/shib-tests-sp/etc/httpd/conf.d/shib.conf b/src/test/docker/shib-tests-sp/etc/httpd/conf.d/shib.conf
new file mode 100644
index 0000000..6092ae7
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/httpd/conf.d/shib.conf
@@ -0,0 +1,59 @@
+# https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPApacheConfig
+
+# RPM installations on platforms with a conf.d directory will
+# result in this file being copied into that directory for you
+# and preserved across upgrades.
+
+# For non-RPM installs, you should copy the relevant contents of
+# this file to a configuration location you control.
+
+#
+# Load the Shibboleth module.
+#
+LoadModule mod_shib /usr/lib64/shibboleth/mod_shib_24.so
+
+#
+# Turn this on to support "require valid-user" rules from other
+# mod_authn_* modules, and use "require shib-session" for anonymous
+# session-based authorization in mod_shib.
+#
+ShibCompatValidUser Off
+
+#
+# Ensures handler will be accessible.
+#
+<Location /Shibboleth.sso>
+  AuthType None
+  Require all granted
+</Location>
+
+#
+# Used for example style sheet in error templates.
+#
+<IfModule mod_alias.c>
+  <Location /shibboleth-sp>
+    AuthType None
+    Require all granted
+  </Location>
+  Alias /shibboleth-sp/main.css /usr/share/shibboleth/main.css
+</IfModule>
+
+#
+# Configure the module for content.
+#
+# You MUST enable AuthType shibboleth for the module to process
+# any requests, and there MUST be a require command as well. To
+# enable Shibboleth but not specify any session/access requirements
+# use "require shibboleth".
+#
+<Location /secure>
+  AuthType shibboleth
+  ShibRequestSetting requireSession 1
+  require shib-session
+</Location>
+
+<Location /cgi-bin/printenv>
+  AuthType shibboleth
+  ShibRequestSetting requireSession 1
+  require shib-session
+</Location>
diff --git a/src/test/docker/shib-tests-sp/etc/pki/tls/certs/.gitignore b/src/test/docker/shib-tests-sp/etc/pki/tls/certs/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/pki/tls/certs/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-sp/etc/pki/tls/private/.gitignore b/src/test/docker/shib-tests-sp/etc/pki/tls/private/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/pki/tls/private/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-sp/etc/shibboleth/idp-metadata.xml b/src/test/docker/shib-tests-sp/etc/shibboleth/idp-metadata.xml
new file mode 100644
index 0000000..b6a91d8
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/shibboleth/idp-metadata.xml
@@ -0,0 +1,79 @@
+<!--
+ This is example metadata only. Do *NOT* supply it as is without review,
+and do *NOT* provide it in real time to your partners.
+This metadata is not dynamic - it will not change as your configuration changes.
+On Demand Metadata Generation available from the metadatagen plugin.
+-->
+<md:EntityDescriptor entityID="https://idp.example.org" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+    <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <md:Extensions>
+            <shibmd:Scope regexp="false">example.org</shibmd:Scope>
+            <mdui:UIInfo>
+                <mdui:DisplayName xml:lang="en">A name for the IdP at idp.tests.shibboleth.net</mdui:DisplayName>
+                <mdui:Description xml:lang="en">Enter a description for the IdP at idp.tests.shibboleth.net</mdui:Description>
+                <mdui:Logo xml:lang="en" width="80" height="80">https://idp.tests.shibboleth.net/path/to/logo.png</mdui:Logo>
+            </mdui:UIInfo>
+        </md:Extensions>
+        <md:KeyDescriptor use="signing">
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIDtTCCAp2gAwIBAgIJAPmsD+VGldyPMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+                        BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+                        aWRnaXRzIFB0eSBMdGQwHhcNMTQwNDExMTMzOTE4WhcNMjQwNDA4MTMzOTE4WjBF
+                        MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+                        ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+                        CgKCAQEAxg0TyQAP/tIvOH89EtaXuRRn8SYzTj7W1TbNY4VvBmobjkRmSkki4hH9
+                        x4sQpi635wn6WtXTN/FNNmkTK3N/LspmBWxfZS+n+cc7I82E5yvCAPX67QsZgqgg
+                        lp2W5dvK/FsMMCS6X6SVqzBLMP88NenXKxY+HMxMs0sT0UKYh1cAEqadrHRBO65a
+                        DBcm5a0sBVYt9K6pgaOHrp/zSIbhnR5tFFLjBbtFktDpHL3AdGBH3OYidNGKBO3t
+                        J3Ms7LeKXsM0+0Y4P+9fHZINL2X3E2N6GVnKs5PZTg9sP0FtIpAbYm/+zCx7Yj1E
+                        T/Er8mDd6tNVGSQsn9s5xUBwGqn14wIDAQABo4GnMIGkMB0GA1UdDgQWBBSiQhSu
+                        p9BYjD2ZuMkEiQK7w/Zq0TB1BgNVHSMEbjBsgBSiQhSup9BYjD2ZuMkEiQK7w/Zq
+                        0aFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
+                        BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPmsD+VGldyPMAwGA1UdEwQF
+                        MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHZmIo9GBTSsD5DJfKkCVUvBafwR089H
+                        BkgVPOuVuEe803BRlKd4BVIsuxAUAy3oqdJYqf9ptPEx8Ef+ALbcDhRbWINhMgO7
+                        0/S4x3pS9gOn7/Y9yZplOe4Jd2q3R8QBef+hKLcD/Uv0Sqy2nilM8BnMga5tqsL+
+                        8oFt0blzXtQ2vcOVyNyG326uZBZv2Cf6FXFsYQX1L/tLeTBJegefgGkg2dqCTKIU
+                        1Qy/Kd2P3/S01kQxjDeG7UfXc9qtelJ68kvzK2d3WOJ2qmsdMxjMNfTItP7FO54M
+                        i8V7gp9HK+EimdSbgu7xktKlrqA2Rsn+dBoPSgOUs/LOGtCS9/biF0w=
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </md:KeyDescriptor>
+        <md:KeyDescriptor use="encryption">
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIDtTCCAp2gAwIBAgIJAPmsD+VGldyPMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+                        BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+                        aWRnaXRzIFB0eSBMdGQwHhcNMTQwNDExMTMzOTE4WhcNMjQwNDA4MTMzOTE4WjBF
+                        MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+                        ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+                        CgKCAQEAxg0TyQAP/tIvOH89EtaXuRRn8SYzTj7W1TbNY4VvBmobjkRmSkki4hH9
+                        x4sQpi635wn6WtXTN/FNNmkTK3N/LspmBWxfZS+n+cc7I82E5yvCAPX67QsZgqgg
+                        lp2W5dvK/FsMMCS6X6SVqzBLMP88NenXKxY+HMxMs0sT0UKYh1cAEqadrHRBO65a
+                        DBcm5a0sBVYt9K6pgaOHrp/zSIbhnR5tFFLjBbtFktDpHL3AdGBH3OYidNGKBO3t
+                        J3Ms7LeKXsM0+0Y4P+9fHZINL2X3E2N6GVnKs5PZTg9sP0FtIpAbYm/+zCx7Yj1E
+                        T/Er8mDd6tNVGSQsn9s5xUBwGqn14wIDAQABo4GnMIGkMB0GA1UdDgQWBBSiQhSu
+                        p9BYjD2ZuMkEiQK7w/Zq0TB1BgNVHSMEbjBsgBSiQhSup9BYjD2ZuMkEiQK7w/Zq
+                        0aFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
+                        BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPmsD+VGldyPMAwGA1UdEwQF
+                        MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHZmIo9GBTSsD5DJfKkCVUvBafwR089H
+                        BkgVPOuVuEe803BRlKd4BVIsuxAUAy3oqdJYqf9ptPEx8Ef+ALbcDhRbWINhMgO7
+                        0/S4x3pS9gOn7/Y9yZplOe4Jd2q3R8QBef+hKLcD/Uv0Sqy2nilM8BnMga5tqsL+
+                        8oFt0blzXtQ2vcOVyNyG326uZBZv2Cf6FXFsYQX1L/tLeTBJegefgGkg2dqCTKIU
+                        1Qy/Kd2P3/S01kQxjDeG7UfXc9qtelJ68kvzK2d3WOJ2qmsdMxjMNfTItP7FO54M
+                        i8V7gp9HK+EimdSbgu7xktKlrqA2Rsn+dBoPSgOUs/LOGtCS9/biF0w=
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </md:KeyDescriptor>
+        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"        Location="https://idp.tests.shibboleth.net/idp/profile/SAML2/SOAP/Redirect/SLO" />
+        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"            Location="https://idp.tests.shibboleth.net/idp/profile/SAML2/POST/SLO" />
+        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://idp.tests.shibboleth.net/idp/profile/SAML2/POST-SimpleSign/SSO" />
+        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"        Location="https://idp.tests.shibboleth.net/idp/profile/SAML2/Redirect/SSO" />
+        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"            Location="https://idp.tests.shibboleth.net/idp/profile/SAML2/POST/SSO" />
+    </md:IDPSSODescriptor>
+</md:EntityDescriptor>
diff --git a/src/test/docker/shib-tests-sp/etc/shibboleth/shibboleth2.xml b/src/test/docker/shib-tests-sp/etc/shibboleth/shibboleth2.xml
new file mode 100644
index 0000000..c389e87
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/shibboleth/shibboleth2.xml
@@ -0,0 +1,116 @@
+<SPConfig xmlns="urn:mace:shibboleth:3.0:native:sp:config"
+    xmlns:conf="urn:mace:shibboleth:3.0:native:sp:config"
+    clockSkew="180">
+
+    <OutOfProcess tranLogFormat="%u|%s|%IDP|%i|%ac|%t|%attr|%n|%b|%E|%S|%SS|%L|%UA|%a" />
+  
+    <!--
+    By default, in-memory StorageService, ReplayCache, ArtifactMap, and SessionCache
+    are used. See example-shibboleth2.xml for samples of explicitly configuring them.
+    -->
+
+    <!-- The ApplicationDefaults element is where most of Shibboleth's SAML bits are defined. -->
+    <ApplicationDefaults entityID="https://sp.tests.shibboleth.net/shibboleth"
+        REMOTE_USER="eppn subject-id pairwise-id persistent-id"
+        cipherSuites="DEFAULT:!EXP:!LOW:!aNULL:!eNULL:!DES:!IDEA:!SEED:!RC4:!3DES:!kRSA:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1">
+
+        <!--
+        Controls session lifetimes, address checks, cookie handling, and the protocol handlers.
+        Each Application has an effectively unique handlerURL, which defaults to "/Shibboleth.sso"
+        and should be a relative path, with the SP computing the full value based on the virtual
+        host. Use of TLS is now assumed because browsers are enforcing it due to SameSite
+        restrictions. Note that while we default checkAddress to "false", this makes an assertion
+        stolen in transit easier for attackers to misuse.
+        -->
+       <Sessions lifetime="28800" timeout="3600" relayState="ss:mem"
+                  checkAddress="false" handlerSSL="true" cookieProps="https"
+                  redirectLimit="exact">
+
+            <!--
+            Configures SSO for a default IdP. To properly allow for >1 IdP, remove
+            entityID property and adjust discoveryURL to point to discovery service.
+            You can also override entityID on /Login query string, or in RequestMap/htaccess.
+            -->
+            <SSO entityID="https://idp.example.org">
+              SAML2
+            </SSO>
+
+            <!-- SAML and local-only logout. -->
+            <Logout>SAML2 Local</Logout>
+
+            <!-- Administrative logout. -->
+            <LogoutInitiator type="Admin" Location="/Logout/Admin" acl="127.0.0.1 ::1" />
+          
+            <!-- Extension service that generates "approximate" metadata based on SP configuration. -->
+            <Handler type="MetadataGenerator" Location="/Metadata" signing="false"/>
+
+            <!-- Status reporting service. -->
+            <Handler type="Status" Location="/Status" acl="127.0.0.1 ::1"/>
+
+            <!-- Session diagnostic service. -->
+            <Handler type="Session" Location="/Session" showAttributeValues="false"/>
+
+            <!-- JSON feed of discovery information. -->
+            <Handler type="DiscoveryFeed" Location="/DiscoFeed"/>
+        </Sessions>
+
+        <!--
+        Allows overriding of error template information/filenames. You can
+        also add your own attributes with values that can be plugged into the
+        templates, e.g., helpLocation below.
+        -->
+        <Errors supportContact="root at localhost"
+            helpLocation="/about.html"
+            styleSheet="/shibboleth-sp/main.css"/>
+
+        <!-- Example of locally maintained metadata. -->
+        <!--
+        <MetadataProvider type="XML" validate="true" path="partner-metadata.xml"/>
+        -->
+
+        <!-- Example of remotely supplied batch of signed metadata. -->
+        <!--
+        <MetadataProvider type="XML" validate="true"
+                url="http://federation.org/federation-metadata.xml"
+              backingFilePath="federation-metadata.xml" maxRefreshDelay="7200">
+            <MetadataFilter type="RequireValidUntil" maxValidityInterval="2419200"/>
+            <MetadataFilter type="Signature" certificate="fedsigner.pem" verifyBackup="false"/>
+            <DiscoveryFilter type="Exclude" matcher="EntityAttributes" trimTags="true" 
+              attributeName="http://macedir.org/entity-category"
+              attributeNameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
+              attributeValue="http://refeds.org/category/hide-from-discovery" />
+        </MetadataProvider>
+        -->
+
+        <!-- Example of remotely supplied "on-demand" signed metadata. -->
+        <!--
+        <MetadataProvider type="MDQ" validate="true" cacheDirectory="mdq"
+                baseUrl="http://mdq.federation.org" ignoreTransport="true">
+            <MetadataFilter type="RequireValidUntil" maxValidityInterval="2419200"/>
+            <MetadataFilter type="Signature" certificate="mdqsigner.pem" />
+        </MetadataProvider>
+        -->
+
+        <MetadataProvider type="XML" validate="true" path="idp-metadata.xml"/>
+
+        <!-- Map to extract attributes from SAML assertions. -->
+        <AttributeExtractor type="XML" validate="true" reloadChanges="false" path="attribute-map.xml"/>
+
+        <!-- Default filtering policy for recognized attributes, lets other data pass. -->
+        <AttributeFilter type="XML" validate="true" path="attribute-policy.xml"/>
+
+        <!-- Simple file-based resolvers for separate signing/encryption keys. -->
+        <CredentialResolver type="File" use="signing"
+            key="sp-signing-key.pem" certificate="sp-signing-cert.pem"/>
+        <CredentialResolver type="File" use="encryption"
+            key="sp-encrypt-key.pem" certificate="sp-encrypt-cert.pem"/>
+        
+    </ApplicationDefaults>
+    
+    <!-- Policies that determine how to process and authenticate runtime messages. -->
+    <SecurityPolicyProvider type="XML" validate="true" path="security-policy.xml"/>
+
+    <!-- Low-level configuration about protocols and bindings available for use. -->
+    <ProtocolProvider type="XML" validate="true" reloadChanges="false" path="protocols.xml"/>
+
+</SPConfig>
diff --git a/src/test/docker/shib-tests-sp/etc/shibboleth/sp-encrypt-cert.pem b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-encrypt-cert.pem
new file mode 100644
index 0000000..f6c7731
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-encrypt-cert.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAqugAwIBAgIUDvgaHmnHrCSksyAqJoa+1euneUEwDQYJKoZIhvcNAQEL
+BQAwIjEgMB4GA1UEAxMXc3AudGVzdHMuc2hpYmJvbGV0aC5uZXQwHhcNMjUxMTEw
+MjM1NjQwWhcNMzUxMTA4MjM1NjQwWjAiMSAwHgYDVQQDExdzcC50ZXN0cy5zaGli
+Ym9sZXRoLm5ldDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAOdco92D
+1kXYs4jNd10DctSdx07FtWbeSrxzixWpwejOJSLr8i49HD56VMZ1UPO2KxGtZ9F5
+JJ5eEmjzEsiJZcVUbddLpdmb6sGvZnHh4wrAMXS2JgWo55vMv3g2kwfHafYPtRqx
+qCJbfwKHQhDbZRyb/g9fQQr3eEVUSYtO127fdaY0MGe06xSyufQYWtvzz1/D7Z51
+UqTLs6I9N5gKR/+9O3x0v20/Gp1GrMkPpjIcHlAPuNFQolPYhkgYpN/W9mqtx5GR
+V2ybMZyBxlwB567QbgqMJxIoFNtS3YjfVpLPsaBv1Nri2NGOkvWhnfUpV9MieYie
+rV+k4KrsNQ06oc68fl0tjm4OyOu+XwK8tdCIIoV/v/0BMDxGOC3JffZoY/RQci2D
+6GvpUsB2Cr8fBvDS9ACTD/JSkZKj2yGJsnK/Ae6fQAZ+Im3h9+jPQxFhczidDM7s
+BeYAbcGtR0t0WTW2Rxj3vaZVMS/i5tlX8Uo45CbxntloblkDa8yoOBCZSwIDAQAB
+o3EwbzBOBgNVHREERzBFghdzcC50ZXN0cy5zaGliYm9sZXRoLm5ldIYqaHR0cHM6
+Ly9zcC50ZXN0cy5zaGliYm9sZXRoLm5ldC9zaGliYm9sZXRoMB0GA1UdDgQWBBTg
+7u4kVLhSD8vIaLXW7QED1WisSDANBgkqhkiG9w0BAQsFAAOCAYEAfH5/u/5Cj3w/
+lM37NDeUDKkKUH1LdZ5iCjShkKNaKpV91GyRhDAkBEM2pVSkWqa2o0q3SKy/F1Z4
+1B+TcJZHBOo9oVS2PZM1ZoqebEX48y4Cn8VBTLzzhArpGL95z20FFCBOzF+MuCz3
+gDsDLB9ys5Dnm8gIXJCkJL9xDXary/5r9zCBblg8ljusH92EQU0/060ewzTMAFIr
+OAdh1sNHL9IFi9yrV/gp251D6IUOG9yjTv+4ljQo7yay/9QQtBEn3zaWObcALokB
+RejdoTFEpEv7T7lZVCypLM6240Z7Oqt9384/WKm+5UgCZRXTI1aTIKZCox2Ea0x+
+oflSf8a6VEoTG0AYER3d+HInM83fYabsyQX/1KQY//KhzfuoitXHAvS2KY30u/Cp
+J7wiiRz8Nh65HkPwNLDSZLQSFk36+2Si/TykTBE5ExyYJVmife2QPENKCiqq++7m
+8T5RL2vwnhKbdrq4u7Nw7H9D06fBgp3afS2irNbUZgbjZJ7y+qyK
+-----END CERTIFICATE-----
diff --git a/src/test/docker/shib-tests-sp/etc/shibboleth/sp-encrypt-key.pem b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-encrypt-key.pem
new file mode 100644
index 0000000..391f83b
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-encrypt-key.pem
@@ -0,0 +1,40 @@
+-----BEGIN PRIVATE KEY-----
+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
+nxk/cNRlmxe/LmTSmRkvZtU=
+-----END PRIVATE KEY-----
diff --git a/src/test/docker/shib-tests-sp/etc/shibboleth/sp-metadata.xml b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-metadata.xml
new file mode 100644
index 0000000..b556b50
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-metadata.xml
@@ -0,0 +1,120 @@
+<!--
+This is example metadata only. Do *NOT* supply it as is without review,
+and do *NOT* provide it in real time to your partners.
+ -->
+<md:EntityDescriptor entityID="https://sp.tests.shibboleth.net/shibboleth" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="_3282c45ccf4d5b0df797b7729a876ac356c6cfce" >
+
+  <md:Extensions xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport">
+    <alg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
+    <alg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#sha384"/>
+    <alg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+    <alg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#sha224"/>
+    <alg:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2009/xmldsig11#dsa-sha256"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+    <alg:SigningMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/>
+  </md:Extensions>
+
+  <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+    <md:Extensions>
+    <init:RequestInitiator xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+        Binding="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+        Location="https://sp.tests.shibboleth.net/Shibboleth.sso/Login" />
+    </md:Extensions>
+    <md:KeyDescriptor use="signing">
+      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+        <ds:KeyName>https://sp.tests.shibboleth.net/shibboleth</ds:KeyName>
+        <ds:KeyName>sp.tests.shibboleth.net</ds:KeyName>
+        <ds:X509Data>
+          <ds:X509SubjectName>CN=sp.tests.shibboleth.net</ds:X509SubjectName>
+            <ds:X509Certificate>
+MIIEQzCCAqugAwIBAgIUWxJO7XpqR99HZ5XS/OKjb0uXBdQwDQYJKoZIhvcNAQEL
+BQAwIjEgMB4GA1UEAxMXc3AudGVzdHMuc2hpYmJvbGV0aC5uZXQwHhcNMjUxMTEw
+MjM1MTM1WhcNMzUxMTA4MjM1MTM1WjAiMSAwHgYDVQQDExdzcC50ZXN0cy5zaGli
+Ym9sZXRoLm5ldDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAIh2620Q
+sBmwLAKNlVWKg2YD7vAJM1QADb7izMYB9LZSL+IrlmsoxouhKqqHfTBoYzaUZOPU
+l+EW7Udc6m+q4j6GfPguxrmrOWYFfpTla4xHvlcQS/791BNWlYp/l65Onw/70nYO
+a2InA/baRlsS2ty9l/7YxMIrbCwsqPQWOdFfv9xgSThgWn2HNIHdAcc8Qg+W7Gf8
+c/vDB9iPXhFemL1uZq08CcLotu/ACqPC28dlTk/ueSrr+ueceAetDmviiLaufXWB
+tU0ZUETC7KtNT2lO8+wSVlQcaqkec9HZQgzfqZqWHdhQur+/PjKl7s0UCRMMuJBs
+uPWj1ffvzNhCZ+0CgGQEZecn3A9xL1bRncByFot2KPfx8ANllec94HGrml4o/YtH
+FiQDECGA3+VbrpckhauyIIQ1ByxkDusVRTKdrrCtJuxbn2JjaWjLHpiBhCg/WaK/
+Yemq+jpw5r77KmdSSWAHLYCTUj/VBx5tMAQe1fo8oMfLkRdbPXtB/CjF9QIDAQAB
+o3EwbzBOBgNVHREERzBFghdzcC50ZXN0cy5zaGliYm9sZXRoLm5ldIYqaHR0cHM6
+Ly9zcC50ZXN0cy5zaGliYm9sZXRoLm5ldC9zaGliYm9sZXRoMB0GA1UdDgQWBBQU
+uj2YPLnPMhnOeptD8yO7PuEwmDANBgkqhkiG9w0BAQsFAAOCAYEAK2PRFM3K8LuO
+jcYOw9+6fRJhdLfkGKrGDdleu6icCV2b9vDlDy2diQSVrUJuepMRC6gPUTi83Cyu
+D9Kp0UjWgMirFLPlA1prsQM6PtBn8S4z/hKIFpr12bTtyQM+Z3TyIyT6Arxx7UpD
+ySm+FuOn9Fn4OH8EO6wnAWOhHF79raLlZvckUNgxczb5ksZp2x1R6UyZK1kDgNUc
+fpBw8WuiZQ6fbhE5md/5K6L8it6n2GrbRtDWeKouoRMIp7MQ/vQe/9LXR2sVBY60
+nq2D30eCr19kx+mQJLZvaGqJsi1D+oV79QYPhyXMYvolG7oBf+z509grxxWygjZ8
+XPJF8N3DJYm0zzwXvtos5U36lujzw/eQVfS/QuYl3CclIInB/ndUmg/wGosB2quG
+lxQlEqq6zZC8ERVndba4YWX/oodYgtgmBjdE3dt5uXih744atAC8Km8oWMR4PLJ/
+F8rqbS6FLy/KUIsziY8yoQtIOsnpDqGvZp8cV8OlHfJ2h3ppx2yb
+            </ds:X509Certificate>
+        </ds:X509Data>
+      </ds:KeyInfo>
+    </md:KeyDescriptor>
+    <md:KeyDescriptor use="encryption">
+      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+        <ds:KeyName>https://sp.tests.shibboleth.net/shibboleth</ds:KeyName>
+        <ds:KeyName>sp.tests.shibboleth.net</ds:KeyName>
+        <ds:X509Data>
+          <ds:X509SubjectName>CN=sp.tests.shibboleth.net</ds:X509SubjectName>
+            <ds:X509Certificate>
+MIIEQzCCAqugAwIBAgIUDvgaHmnHrCSksyAqJoa+1euneUEwDQYJKoZIhvcNAQEL
+BQAwIjEgMB4GA1UEAxMXc3AudGVzdHMuc2hpYmJvbGV0aC5uZXQwHhcNMjUxMTEw
+MjM1NjQwWhcNMzUxMTA4MjM1NjQwWjAiMSAwHgYDVQQDExdzcC50ZXN0cy5zaGli
+Ym9sZXRoLm5ldDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAOdco92D
+1kXYs4jNd10DctSdx07FtWbeSrxzixWpwejOJSLr8i49HD56VMZ1UPO2KxGtZ9F5
+JJ5eEmjzEsiJZcVUbddLpdmb6sGvZnHh4wrAMXS2JgWo55vMv3g2kwfHafYPtRqx
+qCJbfwKHQhDbZRyb/g9fQQr3eEVUSYtO127fdaY0MGe06xSyufQYWtvzz1/D7Z51
+UqTLs6I9N5gKR/+9O3x0v20/Gp1GrMkPpjIcHlAPuNFQolPYhkgYpN/W9mqtx5GR
+V2ybMZyBxlwB567QbgqMJxIoFNtS3YjfVpLPsaBv1Nri2NGOkvWhnfUpV9MieYie
+rV+k4KrsNQ06oc68fl0tjm4OyOu+XwK8tdCIIoV/v/0BMDxGOC3JffZoY/RQci2D
+6GvpUsB2Cr8fBvDS9ACTD/JSkZKj2yGJsnK/Ae6fQAZ+Im3h9+jPQxFhczidDM7s
+BeYAbcGtR0t0WTW2Rxj3vaZVMS/i5tlX8Uo45CbxntloblkDa8yoOBCZSwIDAQAB
+o3EwbzBOBgNVHREERzBFghdzcC50ZXN0cy5zaGliYm9sZXRoLm5ldIYqaHR0cHM6
+Ly9zcC50ZXN0cy5zaGliYm9sZXRoLm5ldC9zaGliYm9sZXRoMB0GA1UdDgQWBBTg
+7u4kVLhSD8vIaLXW7QED1WisSDANBgkqhkiG9w0BAQsFAAOCAYEAfH5/u/5Cj3w/
+lM37NDeUDKkKUH1LdZ5iCjShkKNaKpV91GyRhDAkBEM2pVSkWqa2o0q3SKy/F1Z4
+1B+TcJZHBOo9oVS2PZM1ZoqebEX48y4Cn8VBTLzzhArpGL95z20FFCBOzF+MuCz3
+gDsDLB9ys5Dnm8gIXJCkJL9xDXary/5r9zCBblg8ljusH92EQU0/060ewzTMAFIr
+OAdh1sNHL9IFi9yrV/gp251D6IUOG9yjTv+4ljQo7yay/9QQtBEn3zaWObcALokB
+RejdoTFEpEv7T7lZVCypLM6240Z7Oqt9384/WKm+5UgCZRXTI1aTIKZCox2Ea0x+
+oflSf8a6VEoTG0AYER3d+HInM83fYabsyQX/1KQY//KhzfuoitXHAvS2KY30u/Cp
+J7wiiRz8Nh65HkPwNLDSZLQSFk36+2Si/TykTBE5ExyYJVmife2QPENKCiqq++7m
+8T5RL2vwnhKbdrq4u7Nw7H9D06fBgp3afS2irNbUZgbjZJ7y+qyK
+            </ds:X509Certificate>
+        </ds:X509Data>
+      </ds:KeyInfo>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes192-gcm"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes256-gcm"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#rsa-oaep"/>
+      <md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
+    </md:KeyDescriptor>
+    <md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"                 Location="https://sp.tests.shibboleth.net/Shibboleth.sso/Artifact/SOAP" />
+    <md:SingleLogoutService       Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"                 Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SLO/SOAP"/>
+    <md:SingleLogoutService       Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"        Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SLO/Redirect"/>
+    <md:SingleLogoutService       Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"            Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SLO/POST"/>
+    <md:SingleLogoutService       Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"        Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SLO/Artifact"/>
+    <md:AssertionConsumerService  Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"            Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SAML2/POST"            index="1"/>
+    <md:AssertionConsumerService  Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SAML2/POST-SimpleSign" index="2"/>
+    <md:AssertionConsumerService  Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"        Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SAML2/Artifact"        index="3"/>
+    <md:AssertionConsumerService  Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"                 Location="https://sp.tests.shibboleth.net/Shibboleth.sso/SAML2/ECP"             index="4"/>
+  </md:SPSSODescriptor>
+
+</md:EntityDescriptor>
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-sp/etc/shibboleth/sp-signing-cert.pem b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-signing-cert.pem
new file mode 100644
index 0000000..7e1a730
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-signing-cert.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAqugAwIBAgIUWxJO7XpqR99HZ5XS/OKjb0uXBdQwDQYJKoZIhvcNAQEL
+BQAwIjEgMB4GA1UEAxMXc3AudGVzdHMuc2hpYmJvbGV0aC5uZXQwHhcNMjUxMTEw
+MjM1MTM1WhcNMzUxMTA4MjM1MTM1WjAiMSAwHgYDVQQDExdzcC50ZXN0cy5zaGli
+Ym9sZXRoLm5ldDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAIh2620Q
+sBmwLAKNlVWKg2YD7vAJM1QADb7izMYB9LZSL+IrlmsoxouhKqqHfTBoYzaUZOPU
+l+EW7Udc6m+q4j6GfPguxrmrOWYFfpTla4xHvlcQS/791BNWlYp/l65Onw/70nYO
+a2InA/baRlsS2ty9l/7YxMIrbCwsqPQWOdFfv9xgSThgWn2HNIHdAcc8Qg+W7Gf8
+c/vDB9iPXhFemL1uZq08CcLotu/ACqPC28dlTk/ueSrr+ueceAetDmviiLaufXWB
+tU0ZUETC7KtNT2lO8+wSVlQcaqkec9HZQgzfqZqWHdhQur+/PjKl7s0UCRMMuJBs
+uPWj1ffvzNhCZ+0CgGQEZecn3A9xL1bRncByFot2KPfx8ANllec94HGrml4o/YtH
+FiQDECGA3+VbrpckhauyIIQ1ByxkDusVRTKdrrCtJuxbn2JjaWjLHpiBhCg/WaK/
+Yemq+jpw5r77KmdSSWAHLYCTUj/VBx5tMAQe1fo8oMfLkRdbPXtB/CjF9QIDAQAB
+o3EwbzBOBgNVHREERzBFghdzcC50ZXN0cy5zaGliYm9sZXRoLm5ldIYqaHR0cHM6
+Ly9zcC50ZXN0cy5zaGliYm9sZXRoLm5ldC9zaGliYm9sZXRoMB0GA1UdDgQWBBQU
+uj2YPLnPMhnOeptD8yO7PuEwmDANBgkqhkiG9w0BAQsFAAOCAYEAK2PRFM3K8LuO
+jcYOw9+6fRJhdLfkGKrGDdleu6icCV2b9vDlDy2diQSVrUJuepMRC6gPUTi83Cyu
+D9Kp0UjWgMirFLPlA1prsQM6PtBn8S4z/hKIFpr12bTtyQM+Z3TyIyT6Arxx7UpD
+ySm+FuOn9Fn4OH8EO6wnAWOhHF79raLlZvckUNgxczb5ksZp2x1R6UyZK1kDgNUc
+fpBw8WuiZQ6fbhE5md/5K6L8it6n2GrbRtDWeKouoRMIp7MQ/vQe/9LXR2sVBY60
+nq2D30eCr19kx+mQJLZvaGqJsi1D+oV79QYPhyXMYvolG7oBf+z509grxxWygjZ8
+XPJF8N3DJYm0zzwXvtos5U36lujzw/eQVfS/QuYl3CclIInB/ndUmg/wGosB2quG
+lxQlEqq6zZC8ERVndba4YWX/oodYgtgmBjdE3dt5uXih744atAC8Km8oWMR4PLJ/
+F8rqbS6FLy/KUIsziY8yoQtIOsnpDqGvZp8cV8OlHfJ2h3ppx2yb
+-----END CERTIFICATE-----
diff --git a/src/test/docker/shib-tests-sp/etc/shibboleth/sp-signing-key.pem b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-signing-key.pem
new file mode 100644
index 0000000..ae38cad
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/shibboleth/sp-signing-key.pem
@@ -0,0 +1,40 @@
+-----BEGIN PRIVATE KEY-----
+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
+/8YtuGpO29UNN3mY+zWqwG8=
+-----END PRIVATE KEY-----
diff --git a/src/test/docker/shib-tests-sp/etc/supervisord.conf b/src/test/docker/shib-tests-sp/etc/supervisord.conf
new file mode 100644
index 0000000..d91eab5
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/supervisord.conf
@@ -0,0 +1,32 @@
+[supervisord]
+logfile=/var/log/supervisor/supervisord.log
+nodaemon=true
+user=root
+
+[supervisorctl]
+serverurl=unix:///run/supervisor/supervisor.sock
+username=nobody
+password=password
+
+[unix_http_server]
+file=/run/supervisor/supervisor.sock
+username=nobody
+password=password
+
+[program:httpd]
+command=httpd -DFOREGROUND
+autostart=true
+autorestart=true
+user=apache
+
+[program:shibd]
+command=/usr/sbin/shibd -f -F
+autostart=true
+autorestart=true
+user=shibd
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[include]
+files = supervisord.d/*.ini
diff --git a/src/test/docker/shib-tests-sp/etc/yum.repos.d/shibboleth.repo b/src/test/docker/shib-tests-sp/etc/yum.repos.d/shibboleth.repo
new file mode 100644
index 0000000..2566410
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/etc/yum.repos.d/shibboleth.repo
@@ -0,0 +1,9 @@
+[shibboleth]
+name=Shibboleth (rockylinux9)
+# Please report any problems to https://shibboleth.atlassian.net/jira
+type=rpm-md
+mirrorlist=https://shibboleth.net/cgi-bin/mirrorlist.cgi/rockylinux9
+gpgcheck=1
+gpgkey=https://shibboleth.net/downloads/service-provider/RPMS/repomd.xml.key
+       https://shibboleth.net/downloads/service-provider/RPMS/cantor.repomd.xml.key
+enabled=1
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-sp/var/www/cgi-bin/printenv b/src/test/docker/shib-tests-sp/var/www/cgi-bin/printenv
new file mode 100644
index 0000000..c15db75
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/var/www/cgi-bin/printenv
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+
+# To permit this cgi, replace # on the first line above with the
+# appropriate #!/path/to/perl shebang, and on Unix / Linux also
+# set this script executable with chmod 755.
+#
+# ***** !!! WARNING !!! *****
+# This script echoes the server environment variables and therefore
+# leaks information - so NEVER use it in a live server environment!
+# It is provided only for testing purpose.
+# Also note that it is subject to cross site scripting attacks on
+# MS IE and any other browser which fails to honor RFC2616.
+
+##
+##  printenv -- demo CGI program which just prints its environment
+##
+use strict;
+use warnings;
+
+print "Content-type: text/plain; charset=iso-8859-1\n\n";
+foreach my $var (sort(keys(%ENV))) {
+    my $val = $ENV{$var};
+    $val =~ s|\n|\\n|g;
+    $val =~ s|"|\\"|g;
+    print "${var}=\"${val}\"\n";
+}
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-sp/var/www/html/index.html b/src/test/docker/shib-tests-sp/var/www/html/index.html
new file mode 100644
index 0000000..767a6e7
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/var/www/html/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Hello World</title>
+</head>
+<body>
+Hello world
+</body>
+</html>
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-sp/var/www/html/secure/index.html b/src/test/docker/shib-tests-sp/var/www/html/secure/index.html
new file mode 100644
index 0000000..020bf1f
--- /dev/null
+++ b/src/test/docker/shib-tests-sp/var/www/html/secure/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Secure</title>
+</head>
+<body>
+Secure
+</body>
+</html>
\ No newline at end of file
diff --git a/src/test/java/net/shibboleth/idp/integration/tests/consent/PassiveConsentTest.java b/src/test/java/net/shibboleth/idp/integration/tests/consent/PassiveConsentTest.java
new file mode 100644
index 0000000..ef8a070
--- /dev/null
+++ b/src/test/java/net/shibboleth/idp/integration/tests/consent/PassiveConsentTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.idp.integration.tests.consent;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.integration.tests.saml2.AbstractSAML2IntegrationTest;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.net.URISupport;
+
+/**
+ * Test consent with isPassive=true and allow global consent both true and false.
+ */
+public class PassiveConsentTest extends AbstractSAML2IntegrationTest {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PassiveConsentTest.class);
+
+    /** RP Docker containers. */
+    @Nonnull protected final List<SPContainer> sps = new ArrayList<>();
+
+    @BeforeClass
+    public void setUpURLs() throws Exception {
+
+        loginPageURLPath = "/idp/profile/SAML2/Redirect/SSO";
+
+        responsePageURLPath = "/sp/SAML2/POST/ACS";
+    }
+
+    @Test
+    /**
+     * Test consent with isPassive=true and global consent allowed (the default).
+     * 
+     * @throws Exception
+     *             if an error occurs
+     */
+    public void testIdP2409() throws Exception {
+
+        // Start SP
+
+        final SPContainer sp = startSP("shib-test-sp");
+
+        // Set up SP metadata with runtime port
+
+        setUpSPMetadata(sp.httpsPort);
+
+        // Start browser
+
+        startBrowser();
+
+        // Start IdP
+
+        startServer();
+
+        // Start flow at SP
+
+        final String target = sp.getBaseURL() + "/cgi-bin/printenv";
+
+        final String startFlowURL = sp.getBaseURL() + "/Shibboleth.sso/Login?target=" + URISupport.doURLEncode(target);
+
+        driver.get(startFlowURL);
+
+        // login
+
+        waitForLoginPage();
+
+        login();
+
+        // attribute release
+
+        waitForAttributeReleasePage();
+
+        // start flow again with isPassive=true
+
+        driver.get(startFlowURL + "&isPassive=true");
+
+        // should see NoPassive error
+
+        waitForPageBodyContains("Status: urn:oasis:names:tc:SAML:2.0:status:Requester");
+        waitForPageBodyContains("Sub-Status: urn:oasis:names:tc:SAML:2.0:status:NoPassive");
+        waitForPageBodyContains("Message: An error occurred");
+    }
+
+    @Test(enabled = false)
+    /**
+     * Test consent with isPassive=true and global consent not allowed.
+     * 
+     * @throws Exception
+     *             if an error occurs
+     */
+    public void testIdP2409AllowGlobalFalse() throws Exception {
+
+        // Do not allow global consent
+
+        replaceIdPProperty("idp.consent.allowGlobal", "false");
+
+        testIdP2409();
+    }
+
+    /**
+     * Set up metadata about/for the SP.
+     * 
+     * Copies SP metadata to idp.home/metadata/sp-metadata.xml
+     * 
+     * Updates port in SP metadata.
+     * 
+     * Add SP metadata to the IdP.
+     * 
+     * @throws Exception
+     *             if an error occurs
+     */
+
+    public void setUpSPMetadata(@Nonnull final Integer port) throws Exception {
+
+        // Copy SP metadata to idp.home
+
+        final Path pathToDockerDir = Paths.get("src", "test", "docker");
+
+        final Path pathToImage = pathToDockerDir.resolve("shib-tests-sp");
+
+        final Path pathToImageSPMetadata = pathToImage.resolve(Paths.get("etc", "shibboleth", "sp-metadata.xml"));
+
+        final Path pathToSPMetadata = pathToIdPHome.toAbsolutePath().resolve("metadata").resolve("sp-metadata.xml");
+
+        Files.copy(pathToImageSPMetadata, pathToSPMetadata);
+
+        // Update port in SP metadata
+
+        replaceIdPHomeFile(pathToSPMetadata, //
+                "Location=\"https://sp.tests.shibboleth.net/",
+                "Location=\"https://sp.tests.shibboleth.net:" + port + "/");
+
+        // Enable SP metadata
+
+        final Path pathToMetadataProvidersXML = Paths.get("conf", "metadata-providers.xml");
+
+        final String oldText = "</MetadataProvider>";
+
+        final String newText = "<MetadataProvider id=\"SP\" xsi:type=\"FilesystemMetadataProvider\" metadataFile=\"%{idp.home}/metadata/sp-metadata.xml\" />"
+                + System.lineSeparator() + "</MetadataProvider>";
+
+        replaceIdPHomeFile(pathToMetadataProvidersXML, oldText, newText);
+    }
+
+    /**
+     * Set path to SP keystore to be credentials/idp-userfacing.p12 in either jetty-base/ or tomcat-base/ directories.
+     * 
+     * @param sp
+     *            the SP container
+     */
+    protected void setUpSKeyStore(final SPContainer sp) {
+
+        final Path pathToContainerBase = pathToJettyBase != null ? pathToJettyBase : pathToTomcatBase;
+
+        final Path pathToKeyStore = pathToContainerBase.resolve((Paths.get("credentials", "idp-userfacing.p12")));
+
+        log.debug("Path to key store '{}'", pathToKeyStore);
+
+        sp.setKeyStore(pathToKeyStore.toAbsolutePath().toString());
+    }
+
+    /**
+     * Start SP.
+     * 
+     * @param id
+     *            the id for logging
+     * @return the SP container
+     * @throws ComponentInitializationException
+     *             if an error occurs
+     */
+    protected SPContainer startSP(@Nonnull final String id) throws ComponentInitializationException {
+
+        final SPContainer sp = new SPContainer();
+
+        sps.add(sp);
+
+        sp.setId(id);
+
+        sp.setIdPMetadata(pathToIdPHome.resolve(Paths.get("metadata", "idp-metadata.xml")));
+
+        setUpSKeyStore(sp);
+
+        sp.initialize();
+
+        sp.start();
+
+        return sp;
+    }
+
+    /**
+     * Stop all SPs.
+     */
+    @AfterMethod()
+    protected void stopSPs() {
+        for (final SPContainer sp : sps) {
+            if (sp != null && sp.isRunning()) {
+                sp.stop();
+            }
+        }
+    }
+}
diff --git a/src/test/java/net/shibboleth/idp/integration/tests/consent/SPContainer.java b/src/test/java/net/shibboleth/idp/integration/tests/consent/SPContainer.java
new file mode 100644
index 0000000..6281a75
--- /dev/null
+++ b/src/test/java/net/shibboleth/idp/integration/tests/consent/SPContainer.java
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development, 
+ * Inc. (UCAID) under one or more contributor license agreements.  See the 
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.idp.integration.tests.consent;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.Lifecycle;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.builder.ImageFromDockerfile;
+import org.testcontainers.images.builder.Transferable;
+import org.testcontainers.utility.MountableFile;
+
+import net.shibboleth.idp.integration.tests.BaseTest;
+import net.shibboleth.idp.integration.tests.util.CertificateHelper;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Start and stop a Shibboleth Service Provider (SP) Docker container.
+ * 
+ * Ports are defined after the container is started.
+ * 
+ * Default ServerName is sp.tests.shibboleth.net, may be overridden as system property.
+ * 
+ * The TLS certificate and key for the RP are extracted from a P12 keystore. The path to the keystore must be set and
+ * the password provided as an environment variable.
+ * 
+ * See src/test/docker/shib-tests-rp for Dockerfile and container files.
+ */
+public class SPContainer extends AbstractIdentifiableInitializableComponent implements Lifecycle {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(SPContainer.class);
+
+    /** Cached log prefix. */
+    @Nullable private String logPrefix;
+
+    /** The Docker container. */
+    @NonnullAfterInit public GenericContainer<?> container;
+
+    /** Exposed HTTP port. */
+    @Nullable public Integer httpPort;
+
+    /** Exposed HTTPS port. */
+    @Nullable public Integer httpsPort;
+
+    /** Name of Docker image. */
+    @Nonnull public String imageName = "shib-tests-sp";
+
+    /** IdP hostname or FQDN, defaults to "idp.tests.shibboleth.net" */
+    @Nonnull private String idpHost = "idp.tests.shibboleth.net";
+
+    /** SP hostname or FQDN, defaults to "sp.tests.shibboleth.net" */
+    @Nonnull public String spHost = "sp.tests.shibboleth.net";
+
+    /** Path to TLS keystore in P12 format. */
+    @NonnullAfterInit private Path pathToKeyStore;
+
+    /** Path to the IdP metadata. */
+    @NonnullAfterInit private Path pathToIdPMetadata;
+
+    /**
+     * Path to image directory src/test/docker/{@link #imageName}.
+     * 
+     * @return path to image directory
+     */
+    protected Path pathToImage() {
+
+        final Path pathToImage = Paths.get("src", "test", "docker").resolve(imageName);
+
+        log.trace("{} Path to image '{}' directory '{}'", getLogPrefix(), imageName, pathToImage);
+
+        assert pathToImage.toFile().exists() : "Path to SP image does not exist";
+
+        return pathToImage;
+    }
+
+    /**
+     * Path to Dockerfile src/test/docker/{@link #imageName}/Dockerfile.
+     * 
+     * @return path to Dockerfile
+     */
+    protected Path pathToDockerfile() {
+
+        final Path pathToDockerfile = pathToImage().resolve("Dockerfile");
+
+        log.debug("{} Path to Dockerfile '{}'", getLogPrefix(), pathToDockerfile);
+
+        assert pathToDockerfile.toFile().exists() : "Path to Dockerfile does not exist";
+
+        return pathToDockerfile;
+    }
+
+    /**
+     * Set up container.
+     * 
+     * Copy TLS cert and key into container.
+     * 
+     * Copy IdP metadata to container.
+     * 
+     * Allow access to IdP on host.
+     * 
+     * Set container name and hostname to id.
+     * 
+     * Expose ports 80 and 443.
+     * 
+     * {@inheritDoc}
+     */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+
+        super.doInitialize();
+
+        if (pathToIdPMetadata == null) {
+            throw new ComponentInitializationException("Path to IdP metadata cannot be null");
+        }
+
+        if (pathToKeyStore == null) {
+            throw new ComponentInitializationException("Path to keystore cannot be null");
+        }
+
+        final String keyStorePassword = System.getenv(BaseTest.KEYSTORE_PASSWORD_ENV_VAR);
+        if (keyStorePassword == null) {
+            throw new ComponentInitializationException("Keystore password environment variable not set");
+        }
+
+        log.debug("{} Initializing", getLogPrefix());
+
+        // Do not delete image on exit
+        final ImageFromDockerfile image = new ImageFromDockerfile(imageName, false).withDockerfile(pathToDockerfile());
+        log.debug("{} Initializing with image '{}'", getLogPrefix(), image);
+        container = new GenericContainer<>(image);
+
+        // Copy TLS certificate and key to container from P12 keystore
+        final Pair<String, String> certAndKey = CertificateHelper.extractCertAndKey(pathToKeyStore, keyStorePassword);
+        container.withCopyToContainer(Transferable.of(certAndKey.getFirst()), "/etc/pki/tls/certs/localhost.crt");
+        container.withCopyToContainer(Transferable.of(certAndKey.getSecond()), "/etc/pki/tls/private/localhost.key");
+
+        // Copy OpenID Connect configuration to container
+        final MountableFile idpMetadata = MountableFile.forHostPath(pathToIdPMetadata);
+        container.withCopyFileToContainer(idpMetadata, "/etc/shibboleth/idp-metadata.xml");
+
+        // Add access to the IdP / OP
+        container.withAccessToHost(true);
+
+        // Add DNS resolution for the IdP / OP
+        container.withExtraHost(idpHost, "host-gateway");
+
+        // Set container name to the RP id
+        container.withCreateContainerCmdModifier(cmd -> cmd.withName(getId()));
+
+        // Set container hostname to the SP host name
+        container.withCreateContainerCmdModifier(cmd -> cmd.withHostName(spHost));
+
+        // Set 'ServerName' environment variable to the RP host name
+        container.withEnv("ServerName", spHost);
+
+        // Expose ports 80 and 443
+        container.addExposedPort(80);
+        container.addExposedPort(443);
+
+        log.debug("{} Initialized", getLogPrefix());
+    }
+
+    /**
+     * Get base URL of the form "https://<id>:<port>".
+     * 
+     * For example : "https://sp.tests.shibboleth.net:<port>"
+     * 
+     * The host name / FQDN is set via {@link #setSPHost(String)}.
+     * 
+     * @return base URL
+     */
+    public String getBaseURL() {
+        return "https://" + spHost + ":" + httpsPort.toString();
+    }
+
+    /**
+     * Set the ID to the FQDN of the SP.
+     * 
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setId(String componentId) {
+        super.setId(componentId);
+    }
+
+    /**
+     * Set the hostname or FQDN of the SP.
+     * 
+     * @param host
+     *            SP host
+     */
+    public void setSPHost(@Nonnull final String host) {
+        Constraint.isNotNull(host, "SP host cannot be null");
+        spHost = host;
+    }
+
+    /**
+     * Set the path to the TLS keystore.
+     * 
+     * @param path
+     *            path to TLS keystore
+     */
+    public void setKeyStore(@Nonnull final String path) {
+        pathToKeyStore = Paths.get(path);
+        log.debug("{} Path to TLS keystore '{}'", getLogPrefix(), pathToKeyStore);
+        assert pathToKeyStore.toFile().exists() : "Path to TLS keystore " + path + " does not exist";
+    }
+
+    /**
+     * Set the path to the metadata for/about the IdP.
+     * 
+     * @param path
+     *            path to IdP metadata
+     */
+    public void setIdPMetadata(@Nonnull final Path path) {
+        pathToIdPMetadata = path;
+        log.debug("{} Path to IdP metdata '{}'", getLogPrefix(), pathToIdPMetadata);
+        assert pathToIdPMetadata.toFile().exists() : "Path to IdP metadata " + pathToIdPMetadata + " does not exist";
+    }
+
+    /**
+     * Start container and wait for web server to be available.
+     * 
+     * Set {@link #httpPort} and {@link #httpsPort} from {@link GenericContainer#getMappedPort()}.
+     * 
+     * {@inheritDoc}
+     */
+    @Override
+    public void start() {
+
+        log.info("{} Starting ...", getLogPrefix());
+        container.start();
+        log.debug("{} Started container : '{}'", getLogPrefix(), container.getContainerName());
+
+        httpPort = container.getMappedPort(80);
+        log.debug("{} HTTP  port {}", getLogPrefix(), httpPort);
+
+        httpsPort = container.getMappedPort(443);
+        log.debug("{} HTTPS port {}", getLogPrefix(), httpsPort);
+
+        log.debug("{} Waiting for \"/\" to be available ...", getLogPrefix());
+        container.waitingFor(Wait.forHttp("/"));
+        container.waitingFor(Wait.forHttps("/"));
+
+        log.info("{} Started", getLogPrefix());
+    }
+
+    @Override
+    public void stop() {
+        log.debug("{} Stopping ...", getLogPrefix());
+        container.stop();
+        log.info("{} Stopped", getLogPrefix());
+    }
+
+    @Override
+    public boolean isRunning() {
+        boolean isRunning = container.isRunning();
+        log.info("{} Is running '{}'", getLogPrefix(), isRunning);
+        return isRunning;
+    }
+
+    /**
+     * Return a prefix for logging messages for this component.
+     * 
+     * @return a string for insertion at the beginning of any log messages
+     */
+    @Nonnull
+    @NotEmpty
+    protected String getLogPrefix() {
+        if (logPrefix == null) {
+            logPrefix = "SP '" + getId() + "' :";
+        }
+        assert logPrefix != null;
+        return logPrefix;
+    }
+}

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


More information about the commits mailing list