[java-shib-shared] branch main updated: JSSH-61 - Add a captive Jetty for the tests

Codeberg noreply at shibboleth.net
Tue Dec 16 16:08:20 UTC 2025


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

codeberg pushed a commit to branch main
in repository java-shib-shared.

View the commit online:
https://codeberg.org/Shibboleth/java-shib-shared/commit/23d044e0f0ee8947fffefadad274139f4579fbbb

The following commit(s) were added to refs/heads/main by this push:
     new 23d044e0 JSSH-61 - Add a captive Jetty for the tests
23d044e0 is described below

commit 23d044e0f0ee8947fffefadad274139f4579fbbb
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Dec 16 09:01:05 2025 -0700

    JSSH-61 - Add a captive Jetty for the tests
    
    https://shibboleth.atlassian.net/browse/JSSH-61
    
    Initial cut at this supporting server a single resource.
---
 shib-testing/pom.xml                               |  12 ++
 .../shibboleth/shared/testing/EmbeddedJetty.java   | 221 +++++++++++++++++++++
 .../shared/testing/credentials/localhost.key       |  40 ++++
 .../shared/testing/credentials/localhost.p12       | Bin 0 -> 3459 bytes
 .../shared/testing/credentials/localhost.pem       |  26 +++
 5 files changed, 299 insertions(+)

diff --git a/shib-testing/pom.xml b/shib-testing/pom.xml
index 4c245ad7..bc746c62 100644
--- a/shib-testing/pom.xml
+++ b/shib-testing/pom.xml
@@ -17,6 +17,7 @@
     <properties>
         <automatic.module.name>net.shibboleth.shared.testing</automatic.module.name>
         <checkstyle.configLocation>${project.basedir}/../resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
+        <jetty.version>12.1.5</jetty.version>
     </properties>
 
     <dependencies>
@@ -72,6 +73,17 @@
             <artifactId>velocity-engine-core</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>${jetty.groupId}</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>${jetty.groupId}</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+
         <!-- Provided Dependencies -->
 
         <!-- Runtime Dependencies -->
