[java-idp-oidc] 01/02: JOIDC-284 - Use local/embedded jetty instead of remote Git repository for test data
Codeberg
noreply at shibboleth.net
Sun Jul 12 11:08:58 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch maint-4.3
in repository java-idp-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-oidc/commit/4c7ac133e066c128c9896e259a42f3cef735ee13
commit 4c7ac133e066c128c9896e259a42f3cef735ee13
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Sun Jul 12 13:23:28 2026 +0300
JOIDC-284 - Use local/embedded jetty instead of remote Git repository for test data
https://shibboleth.atlassian.net/browse/JOIDC-284
Temporary copy of EmbeddedJetty and its resources from java-shib-shared/shib-testing
- This can be removed once we bump the IdP dependencies
---
idp-oidc-extension-impl/pom.xml | 13 +
.../idp/plugin/oidc/op/testing/EmbeddedJetty.java | 302 +++++++++++++++++++++
.../shared/testing/credentials/localhost.key | 40 +++
.../shared/testing/credentials/localhost.p12 | Bin 0 -> 3459 bytes
.../shared/testing/credentials/localhost.pem | 26 ++
5 files changed, 381 insertions(+)
diff --git a/idp-oidc-extension-impl/pom.xml b/idp-oidc-extension-impl/pom.xml
index a1f32617..7b7353df 100644
--- a/idp-oidc-extension-impl/pom.xml
+++ b/idp-oidc-extension-impl/pom.xml
@@ -15,6 +15,7 @@
<packaging>jar</packaging>
<properties>
+ <jetty.version>12.1.5</jetty.version>
<checkstyle.configLocation>${project.basedir}/../resources/checkstyle.xml</checkstyle.configLocation>
<automatic.module.name>net.shibboleth.idp.plugin.oidc.op.impl</automatic.module.name>
</properties>
@@ -518,6 +519,18 @@
<version>${nashorn.jdk.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${jetty.groupId}</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${jetty.groupId}</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/testing/EmbeddedJetty.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/testing/EmbeddedJetty.java
new file mode 100644
index 00000000..8e21882a
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/testing/EmbeddedJetty.java
@@ -0,0 +1,302 @@
+/*
+ * 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.plugin.oidc.op.testing;
+
+import java.io.Closeable;
+import java.io.IOException;
+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.primitive.NonnullSupplier;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.shared.spring.resource.ResourceHelper;
+
+/**
+ * Testing fixture using Jetty to server a specified handler.
+ *
+ * TODO: temporarily copied from java-shib-shared/shib-testing - switch into that after bumping dependencies
+ */
+public class EmbeddedJetty implements Closeable {
+
+ /** Addresses to listen on. */
+ private List<InetAddress> jettyListenAddrs;
+
+ /** Non-TLS port */
+ private int port = 8080;
+
+ /** TLS port */
+ private int securePort = 8443;
+
+ /** Captures server. */
+ @Nullable Server jettyServer;
+
+ /**
+ * Sets the non-TLS port to listen on.
+ *
+ * <p>Defaults to 8080.</p>
+ *
+ * @param p port
+ */
+ public void setPort(final int p) {
+ port = p;
+ }
+
+ /**
+ * Sets the TLS port to listen on.
+ *
+ * <p>Defaults to 8443.</p>
+ *
+ * @param p port
+ */
+ public void setSecurePort(final int p) {
+ securePort = 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(getServerKeystorePassword());
+
+ resolveListenAddresses();
+ ArrayList<ServerConnector> connectors = new ArrayList<>();
+ jettyListenAddrs.forEach(addr -> {
+
+ final ServerConnector connector = new ServerConnector(server);
+ connector.setHost(addr.getHostAddress());
+ connector.setPort(port);
+ connectors.add(connector);
+
+ final ServerConnector tlsConnector = new ServerConnector(server, sslContextFactory);
+ tlsConnector.setHost(addr.getHostAddress());
+ tlsConnector.setPort(securePort);
+ connectors.add(tlsConnector);
+ });
+ 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);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void close() throws IOException {
+ stopServer();
+ }
+
+ /**
+ * 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"));
+ }
+
+ /**
+ * Returns the TLS keystore used by the server as a resource in PKCS12 format
+ *
+ * @return TLS server keystore resource
+ */
+ @Nonnull public static Resource getServerKeystoreResource() {
+ return ResourceHelper.of(new ClassPathResource("/net/shibboleth/shared/testing/credentials/localhost.p12"));
+ }
+
+ /**
+ * Returns the TLS server keystore password.
+ *
+ * @return keystore password
+ */
+ @Nonnull public static String getServerKeystorePassword() {
+ return "changeit";
+ }
+
+ /**
+ * Jetty handler to server content.
+ */
+ public static class ResourceHandler extends Handler.Abstract {
+
+ @Nonnull final NonnullSupplier<Integer> statusSupplier;
+
+ @Nonnull final NonnullSupplier<Resource> resourceSupplier;
+
+ @Nullable String contentType;
+
+ /**
+ * Constructor.
+ *
+ * @param s status
+ * @param r resource to serve
+ */
+ public ResourceHandler(final int s, @Nonnull final Resource r) {
+ statusSupplier = NonnullSupplier.of(s);
+ resourceSupplier = NonnullSupplier.of(Constraint.isNotNull(r, "Resource cannot be null"));
+ }
+
+ /**
+ * Constructor.
+ *
+ * <p>Uses 200 as the status.</p>
+ *
+ * @param r resource to serve
+ */
+ public ResourceHandler(@Nonnull final Resource r) {
+ this(200, r);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param s status supplier
+ * @param r resource supplier
+ */
+ public ResourceHandler(@Nonnull final NonnullSupplier<Integer> s, @Nonnull final NonnullSupplier<Resource> r) {
+ statusSupplier = Constraint.isNotNull(s, "Status supplier cannot be null");
+ resourceSupplier = Constraint.isNotNull(r, "Resource supplier cannot be null");
+ }
+
+ /**
+ * Constructor.
+ *
+ * <p>Uses 200 as the status.</p>
+ *
+ * @param r resource supplier
+ */
+ public ResourceHandler(@Nonnull final NonnullSupplier<Resource> r) {
+ this(NonnullSupplier.of(200), r);
+ }
+
+ /**
+ * 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(statusSupplier.get());
+ response.write(true, ByteBuffer.wrap(resourceSupplier.get().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/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.key b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.key
new file mode 100644
index 00000000..f233d072
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.key
@@ -0,0 +1,40 @@
+-----BEGIN PRIVATE KEY-----
+MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCnIstvkXhPD0RY
+kl9Z01FwoO11uGf/+OaXyI5krFahD+277QpbSCIqgbvMVTI5zPdya1ukLTEjvB8f
+0ytpXSZ1sH5dB5ur7YwAE1cHsFg6MvMAztSvqcVZrOYjGZFUNzS22UVk1zkdm6do
++FKVDfMCRyy/soQQidKkFESHERbDJ3rRk9XQHMG0cVnfnohILYmiar9TosEwO1EE
+a0vaBeK6ctdRW3OOW401yfQy31gQqxKmsX+nDPdDznUy/yxe/ZtRWyiMbTgtASmj
+YgFSipt9zcpQi0Igwb77sLDXc0GI3vuyCQfhq1V54puv+5yNq2J1khh3pMRWkHYD
+MJIJA40K9sR5cjdCuo0i0JgV+syMedV96xSwLPKY4bUF80pfRZlhv/0dAiJVSoMk
+n6vYKTq4/iGYEI0KFhWs9UdZXgyWQJAGaAuc+vXQ88On7A24SBaBZFGvQ8NtXsFL
+9EjPelSDbuqH5F+8SFjD9v3EC3K9H83/78Hmr8kGwmMUnNr3M/0CAwEAAQKCAYAA
++CKeChglvKhJUBG2x5MGsWcWAAuQpPcZ9PVxTZf9BUazCKmsyNjPBUyWuv0vXVfe
+UMpcDQdUSKc512FQtsGnRYDU9baxVViy3NeQlOwDx4iTq7Gv7a7ohyg9FpY6ZARt
+mi6KoT5Un/v3ohdTX0EfmebIdq2kZFcVml+hXcETijtJwSXNE4fnMllgS9Xa7LuK
+P/M7rN2aGFzTkBdzBZi3X6qybylpL0xPIsNWt1UD6gRbB9c1LeBI8OJc+SKVz98g
+G6yOyNFoTKDCUsyGvYSq6HF5c5r/aJGLRVawdCKGiqH1wYQ+mQuZDs/bma3btdvu
+2kdq8V0+9B5PQiNTh9DPtfLAE4nnd1Tt5WgKA1aXkCXXYgP4Wn+5WSImcfAqmZ3r
+WSyV22UtjGqSGiWvtyR9whY+B+1pGhPrhRk4TVu3w0BWE/pZkHXe0QeNN3CnN/Pu
+6e7P/nNhTc3moNSAEO7KbYof4ltFbi+VcKEXV5Aaz1zVWbg5D3JLzNvZvb+EMbMC
+gcEAztUTnM4nT55MOYbZjCwHqUCl8SiaNTzKgIf9PbQkCUwtydTiQ8i3sZxqLybV
+36/aSSzyajp1n/2uIMtV/RA/Pv1LC/4kewanZaKN3YmWyk67YfraaH7Q9OaEag4K
+SrAqnBCpou8oTBR6IrzQ+eQRWF7UdapzNwVMJlr3mHpbp+jAeBuomWZL/bhNee52
+KzZ4H8R2McVoQO+hk21jpVbO33JXE17XrbjfaleoZu8ZkOGmOAdjXjgSXvNd+2BA
+AQvXAoHBAM7d9y7gB/s9THytNs6ppRqTI5PSKfSRWgH/Zp4+PTqq9ITq+Cu25U1p
+IX5BobVXbI8rR/c9DU7v19gWOhrdkWAUGyk6vOXzO/H4660M4Bc2O2Gm3FvcEtQ8
+kv3jAp3YxoChXs9jcXxsFH5Y91ivJ+eeMcah8UuLQ4Sqda8VbLlYXpv9rV2UkCO9
+4kOlcZSlpqZq4PngWO7rf3WkG76balvvHh1z9mAKrWr9zZChXRt5VPVJ/FeFSAag
+u0bT7yWkSwKBwHVRDOfTUx7pBglXQMwuKUZKYhWQ1y89RQt0lyLJq+sOJ6aktpaG
+IhN/Sgdmusc/IsyAzxuL/y35oSv+yc5ZydX7q/aod57EnmyasGcpZLtpvwWLWRkO
+XY2btx3EyvekRvbwyJefmbbVopVTjiE/yMrcNxxqyyE5QwE6ddgqBxUNgyZdYdto
+18+ZG3D+3k4Sfj5enEAM3d5/TaGm2W9t9rdtTpCxKhriku7pu55vHow8QaDkJ+vI
+WDs9RWCRLpypTwKBwQDA7y3xrwpinPowMdCzEG+nCGIfJNzyd3nt8QkBP2UVyYnC
+Se9pvevAtfOB8K5kFgRuxtwYz/0QiQrTQ2+vzMQgSsBGRL0W7jMTa6hKvn5lx7O8
+UMameeupvFEPr2CqXRpNr7NgUwvuElNOv6T5NmtOTzF3Y2RLo7g2DFE0GRRNDQk4
+DXFanQuN+jQECVKUY6a5AWeQRVhMhKFc09D4hbS9x5dbuuKnEm5JIitN3+GZlSDS
+oM1Txz+0xsXDujgJ8F8CgcEAped4qn8VCBEuQJ++cftDQswSLEqAxdxtgrhOr2Nr
+tM4bKd7PJTBYFOloaXzBPyNoIhEgVYNGEBHIfn/DP1UbVCj/gfV96tq2ILtfU2Oh
+ZJRxTNPamWdnf69rKt90QYoMrwMh/3unWPZDLOu6wH3YHNNLtdaiZuAUlndMi2OM
+1L4Z5MF+ou+idfNF3SsC6CGHTM8Da4MJZ9uMdyUcr10wmpFMipfeSbyoZEbrrKTT
+gDLxc+vxfq7JO3K28FDjPFtq
+-----END PRIVATE KEY-----
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.p12 b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.p12
new file mode 100644
index 00000000..49aaa30b
Binary files /dev/null and b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.p12 differ
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.pem b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/shared/testing/credentials/localhost.pem
new file mode 100644
index 00000000..ae068c9c
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/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