diff --git a/shib-testing/src/main/java/net/shibboleth/shared/testing/EmbeddedJetty.java b/shib-testing/src/main/java/net/shibboleth/shared/testing/EmbeddedJetty.java
new file mode 100644
index 00000000..c0e70ac4
--- /dev/null
+++ b/shib-testing/src/main/java/net/shibboleth/shared/testing/EmbeddedJetty.java
@@ -0,0 +1,221 @@
+/*
+ * 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.shared.testing;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.SystemDefaultDnsResolver;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.springframework.core.io.ClassPathResource;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.shared.spring.resource.ResourceHelper;
+
+/**
+ * Testing fixture using Jetty to server a specified handler.
+ */
+public class EmbeddedJetty {
+
+    /** Addresses to listen on. */
+    private List<InetAddress> jettyListenAddrs;
+    
+    /** TLS port */
+    private int port = 8443;
+    
+    /** Captures server. */
+    @Nullable Server jettyServer;
+    
+    /**
+     * Sets the TLS port to listen on.
+     * 
+     * <p>Defaults to 8443.</p>
+     * 
+     * @param p port
+     */
+    public void setPort(final int p) {
+        port = p;
+    }
+    
+    /**
+     * Starts embedded server to serve specified test content.
+     * 
+     * <p>The caller is expected to call {@link #stopServer} when finished.</p>
+     * 
+     * @param handler
+     * 
+     * @throws UnknownHostException
+     * @throws SocketException
+     */
+    public void startServer(final Handler handler) throws UnknownHostException, SocketException {
+        final Server server = new Server();
+
+        final var sslContextFactory = new SslContextFactory.Server();
+        sslContextFactory.setKeyStoreType("PKCS12");
+        sslContextFactory.setKeyStoreResource(ResourceFactory.of(server).newClassLoaderResource("/net/shibboleth/shared/testing/credentials/localhost.p12"));
+        sslContextFactory.setKeyStorePassword("changeit");
+        
+        resolveListenAddresses();
+        ArrayList<ServerConnector> connectors = new ArrayList<>();
+        jettyListenAddrs.forEach(addr ->  {
+            final ServerConnector connector = new ServerConnector(server, sslContextFactory);
+            connector.setHost(addr.getHostAddress());
+            connector.setPort(port);
+            connectors.add(connector);
+        });
+        server.setConnectors(connectors.toArray(new Connector[] {}));
+
+        server.setHandler(handler);
+        try {
+            server.start();
+        } catch (final Exception e) {
+            try {
+                server.stop();
+            } catch (final Exception e2) {}
+            throw new RuntimeException("Jetty startup failed", e);
+        }
+        final Thread serverRunner = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    server.join();
+                } catch (final InterruptedException e) {}
+            }
+        });
+        serverRunner.start();
+        jettyServer = server;
+    }
+    
+    /**
+     * Stop the server if started.
+     */
+    public void stopServer() {
+        if (jettyServer != null) {
+            try {
+                jettyServer.stop();
+            } catch (final Exception e) {
+                throw new RuntimeException("Jetty stop failed", e);
+            }
+        }
+    }
+    
+    /**
+     * Returns the TLS certificate used by the server as a resource in PEM format
+     * 
+     * @return TLS server certificate resource
+     */
+    @Nonnull public static Resource getServerCertficateResource() {
+        return ResourceHelper.of(new ClassPathResource("/net/shibboleth/shared/testing/credentials/localhost.pem"));
+    }    
+    
+    /**
+     * Jetty handler to server content.
+     */
+    public static class ResourceHandler extends Handler.Abstract {
+
+        final int status;
+        
+        @Nonnull final Resource resource;
+        
+        @Nullable String contentType;
+
+        /**
+         * Constructor.
+         *
+         * @param s status
+         * @param r resource to serve
+         */
+        public ResourceHandler(final int s, @Nonnull final Resource r) {
+            status = s;
+            resource = Constraint.isNotNull(r, "Resource cannot be null");
+        }
+        
+        /**
+         * Set value of content type header.
+         * 
+         * @param type content type value
+         */
+        public void setContentType(@Nullable final String type) {
+            contentType = type;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean handle(Request request, Response response, Callback callback) throws Exception {
+            if (contentType != null) {
+                response.getHeaders().add("Content-Type", contentType);
+            }
+            response.setStatus(status);
+            response.write(true, ByteBuffer.wrap(resource.getInputStream().readAllBytes()), callback);
+            return true;
+        }
+    }
+
+    /**
+     * Resolve all applicable localhost interfaces.
+     * 
+     * @throws UnknownHostException 
+     * @throws SocketException 
+     */
+    private void resolveListenAddresses() throws UnknownHostException, SocketException {
+        // As of HttpClient 5.x, we need Jetty to listen on all localhost IPv4 and IPv6 addresses,
+        // b/c on connection failure (e.g. TLS handshake failure) HC will try them all,
+        // so they all have to respond similarly for the tests that expect failure via a specified exception type.
+        // This is an attempt to get them portably depending on whether IPv4 and/or IPv6 is enabled.
+
+        // Resolve the 'localhost' addrs that will be resolved and used by HttpClient
+        final List<InetAddress> localhostAddrs = CollectionSupport.listOf(SystemDefaultDnsResolver.INSTANCE.resolve("localhost"));
+
+        // Resolve available loopback and link-local interfaces
+        // Using ByteBuffer just to get hashcode() and equals() for byte[] for filtering using the Set.
+       final Set<ByteBuffer> interfaceAddrs = Collections.list(NetworkInterface.getNetworkInterfaces()).stream()
+               .map(NetworkInterface::getInterfaceAddresses)
+               .flatMap(Collection::stream)
+               .map(InterfaceAddress::getAddress)
+               .filter(addr -> addr.isLoopbackAddress() || addr.isLinkLocalAddress() )
+               .map(InetAddress::getAddress)
+               .map(ByteBuffer::wrap)
+               .collect(Collectors.toSet());
+       
+       // Retain for listening those 'localhost' addrs which correspond to enabled interfaces
+       jettyListenAddrs = localhostAddrs.stream()
+               .filter(addr -> interfaceAddrs.contains(ByteBuffer.wrap(addr.getAddress())))
+               .collect(Collectors.toList());
+    }
+   
+}
\ No newline at end of file
diff --git a/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.key b/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.key
new file mode 100644
index 00000000..f233d072
--- /dev/null
+++ b/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.key
@@ -0,0 +1,40 @@
+-----BEGIN PRIVATE KEY-----
+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
+gDLxc+vxfq7JO3K28FDjPFtq
+-----END PRIVATE KEY-----
diff --git a/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.p12 b/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.p12
new file mode 100644
index 00000000..49aaa30b
Binary files /dev/null and b/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.p12 differ
diff --git a/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.pem b/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.pem
new file mode 100644
index 00000000..ae068c9c
--- /dev/null
+++ b/shib-testing/src/main/resources/net/shibboleth/shared/testing/credentials/localhost.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEZTCCAs2gAwIBAgIUaOqLVUXbNQuPp+JzeFZaC4v/Po0wDQYJKoZIhvcNAQEL
+BQAwQTELMAkGA1UEBhMCVVMxHjAcBgNVBAoMFVNoaWJib2xldGggQ29uc29ydGl1
+bTESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTIyMDkwODE0MDkyNFoYDzIwNzcwNjEx
+MTQwOTI0WjBBMQswCQYDVQQGEwJVUzEeMBwGA1UECgwVU2hpYmJvbGV0aCBDb25z
+b3J0aXVtMRIwEAYDVQQDDAlsb2NhbGhvc3QwggGiMA0GCSqGSIb3DQEBAQUAA4IB
+jwAwggGKAoIBgQCnIstvkXhPD0RYkl9Z01FwoO11uGf/+OaXyI5krFahD+277Qpb
+SCIqgbvMVTI5zPdya1ukLTEjvB8f0ytpXSZ1sH5dB5ur7YwAE1cHsFg6MvMAztSv
+qcVZrOYjGZFUNzS22UVk1zkdm6do+FKVDfMCRyy/soQQidKkFESHERbDJ3rRk9XQ
+HMG0cVnfnohILYmiar9TosEwO1EEa0vaBeK6ctdRW3OOW401yfQy31gQqxKmsX+n
+DPdDznUy/yxe/ZtRWyiMbTgtASmjYgFSipt9zcpQi0Igwb77sLDXc0GI3vuyCQfh
+q1V54puv+5yNq2J1khh3pMRWkHYDMJIJA40K9sR5cjdCuo0i0JgV+syMedV96xSw
+LPKY4bUF80pfRZlhv/0dAiJVSoMkn6vYKTq4/iGYEI0KFhWs9UdZXgyWQJAGaAuc
++vXQ88On7A24SBaBZFGvQ8NtXsFL9EjPelSDbuqH5F+8SFjD9v3EC3K9H83/78Hm
+r8kGwmMUnNr3M/0CAwEAAaNTMFEwHQYDVR0OBBYEFEgRz075hrhEAzUUGknaE65f
+1ZQBMB8GA1UdIwQYMBaAFEgRz075hrhEAzUUGknaE65f1ZQBMA8GA1UdEwEB/wQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADggGBADZ3aT8thtt42ldx8mKbMKnHooycPUf5
+QY0y4/GSOu5AFhbLPyQFPGCNr5RLKYEvUZzJssnuIKjg/VK8a19BdS9UiV5n6vFu
+75gUHuBW8cOjl16XUorMO+IFD+1bHO7jBu8ZHqrqOxzhDyHH2MqwI/zFi/wzjzh4
+q39fo3F05A6gyaA2DOz9dhyDeg8tlMIlgkgBqe8OwiqvuKUFZmog4wt44X297B5N
+9fI7xRFpoD1i7uhsJW7byewmNXQb5HJXAit/U7iOY+1J4ZSYtkwm3BVA1fhnY4Ys
+d+ex553wVS3RyfrEoLth3E8f9vksLVdWSaDjaWnY49B/NX6kMVWQd8CzoOJDYK+P
+uOGv8s6IN1qCNdDE0B9QHIMpkGP7y0kWKNbYoSKgRTSHJA/viARFOe3UKRpHie2u
+dJ2siyveIgSpflsUJ0acmzkrJdBEdXj7QoWeIosSOtxvkwSvfRjDaiBCbr3SGvxj
+jpv4MN2X1UTUAOhOi5c+Vw5zQ32xX1cC8A==
+-----END CERTIFICATE-----

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


More information about the commits mailing list