Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-6566-WebSocketMessageSinks

This commit is contained in:
Lachlan Roberts 2021-08-27 17:21:53 +10:00
commit 7569e4b07a
117 changed files with 2545 additions and 743 deletions

View File

@ -174,6 +174,12 @@ public class FastFileServer
x.printStackTrace();
async.complete();
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
};
// send "medium" files from an input stream

View File

@ -511,3 +511,19 @@ Below is an example which, like the one above, sets up a server with a `HashLogi
----
include::{SRCDIR}/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java[]
----
==== JSR 196: Java Authentication Service Provider Interface for Containers (JASPI)
Jetty can utilize portable authentication modules that implements the Jakarta Authentication specification. This requires the jetty-jaspi module.
Only modules conforming to the ServerAuthModule interface in the https://www.jcp.org/en/jsr/detail?id=196[JASPI Spec] are supported. These modules must be configured before start-up.
The following illustrates a jetty module setting up HTTP Basic Authentication using an Authentication module that comes packaged with the jetty-jaspi module: `org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule`
[source, xml, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml[tags=documentation]
----
Given the portability goal of Jakarta Authentication, custom or 3rd party `ServerAuthModule` implementations may be configured instead here.

View File

@ -34,6 +34,7 @@ include::annotations/chapter.adoc[]
include::jsp/chapter.adoc[]
include::jndi/chapter.adoc[]
include::jaas/chapter.adoc[]
include::jaspi/chapter.adoc[]
include::jmx/chapter.adoc[]
include::logging/chapter.adoc[]
include::troubleshooting/chapter.adoc[]

View File

@ -0,0 +1,68 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[og-jaspi]]
=== JASPI
Enabling this module allows Jetty to utilize authentication modules that implement the JSR 196 (JASPI) specification. JASPI provides an SPI (Service Provider Interface) for pluggable, portable, and standardized authentication modules. Compatible modules are portable between servers that support the JASPI specification. This module provides a bridge from Java Authentication to the Jetty Security framework.
Only modules conforming to the "Servlet Container Profile" with the ServerAuthModule interface within the https://www.jcp.org/en/jsr/detail?id=196[JASPI Spec] are supported. These modules must be configured before start-up. Operations for runtime registering or de-registering authentication modules are not supported.
[[og-jaspi-configuration]]
==== Configuration
[[og-jaspi-module]]
===== The `jaspi` module
Enable the `jaspi` module:
----
include::{JETTY_HOME}/modules/jaspi.mod[]
----
[[og-jaspi-xml]]
===== Configure JASPI
To enable the `jaspi` module you can use the following command (issued from within the `$JETTY_BASE` directory):
----
$ java -jar $JETTY_HOME/start.jar --add-modules=jaspi
----
You can then register a `AuthConfigProvider` onto the static `AuthConfigFactory` obtained with `AuthConfigFactory.getFactory()`. This registration can be done in the XML configuration file which will be copied to `$JETTY_BASE/etc/jaspi/jaspi-authmoduleconfig.xml` when the module is enabled.
====== JASPI Demo
The `jaspi-demo` module illustrates setting up HTTP Basic Authentication using a Java Authentication module that comes packaged with jetty: `org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule`, and applies it for a context named `/test`.
[source, xml]
----
include::{JETTY_HOME}/etc/jaspi/jaspi-demo.xml[]
----
This example uses the `AuthConfigProvider` implementation provided by Jetty to register a `ServerAuthModule` directly. Other custom or 3rd party modules that are compatible with the `ServerAuthModule` interface in JASPI can be registered in the same way.
===== Integration with Jetty Authentication Mechanisms
To integrate with Jetty authentication mechanisms you must add a `LoginService` to your context. The `LoginService` provides a way for you to obtain a `UserIdentity` from a username and credentials. JASPI can interact with this Jetty `LoginService` by using the `PasswordValidationCallback`.
The `CallerPrincipalCallback` and `GroupPrincipalCallback` do not require use of a Jetty `LoginService`. The principal from the `CallerPrincipalCallback` will be used directly with the `IdentityService` to produce a `UserIdentity`.
===== Replacing the Jetty DefaultAuthConfigFactory
Jetty provides an implementation of the `AuthConfigFactory` interface which is used to register `AuthConfigProviders`. This can be replaced by a custom implementation by adding a custom module which provides `auth-config-factory`.
This custom module must reference an XML file which sets a new instance of the `AuthConfigFactory` with the static method `AuthConfigFactory.setFactory()`.
For an example of this see the `jaspi-default-auth-config-factory` module, which provides the default implementation used by Jetty.
----
include::{JETTY_HOME}/modules/jaspi-default-auth-config-factory.mod[]
----

View File

@ -60,14 +60,16 @@ import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import static org.hamcrest.MatcherAssert.assertThat;
@ -365,7 +367,7 @@ public class HttpClientTLSTest
// Excluded in JDK 11+ because resumed sessions cannot be compared
// using their session IDs even though they are resumed correctly.
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10})
@EnabledForJreRange(max = JRE.JAVA_10)
@Test
public void testHandshakeSucceededWithSessionResumption() throws Exception
{
@ -445,7 +447,7 @@ public class HttpClientTLSTest
// Excluded in JDK 11+ because resumed sessions cannot be compared
// using their session IDs even though they are resumed correctly.
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10})
@EnabledForJreRange(max = JRE.JAVA_10)
@Test
public void testClientRawCloseDoesNotInvalidateSession() throws Exception
{
@ -1013,7 +1015,6 @@ public class HttpClientTLSTest
// Force TLS-level hostName verification, as we want to receive the correspondent certificate.
clientTLS.setEndpointIdentificationAlgorithm("HTTPS");
startClient(clientTLS);
clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER);
// Send a request with SNI "localhost", we should get the certificate at alias=localhost.
@ -1027,18 +1028,40 @@ public class HttpClientTLSTest
.scheme(HttpScheme.HTTPS.asString())
.send();
assertEquals(HttpStatus.OK_200, response2.getStatus());
}
/* TODO Fix. See #6624
if (Net.isIpv6InterfaceAvailable())
@Test
@EnabledForJreRange(max = JRE.JAVA_16, disabledReason = "Since Java 17, SNI host names can only have letter|digit|hyphen characters.")
public void testForcedNonDomainSNIWithIPv6() throws Exception
{
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
SslContextFactory.Server serverTLS = new SslContextFactory.Server();
serverTLS.setKeyStorePath("src/test/resources/keystore_sni_non_domain.p12");
serverTLS.setKeyStorePassword("storepwd");
serverTLS.setSNISelector((keyType, issuers, session, sniHost, certificates) ->
{
// Send a request with SNI "[::1]", we should get the certificate at alias=ip.
ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send();
// We have forced the client to send the non-domain SNI.
assertNotNull(sniHost);
return serverTLS.sniSelect(keyType, issuers, session, sniHost, certificates);
});
startServer(serverTLS, new EmptyServerHandler());
assertEquals(HttpStatus.OK_200, response3.getStatus());
}
*/
SslContextFactory.Client clientTLS = new SslContextFactory.Client();
// Trust any certificate received by the server.
clientTLS.setTrustStorePath("src/test/resources/keystore_sni_non_domain.p12");
clientTLS.setTrustStorePassword("storepwd");
// Force TLS-level hostName verification, as we want to receive the correspondent certificate.
clientTLS.setEndpointIdentificationAlgorithm("HTTPS");
startClient(clientTLS);
clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER);
// Send a request with SNI "[::1]", we should get the certificate at alias=ip.
ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send();
assertEquals(HttpStatus.OK_200, response3.getStatus());
}
@Test

View File

@ -68,7 +68,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.JRE;
@ -82,7 +81,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.condition.OS.LINUX;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
// Other JREs have slight differences in how TLS work
// and this test expects a very specific TLS behavior.
@ -1027,7 +1025,6 @@ public class SslBytesServerTest extends SslBytesTest
}
@Test
@DisabledOnOs(WINDOWS) // Don't run on Windows (buggy JVM)
public void testRequestWithBigContentWriteBlockedThenReset() throws Exception
{
final SSLSocket client = newClient();
@ -1082,7 +1079,6 @@ public class SslBytesServerTest extends SslBytesTest
}
@Test
@DisabledOnOs(WINDOWS) // Don't run on Windows (buggy JVM)
public void testRequestWithBigContentReadBlockedThenReset() throws Exception
{
final SSLSocket client = newClient();

View File

@ -33,41 +33,49 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class DeploymentTempDirTest
{
private static final Logger LOG = Log.getLogger(DeploymentTempDirTest.class);
public WorkDir workDir;
private final WebAppProvider webAppProvider = new WebAppProvider();
private final ContextHandlerCollection contexts = new ContextHandlerCollection();
private final Path testDir = MavenTestingUtils.getTargetTestingPath(DeploymentTempDirTest.class.getSimpleName());
private final Path tmpDir = testDir.resolve("tmpDir");
private final Path webapps = testDir.resolve("webapps");
private final Server server = new Server();
private Path tmpDir;
private Path webapps;
private Server server;
private WebAppProvider webAppProvider;
private ContextHandlerCollection contexts;
private final TestListener listener = new TestListener();
@BeforeEach
public void setup() throws Exception
{
Path testDir = workDir.getEmptyPathDir();
tmpDir = testDir.resolve("tmpDir");
webapps = testDir.resolve("webapps");
FS.ensureDirExists(tmpDir);
FS.ensureDirExists(webapps);
server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
FS.ensureEmpty(testDir);
FS.ensureEmpty(tmpDir);
FS.ensureEmpty(webapps);
webAppProvider = new WebAppProvider();
webAppProvider.setMonitoredDirName(webapps.toString());
webAppProvider.setScanInterval(0);
@ -75,6 +83,7 @@ public class DeploymentTempDirTest
deploymentManager.addAppProvider(webAppProvider);
server.addBean(deploymentManager);
contexts = new ContextHandlerCollection();
HandlerCollection handlerCollection = new HandlerCollection();
handlerCollection.addHandler(contexts);
handlerCollection.addHandler(new DefaultHandler());

View File

@ -26,13 +26,10 @@ import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
/**
* Similar in scope to {@link ScanningAppProviderStartupTest}, except is concerned with the modification of existing
* deployed webapps due to incoming changes identified by the {@link ScanningAppProvider}.
@ -155,8 +152,6 @@ public class ScanningAppProviderRuntimeUpdatesTest
* @throws Exception on test failure
*/
@Test
@DisabledOnOs(WINDOWS)
// This test will not work on Windows as second war file would, not be written over the first one because of a file lock
public void testAfterStartupThenUpdateContext() throws Exception
{
jetty.copyWebapp("foo-webapp-1.war", "foo.war");

View File

@ -99,6 +99,8 @@ public class WebAppProviderTest
@Test
public void testStartupContext()
{
assumeTrue(symlinkSupported);
// Check Server for Handlers
jetty.assertWebAppContextsExists("/bar", "/foo", "/bob");

View File

@ -26,7 +26,7 @@ lib/logging/slf4j-jdk14-${slf4j.version}.jar
-Djava.util.logging.config.file=${jetty.base}/resources/java-util-logging.properties
[ini]
slf4j.version?=2.0.0-alpha1
slf4j.version?=2.0.0-alpha4
[license]
SLF4J is distributed under the MIT License.

View File

@ -24,7 +24,7 @@ lib/logging/logback-classic-${logback.version}.jar
lib/logging/logback-core-${logback.version}.jar
[ini]
logback.version?=1.3.0-alpha5
logback.version?=1.3.0-alpha9
jetty.webapp.addServerClasses+=,ch.qos.logback.
[license]

View File

@ -15,5 +15,5 @@ slf4j
lib/logging/slf4j-api-${slf4j.version}.jar
[ini]
slf4j.version?=2.0.0-alpha1
slf4j.version?=2.0.0-alpha4
jetty.webapp.addServerClasses+=,org.slf4j.

View File

@ -135,6 +135,12 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
close();
promise.failed(x);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
}
private static class ConnectionListener implements Connection.Listener

View File

@ -29,9 +29,12 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Unable to create all of the streams")
public class ConcurrentStreamCreationTest extends AbstractTest
{
@Test

View File

@ -98,9 +98,8 @@ public class SmallThreadPoolLoadTest extends AbstractTest
Thread testThread = Thread.currentThread();
Scheduler.Task task = client.getScheduler().schedule(() ->
{
logger.warn("Interrupting test, it is taking too long{}Server:{}{}{}Client:{}{}",
System.lineSeparator(), System.lineSeparator(), server.dump(),
System.lineSeparator(), System.lineSeparator(), client.dump());
logger.warn("Interrupting test, it is taking too long - \nServer: \n" +
server.dump() + "\nClient: \n" + client.dump());
testThread.interrupt();
}, iterations * factor, TimeUnit.MILLISECONDS);
@ -184,9 +183,8 @@ public class SmallThreadPoolLoadTest extends AbstractTest
if (success)
latch.countDown();
else
logger.warn("Request {} took too long{}Server:{}{}{}Client:{}{}", requestId,
System.lineSeparator(), System.lineSeparator(), server.dump(),
System.lineSeparator(), System.lineSeparator(), client.dump());
logger.warn("Request {} took too long - \nServer: \n" +
server.dump() + "\nClient: \n" + client.dump(), requestId);
return !reset.get();
}

View File

@ -35,6 +35,7 @@ import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -50,6 +51,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
private final Collection<Entry> processedEntries = new ArrayList<>();
private final HTTP2Session session;
private final ByteBufferPool.Lease lease;
private InvocationType invocationType = InvocationType.NON_BLOCKING;
private Throwable terminated;
private Entry stalledEntry;
@ -59,6 +61,12 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
this.lease = new ByteBufferPool.Lease(session.getGenerator().getByteBufferPool());
}
@Override
public InvocationType getInvocationType()
{
return invocationType;
}
public void window(IStream stream, WindowUpdateFrame frame)
{
Throwable closed;
@ -214,7 +222,10 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
// We use ArrayList contains() + add() instead of HashSet add()
// because that is faster for collections of size up to 250 entries.
if (!processedEntries.contains(entry))
{
processedEntries.add(entry);
invocationType = Invocable.combine(invocationType, Invocable.getInvocationType(entry.getCallback()));
}
if (entry.getDataBytesRemaining() == 0)
pending.remove();
@ -311,6 +322,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
processedEntries.forEach(Entry::succeeded);
processedEntries.clear();
invocationType = InvocationType.NON_BLOCKING;
if (stalledEntry != null)
{

View File

@ -72,6 +72,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -1991,7 +1992,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
private void sendGoAwayAndTerminate(GoAwayFrame frame, GoAwayFrame eventFrame)
{
sendGoAway(frame, Callback.from(() -> terminate(eventFrame)));
sendGoAway(frame, Callback.from(Callback.NOOP, () -> terminate(eventFrame)));
}
private void sendGoAway(GoAwayFrame frame, Callback callback)
@ -2197,7 +2198,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
stream.setListener(listener);
stream.process(new PrefaceFrame(), Callback.NOOP);
Callback streamCallback = Callback.from(() -> promise.succeeded(stream), x ->
Callback streamCallback = Callback.from(Invocable.InvocationType.NON_BLOCKING, () -> promise.succeeded(stream), x ->
{
HTTP2Session.this.onStreamDestroyed(streamId);
promise.failed(x);

View File

@ -742,6 +742,15 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
callback.failed(x);
}
@Override
public InvocationType getInvocationType()
{
synchronized (this)
{
return sendCallback != null ? sendCallback.getInvocationType() : Callback.super.getInvocationType();
}
}
private Callback endWrite()
{
try (AutoLock l = lock.lock())

View File

@ -63,6 +63,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,281 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.http2.client.http;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.AbstractEndPoint;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BlockedWritesWithSmallThreadPoolTest
{
private Server server;
private ServerConnector connector;
private QueuedThreadPool serverThreads;
private HTTP2Client client;
private void start(Handler handler) throws Exception
{
// Threads: 1 acceptor, 1 selector, 1 reserved, 1 application.
serverThreads = newSmallThreadPool("server", 4);
server = new Server(serverThreads);
HTTP2CServerConnectionFactory http2 = new HTTP2CServerConnectionFactory(new HttpConfiguration());
connector = new ServerConnector(server, 1, 1, http2);
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
private void start(RawHTTP2ServerConnectionFactory factory) throws Exception
{
// Threads: 1 acceptor, 1 selector, 1 reserved, 1 application.
serverThreads = newSmallThreadPool("server", 4);
server = new Server(serverThreads);
connector = new ServerConnector(server, 1, 1, factory);
server.addConnector(connector);
server.start();
}
private QueuedThreadPool newSmallThreadPool(String name, int maxThreads)
{
QueuedThreadPool pool = new QueuedThreadPool(maxThreads, maxThreads);
pool.setName(name);
pool.setReservedThreads(1);
pool.setDetailedDump(true);
return pool;
}
@AfterEach
public void dispose()
{
LifeCycle.stop(client);
LifeCycle.stop(server);
}
@Test
public void testServerThreadsBlockedInWrites() throws Exception
{
int contentLength = 16 * 1024 * 1024;
AtomicReference<AbstractEndPoint> serverEndPointRef = new AtomicReference<>();
start(new EmptyServerHandler()
{
@Override
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
serverEndPointRef.compareAndSet(null, (AbstractEndPoint)jettyRequest.getHttpChannel().getEndPoint());
// Write a large content to cause TCP congestion.
response.getOutputStream().write(new byte[contentLength]);
}
});
client = new HTTP2Client();
// Set large flow control windows so the server hits TCP congestion.
int window = 2 * contentLength;
client.setInitialSessionRecvWindow(window);
client.setInitialStreamRecvWindow(window);
client.start();
FuturePromise<Session> promise = new FuturePromise<>();
client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener.Adapter(), promise);
Session session = promise.get(5, SECONDS);
CountDownLatch clientBlockLatch = new CountDownLatch(1);
CountDownLatch clientDataLatch = new CountDownLatch(1);
// Send a request to TCP congest the server.
HttpURI uri = HttpURI.build("http://localhost:" + connector.getLocalPort() + "/congest");
MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY);
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
try
{
// Block here to stop reading from the network
// to cause the server to TCP congest.
clientBlockLatch.await(5, SECONDS);
callback.succeeded();
if (frame.isEndStream())
clientDataLatch.countDown();
}
catch (InterruptedException x)
{
callback.failed(x);
}
}
});
await().atMost(5, SECONDS).until(() ->
{
AbstractEndPoint serverEndPoint = serverEndPointRef.get();
return serverEndPoint != null && serverEndPoint.getWriteFlusher().isPending();
});
// Wait for NIO on the server to be OP_WRITE interested.
Thread.sleep(1000);
// Make sure there is a reserved thread.
if (serverThreads.getAvailableReservedThreads() != 1)
{
assertFalse(serverThreads.tryExecute(() -> {}));
await().atMost(5, SECONDS).until(() -> serverThreads.getAvailableReservedThreads() == 1);
}
// Use the reserved thread for a blocking operation, simulating another blocking write.
CountDownLatch serverBlockLatch = new CountDownLatch(1);
assertTrue(serverThreads.tryExecute(() -> await().atMost(20, SECONDS).until(() -> serverBlockLatch.await(15, SECONDS), b -> true)));
assertEquals(0, serverThreads.getReadyThreads());
// Unblock the client to read from the network, which should unblock the server write().
clientBlockLatch.countDown();
assertTrue(clientDataLatch.await(10, SECONDS), server.dump());
serverBlockLatch.countDown();
}
@Test
public void testClientThreadsBlockedInWrite() throws Exception
{
int contentLength = 16 * 1024 * 1024;
CountDownLatch serverBlockLatch = new CountDownLatch(1);
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
try
{
// Block here to stop reading from the network
// to cause the client to TCP congest.
serverBlockLatch.await(5, SECONDS);
callback.succeeded();
if (frame.isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
}
catch (InterruptedException x)
{
callback.failed(x);
}
}
};
}
});
int window = 2 * contentLength;
http2.setInitialSessionRecvWindow(window);
http2.setInitialStreamRecvWindow(window);
start(http2);
client = new HTTP2Client();
// Threads: 1 selector, 1 reserved, 1 application.
QueuedThreadPool clientThreads = newSmallThreadPool("client", 3);
client.setExecutor(clientThreads);
client.setSelectors(1);
client.start();
FuturePromise<Session> promise = new FuturePromise<>();
client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener.Adapter(), promise);
Session session = promise.get(5, SECONDS);
// Send a request to TCP congest the client.
HttpURI uri = HttpURI.build("http://localhost:" + connector.getLocalPort() + "/congest");
MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
assertEquals(HttpStatus.OK_200, response.getStatus());
latch.countDown();
}
});
Stream stream = streamPromise.get(5, SECONDS);
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(contentLength), true), Callback.NOOP);
await().atMost(5, SECONDS).until(() ->
{
AbstractEndPoint clientEndPoint = (AbstractEndPoint)((HTTP2Session)session).getEndPoint();
return clientEndPoint.getWriteFlusher().isPending();
});
// Wait for NIO on the client to be OP_WRITE interested.
Thread.sleep(1000);
CountDownLatch clientBlockLatch = new CountDownLatch(1);
// Make sure the application thread is blocked.
clientThreads.execute(() -> await().until(() -> clientBlockLatch.await(15, SECONDS), b -> true));
// Make sure the reserved thread is blocked.
if (clientThreads.getAvailableReservedThreads() != 1)
{
assertFalse(clientThreads.tryExecute(() -> {}));
await().atMost(5, SECONDS).until(() -> clientThreads.getAvailableReservedThreads() == 1);
}
// Use the reserved thread for a blocking operation, simulating another blocking write.
assertTrue(clientThreads.tryExecute(() -> await().until(() -> clientBlockLatch.await(15, SECONDS), b -> true)));
await().atMost(5, SECONDS).until(() -> clientThreads.getReadyThreads() == 0);
// Unblock the server to read from the network, which should unblock the client.
serverBlockLatch.countDown();
assertTrue(latch.await(10, SECONDS), client.dump());
clientBlockLatch.countDown();
}
}

View File

@ -486,7 +486,7 @@ public class ClientConnector extends ContainerLifeCycle
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not configure {} to {} on {}", option, value, channel);
LOG.debug("Could not configure {} to {} on {}", option, value, channel, x);
}
}

View File

@ -80,17 +80,5 @@
<version>1.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.components</groupId>
<artifactId>geronimo-jaspi</artifactId>
<version>2.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jaspic_1.0_spec</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Call class="javax.security.auth.message.config.AuthConfigFactory" name="getFactory">
<!-- Configure the AuthConfigFactory here. -->
</Call>
</Configure>

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- ===================================================================== -->
<!-- Configure a factory for Jaspi -->
<!-- ===================================================================== -->
<Call class="javax.security.auth.message.config.AuthConfigFactory" name="setFactory">
<Arg>
<New id="jaspiAuthConfigFactory" class="org.eclipse.jetty.security.jaspi.DefaultAuthConfigFactory" />
</Arg>
</Call>
<Call name="addBean">
<Arg>
<Ref refid="jaspiAuthConfigFactory" />
</Arg>
<Arg type="boolean">false</Arg>
</Call>
</Configure>

View File

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Call class="javax.security.auth.message.config.AuthConfigFactory" name="getFactory">
<Call name="registerConfigProvider">
<!-- The Jetty provided implementation of AuthConfigProvider which will wrap a ServerAuthModule. -->
<Arg type="String">org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider</Arg>
<!-- A Map of initialization properties. -->
<Arg>
<Map>
<Entry>
<!-- Provide the fully qualified classname of the ServerAuthModule to be used. -->
<Item>ServerAuthModule</Item>
<Item>org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule</Item>
</Entry>
<Entry>
<!-- Realm as utilised by Jetty Security -->
<Item>org.eclipse.jetty.security.jaspi.modules.RealmName</Item>
<Item>Test Realm</Item>
</Entry>
</Map>
</Arg>
<!-- Message Layer Identifier as per spec chapter 3.1 -->
<Arg type="String">HttpServlet</Arg>
<!-- Application Context Identifier as per spec chapter 3.2
AppContextID ::= hostname blank context-path
The algorithm applied here will use the
_serverName on the configured JaspiAuthenticatorFactory (if set) and try to match it
against the "server" part (in the "server /test" example below).
Next it will try to match the ServletContext#getVirtualServerName to the "server" part.
If neither are set, it will then try to match the first Subject's principal name, and finally fall back to
the default value "server" if none are available.
The context-path should match the context path where this applies.
-->
<Arg type="String">server /test</Arg>
<!-- A friendly description of the provided auth-module. -->
<Arg type="String">A simple provider using HTTP BASIC authentication.</Arg>
</Call>
</Call>
</Configure>

View File

@ -0,0 +1,16 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Provides a DefaultAuthConfigFactory for jaspi
[tags]
security
[depend]
security
[provide]
auth-config-factory
[xml]
etc/jaspi/jaspi-default.xml

View File

@ -0,0 +1,16 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables JASPI basic authentication the /test context path.
[tags]
security
[depend]
jaspi
[xml]
etc/jaspi/jaspi-demo.xml
[files]
basehome:etc/jaspi/jaspi-demo.xml|etc/jaspi/jaspi-demo.xml

View File

@ -3,9 +3,20 @@
[description]
Enables JASPI authentication for deployed web applications.
[tags]
security
[depend]
security
auth-config-factory
[lib]
lib/jetty-jaspi-${jetty.version}.jar
lib/jaspi/*.jar
[xml]
etc/jaspi/jaspi-authmoduleconfig.xml
[files]
basehome:etc/jaspi/jaspi-authmoduleconfig.xml|etc/jaspi/jaspi-authmoduleconfig.xml

View File

@ -19,8 +19,9 @@ module org.eclipse.jetty.security.jaspi
exports org.eclipse.jetty.security.jaspi;
exports org.eclipse.jetty.security.jaspi.callback;
exports org.eclipse.jetty.security.jaspi.modules;
exports org.eclipse.jetty.security.jaspi.provider;
requires javax.security.auth.message;
requires transitive javax.security.auth.message;
requires jetty.servlet.api;
requires transitive org.eclipse.jetty.security;
requires org.slf4j;

View File

@ -0,0 +1,249 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.jaspi;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.security.auth.AuthPermission;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A very basic {@link AuthConfigFactory} that allows for registering providers programmatically.
*/
public class DefaultAuthConfigFactory extends AuthConfigFactory
{
private static final Logger LOG = LoggerFactory.getLogger(DefaultAuthConfigFactory.class);
private final Map<String, DefaultRegistrationContext> _registrations = new ConcurrentHashMap<>();
public DefaultAuthConfigFactory()
{
}
@Override
public AuthConfigProvider getConfigProvider(String layer, String appContext, RegistrationListener listener)
{
DefaultRegistrationContext registrationContext = _registrations.get(getKey(layer, appContext));
if (registrationContext == null)
registrationContext = _registrations.get(getKey(null, appContext));
if (registrationContext == null)
registrationContext = _registrations.get(getKey(layer, null));
if (registrationContext == null)
registrationContext = _registrations.get(getKey(null, null));
if (registrationContext == null)
return null;
// TODO: according to the javadoc you're supposed to register listener even if there is no context available.
if (listener != null)
registrationContext.addListener(listener);
return registrationContext.getProvider();
}
@Override
public String registerConfigProvider(String className, Map properties, String layer, String appContext, String description)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("registerAuthConfigProvider"));
String key = getKey(layer, appContext);
AuthConfigProvider configProvider = createConfigProvider(className, properties);
DefaultRegistrationContext context = new DefaultRegistrationContext(configProvider, layer, appContext, description, true);
DefaultRegistrationContext oldContext = _registrations.put(key, context);
if (oldContext != null)
oldContext.notifyListeners();
return key;
}
@Override
public String registerConfigProvider(AuthConfigProvider provider, String layer, String appContext, String description)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("registerAuthConfigProvider"));
String key = getKey(layer, appContext);
DefaultRegistrationContext context = new DefaultRegistrationContext(provider, layer, appContext, description, false);
DefaultRegistrationContext oldContext = _registrations.put(key, context);
if (oldContext != null)
oldContext.notifyListeners();
return key;
}
@Override
public boolean removeRegistration(String registrationID)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("removeAuthRegistration"));
DefaultRegistrationContext registrationContext = _registrations.remove(registrationID);
if (registrationContext == null)
return false;
registrationContext.notifyListeners();
return true;
}
@Override
public String[] detachListener(RegistrationListener listener, String layer, String appContext)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("detachAuthListener"));
List<String> registrationIds = new ArrayList<>();
for (DefaultRegistrationContext registration : _registrations.values())
{
if ((layer == null || layer.equals(registration.getMessageLayer())) && (appContext == null || appContext.equals(registration.getAppContext())))
{
if (registration.removeListener(listener))
registrationIds.add(getKey(registration.getMessageLayer(), registration.getAppContext()));
}
}
return registrationIds.toArray(new String[0]);
}
@Override
public String[] getRegistrationIDs(AuthConfigProvider provider)
{
List<String> registrationIds = new ArrayList<>();
for (DefaultRegistrationContext registration : _registrations.values())
{
if (provider == registration.getProvider())
registrationIds.add(getKey(registration.getMessageLayer(), registration.getAppContext()));
}
return registrationIds.toArray(new String[0]);
}
@Override
public RegistrationContext getRegistrationContext(String registrationID)
{
return _registrations.get(registrationID);
}
@Override
public void refresh()
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("refreshAuth"));
// TODO: maybe we should re-construct providers created from classname.
}
private static String getKey(String layer, String appContext)
{
return layer + "/" + appContext;
}
@SuppressWarnings("rawtypes")
private AuthConfigProvider createConfigProvider(String className, Map properties)
{
try
{
// Javadoc specifies all AuthConfigProvider implementations must have this constructor, and that
// to construct this we must pass a null value for the factory argument of the constructor.
return (AuthConfigProvider)Class.forName(className)
.getConstructor(Map.class, AuthConfigFactory.class)
.newInstance(properties, null);
}
catch (ReflectiveOperationException e)
{
throw new SecurityException(e);
}
}
private static class DefaultRegistrationContext implements RegistrationContext
{
private final String _layer;
private final String _appContext;
private final boolean _persistent;
private final AuthConfigProvider _provider;
private final String _description;
private final List<RegistrationListener> _listeners = new CopyOnWriteArrayList<>();
public DefaultRegistrationContext(AuthConfigProvider provider, String layer, String appContext, String description, boolean persistent)
{
_provider = provider;
_layer = layer;
_appContext = appContext;
_description = description;
_persistent = persistent;
}
public AuthConfigProvider getProvider()
{
return _provider;
}
@Override
public String getMessageLayer()
{
return _layer;
}
@Override
public String getAppContext()
{
return _appContext;
}
@Override
public String getDescription()
{
return _description;
}
@Override
public boolean isPersistent()
{
return false;
}
public void addListener(RegistrationListener listener)
{
_listeners.add(listener);
}
public void notifyListeners()
{
for (RegistrationListener listener : _listeners)
{
try
{
listener.notify(_layer, _appContext);
}
catch (Throwable t)
{
LOG.warn("Error from RegistrationListener", t);
}
}
}
public boolean removeListener(RegistrationListener listener)
{
return _listeners.remove(listener);
}
}
}

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.security.jaspi;
import java.io.IOException;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
@ -22,6 +23,9 @@ import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;
import javax.servlet.ServletRequest;
@ -30,57 +34,100 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.jetty.security.EmptyLoginService;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.WrappedAuthConfiguration;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.security.authentication.SessionAuthentication;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.UserIdentity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory.MESSAGE_LAYER;
/**
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
* Implementation of Jetty {@link LoginAuthenticator} that is a bridge from Javax Authentication to Jetty Security.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class JaspiAuthenticator extends LoginAuthenticator
{
private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthenticator.class.getName());
private final ServerAuthConfig _authConfig;
private final Map _authProperties;
private final ServletCallbackHandler _callbackHandler;
private final Subject _serviceSubject;
private final String _appContext;
private final boolean _allowLazyAuthentication;
private final AuthConfigFactory _authConfigFactory = AuthConfigFactory.getFactory();
private Map _authProperties;
private IdentityService _identityService;
private ServletCallbackHandler _callbackHandler;
private ServerAuthConfig _authConfig;
private final IdentityService _identityService;
public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject,
boolean allowLazyAuthentication, IdentityService identityService)
public JaspiAuthenticator(Subject serviceSubject, String appContext, boolean allowLazyAuthentication)
{
_serviceSubject = serviceSubject;
_appContext = appContext;
_allowLazyAuthentication = allowLazyAuthentication;
}
@Deprecated
public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject, boolean allowLazyAuthentication, IdentityService identityService)
{
// TODO maybe pass this in via setConfiguration ?
if (callbackHandler == null)
throw new NullPointerException("No CallbackHandler");
if (authConfig == null)
throw new NullPointerException("No AuthConfig");
this._authConfig = authConfig;
this._authProperties = authProperties;
this._callbackHandler = callbackHandler;
this._serviceSubject = serviceSubject;
this._allowLazyAuthentication = allowLazyAuthentication;
this._identityService = identityService;
this._appContext = null;
this._authConfig = authConfig;
}
@Override
public void setConfiguration(AuthConfiguration configuration)
{
LoginService loginService = configuration.getLoginService();
if (loginService == null)
{
// Add an empty login service so we can use JASPI without tying into Jetty auth mechanisms.
configuration = new JaspiAuthConfiguration(configuration);
loginService = configuration.getLoginService();
}
super.setConfiguration(configuration);
// Only do this if the new constructor was used.
if (_authConfig == null)
{
_identityService = configuration.getIdentityService();
_callbackHandler = new ServletCallbackHandler(loginService);
_authProperties = new HashMap();
for (String key : configuration.getInitParameterNames())
{
_authProperties.put(key, configuration.getInitParameter(key));
}
}
}
private ServerAuthConfig getAuthConfig() throws AuthException
{
if (_authConfig != null)
return _authConfig;
RegistrationListener listener = (layer, appContext) -> _authConfig = null;
AuthConfigProvider authConfigProvider = _authConfigFactory.getConfigProvider(MESSAGE_LAYER, _appContext, listener);
if (authConfigProvider == null)
{
_authConfigFactory.detachListener(listener, MESSAGE_LAYER, _appContext);
return null;
}
_authConfig = authConfigProvider.getServerAuthConfig(MESSAGE_LAYER, _appContext, _callbackHandler);
return _authConfig;
}
@Override
@ -124,8 +171,12 @@ public class JaspiAuthenticator extends LoginAuthenticator
{
try
{
String authContextId = _authConfig.getAuthContextID(messageInfo);
ServerAuthContext authContext = _authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties);
ServerAuthConfig authConfig = getAuthConfig();
if (authConfig == null)
throw new ServerAuthException("No ServerAuthConfig");
String authContextId = authConfig.getAuthContextID(messageInfo);
ServerAuthContext authContext = authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties);
Subject clientSubject = new Subject();
AuthStatus authStatus = authContext.validateRequest(messageInfo, clientSubject, _serviceSubject);
@ -154,6 +205,8 @@ public class JaspiAuthenticator extends LoginAuthenticator
if (principal == null)
{
String principalName = principalCallback.getName();
// TODO: if the Principal class is provided it doesn't need to be in subject, why do we enforce this here?
Set<Principal> principals = principalCallback.getSubject().getPrincipals();
for (Principal p : principals)
{
@ -214,8 +267,12 @@ public class JaspiAuthenticator extends LoginAuthenticator
{
try
{
String authContextId = _authConfig.getAuthContextID(messageInfo);
ServerAuthContext authContext = _authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties);
ServerAuthConfig authConfig = getAuthConfig();
if (authConfig == null)
throw new NullPointerException("no ServerAuthConfig found for context");
String authContextId = authConfig.getAuthContextID(messageInfo);
ServerAuthContext authContext = authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties);
// TODO
// authContext.cleanSubject(messageInfo,validatedUser.getUserIdentity().getSubject());
AuthStatus status = authContext.secureResponse(messageInfo, _serviceSubject);
@ -226,4 +283,20 @@ public class JaspiAuthenticator extends LoginAuthenticator
throw new ServerAuthException(e);
}
}
private static class JaspiAuthConfiguration extends WrappedAuthConfiguration
{
private final LoginService loginService = new EmptyLoginService();
public JaspiAuthConfiguration(AuthConfiguration configuration)
{
super(configuration);
}
@Override
public LoginService getLoginService()
{
return loginService;
}
}
}

View File

@ -14,16 +14,10 @@
package org.eclipse.jetty.security.jaspi;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.servlet.ServletContext;
import org.eclipse.jetty.security.Authenticator;
@ -32,14 +26,29 @@ import org.eclipse.jetty.security.DefaultAuthenticatorFactory;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Javax Authentication (JASPI) Authenticator Factory.
*
* This is used to link a jetty-security {@link Authenticator.Factory} to a Javax Authentication {@link AuthConfigFactory}.
* <p>
* This should be initialized with the provided {@link DefaultAuthConfigFactory} to set up Javax Authentication {@link AuthConfigFactory} before use.
* (A different {@link AuthConfigFactory} may also be provided using the same steps below)
* <p>
* To initialize either:
* <ul>
* <li>invoke {@link AuthConfigFactory#setFactory(AuthConfigFactory)}</li>
* <li>Alternatively: set {@link AuthConfigFactory#DEFAULT_FACTORY_SECURITY_PROPERTY}</li>
* </ul>
*
*/
public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory
{
private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthenticatorFactory.class);
private static String MESSAGE_LAYER = "HTTP";
public static final String MESSAGE_LAYER = "HttpServlet";
private Subject _serviceSubject;
private String _serverName;
@ -79,52 +88,26 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory
@Override
public Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService)
{
Authenticator authenticator = null;
try
{
AuthConfigFactory authConfigFactory = AuthConfigFactory.getFactory();
RegistrationListener listener = (layer, appContext) ->
{
};
AuthConfigFactory factory = AuthConfigFactory.getFactory();
if (factory == null)
return null;
Subject serviceSubject = findServiceSubject(server);
String serverName = findServerName(server);
String serverName = findServerName(context, server);
Subject serviceSubject = findServiceSubject(server);
String contextPath = StringUtil.isEmpty(context.getContextPath()) ? "/" : context.getContextPath();
String appContext = serverName + " " + contextPath;
String contextPath = context.getContextPath();
if (contextPath == null || contextPath.length() == 0)
contextPath = "/";
String appContext = serverName + " " + contextPath;
// We will only create the Authenticator if an AuthConfigProvider matches this context.
if (factory.getConfigProvider(MESSAGE_LAYER, appContext, null) == null)
return null;
AuthConfigProvider authConfigProvider = authConfigFactory.getConfigProvider(MESSAGE_LAYER, appContext, listener);
if (authConfigProvider != null)
{
ServletCallbackHandler servletCallbackHandler = new ServletCallbackHandler(loginService);
ServerAuthConfig serverAuthConfig = authConfigProvider.getServerAuthConfig(MESSAGE_LAYER, appContext, servletCallbackHandler);
if (serverAuthConfig != null)
{
Map map = new HashMap();
for (String key : configuration.getInitParameterNames())
{
map.put(key, configuration.getInitParameter(key));
}
authenticator = new JaspiAuthenticator(serverAuthConfig, map, servletCallbackHandler,
serviceSubject, true, identityService);
}
}
}
catch (AuthException e)
{
LOG.warn("Failed to get ServerAuthConfig", e);
}
return authenticator;
return new JaspiAuthenticator(serviceSubject, appContext, true);
}
/**
* Find a service Subject.
* If {@link #setServiceSubject(Subject)} has not been used to
* set a subject, then the {@link Server#getBeans(Class)} method is
* used to look for a Subject.
* Find a service Subject. If {@link #setServiceSubject(Subject)} has not been
* used to set a subject, then the {@link Server#getBeans(Class)} method is used
* to look for a Subject.
*
* @param server the server to pull the Subject from
* @return the subject
@ -140,18 +123,24 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory
}
/**
* Find a servername.
* If {@link #setServerName(String)} has not been called, then
* use the name of the a principal in the service subject.
* If not found, return "server".
* Find a servername. If {@link #setServerName(String)} has not been called,
* then use the virtualServerName of the context.
* If this is also null, then use the name of the a principal in the service subject.
* If none are found, return "server".
* @param context
*
* @param server the server to find the name of
* @return the server name from the service Subject (or default value if not found in subject or principals)
* @return the server name from the service Subject (or default value if not
* found in subject or principals)
*/
protected String findServerName(Server server)
{
protected String findServerName(ServletContext context, Server server)
{
if (_serverName != null)
return _serverName;
String virtualServerName = context.getVirtualServerName();
if (virtualServerName != null)
return virtualServerName;
Subject subject = findServiceSubject(server);
if (subject != null)

View File

@ -33,14 +33,13 @@ import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback;
import org.eclipse.jetty.server.UserIdentity;
/**
* Idiot class required by jaspi stupidity
* This {@link CallbackHandler} will bridge {@link Callback}s to handle to the given to the Jetty {@link LoginService}.
*/
public class ServletCallbackHandler implements CallbackHandler
{
private final LoginService _loginService;
private final ThreadLocal<CallerPrincipalCallback> _callerPrincipals = new ThreadLocal<CallerPrincipalCallback>();
private final ThreadLocal<GroupPrincipalCallback> _groupPrincipals = new ThreadLocal<GroupPrincipalCallback>();
private final ThreadLocal<CallerPrincipalCallback> _callerPrincipals = new ThreadLocal<>();
private final ThreadLocal<GroupPrincipalCallback> _groupPrincipals = new ThreadLocal<>();
public ServletCallbackHandler(LoginService loginService)
{
@ -64,6 +63,7 @@ public class ServletCallbackHandler implements CallbackHandler
else if (callback instanceof PasswordValidationCallback)
{
PasswordValidationCallback passwordValidationCallback = (PasswordValidationCallback)callback;
@SuppressWarnings("unused")
Subject subject = passwordValidationCallback.getSubject();
UserIdentity user = _loginService.login(passwordValidationCallback.getUsername(), passwordValidationCallback.getPassword(), null);

View File

@ -15,11 +15,14 @@ package org.eclipse.jetty.security.jaspi;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;
/**
* @deprecated use {@link org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider}.
*/
@Deprecated
public class SimpleAuthConfig implements ServerAuthConfig
{
public static final String HTTP_SERVLET = "HttpServlet";
@ -35,7 +38,7 @@ public class SimpleAuthConfig implements ServerAuthConfig
}
@Override
public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties) throws AuthException
public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties)
{
return _serverAuthContext;
}

View File

@ -39,7 +39,11 @@ import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.util.security.Password;
public class BaseAuthModule implements ServerAuthModule, ServerAuthContext
/**
* Simple abstract module implementing a Javax Authentication {@link ServerAuthModule} and {@link ServerAuthContext}.
* To be used as a building block for building more sophisticated auth modules.
*/
public abstract class BaseAuthModule implements ServerAuthModule, ServerAuthContext
{
private static final Class[] SUPPORTED_MESSAGE_TYPES = new Class[]{HttpServletRequest.class, HttpServletResponse.class};
@ -92,12 +96,6 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext
return AuthStatus.SEND_SUCCESS;
}
@Override
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
{
return AuthStatus.SEND_FAILURE;
}
/**
* @param messageInfo message info to examine for mandatory flag
* @return whether authentication is mandatory or optional
@ -110,9 +108,7 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext
return Boolean.parseBoolean(mandatory);
}
protected boolean login(Subject clientSubject, String credentials,
String authMethod, MessageInfo messageInfo)
throws IOException, UnsupportedCallbackException
protected boolean login(Subject clientSubject, String credentials, String authMethod, MessageInfo messageInfo) throws IOException, UnsupportedCallbackException
{
credentials = credentials.substring(credentials.indexOf(' ') + 1);
credentials = new String(Base64.getDecoder().decode(credentials), StandardCharsets.ISO_8859_1);
@ -122,10 +118,7 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext
return login(clientSubject, userName, new Password(password), authMethod, messageInfo);
}
protected boolean login(Subject clientSubject, String username,
Credential credential, String authMethod,
MessageInfo messageInfo)
throws IOException, UnsupportedCallbackException
protected boolean login(Subject clientSubject, String username, Credential credential, String authMethod, MessageInfo messageInfo) throws IOException, UnsupportedCallbackException
{
CredentialValidationCallback credValidationCallback = new CredentialValidationCallback(clientSubject, username, credential);
callbackHandler.handle(new Callback[]{credValidationCallback});

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.security.jaspi;
package org.eclipse.jetty.security.jaspi.modules;
import java.io.IOException;
import java.util.Map;
@ -22,46 +22,45 @@ import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.security.jaspi.modules.BaseAuthModule;
import org.eclipse.jetty.util.security.Constraint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BasicAuthModule extends BaseAuthModule
/**
* A {@link ServerAuthModule} implementation of HTTP Basic Authentication.
*/
public class BasicAuthenticationAuthModule extends BaseAuthModule
{
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthModule.class);
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthenticationAuthModule.class);
private String realmName;
private static final String REALM_KEY = "org.eclipse.jetty.security.jaspi.modules.RealmName";
public BasicAuthModule()
public BasicAuthenticationAuthModule()
{
}
public BasicAuthModule(CallbackHandler callbackHandler, String realmName)
public BasicAuthenticationAuthModule(CallbackHandler callbackHandler, String realmName)
{
super(callbackHandler);
this.realmName = realmName;
}
@Override
public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy,
CallbackHandler handler, Map options)
throws AuthException
public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler callbackHandler, Map options) throws AuthException
{
super.initialize(requestPolicy, responsePolicy, handler, options);
super.initialize(requestPolicy, responsePolicy, callbackHandler, options);
realmName = (String)options.get(REALM_KEY);
}
@Override
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject,
Subject serviceSubject)
throws AuthException
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
{
HttpServletRequest request = (HttpServletRequest)messageInfo.getRequestMessage();
HttpServletResponse response = (HttpServletResponse)messageInfo.getResponseMessage();
@ -87,11 +86,7 @@ public class BasicAuthModule extends BaseAuthModule
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return AuthStatus.SEND_CONTINUE;
}
catch (IOException e)
{
throw new AuthException(e.getMessage());
}
catch (UnsupportedCallbackException e)
catch (IOException | UnsupportedCallbackException e)
{
throw new AuthException(e.getMessage());
}

View File

@ -1,45 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.jaspi.modules;
import java.util.Arrays;
public class UserInfo
{
private final String userName;
private char[] password;
public UserInfo(String userName, char[] password)
{
this.userName = userName;
this.password = password;
}
public String getUserName()
{
return userName;
}
public char[] getPassword()
{
return password;
}
public void clearPassword()
{
Arrays.fill(password, (char)0);
password = null;
}
}

View File

@ -0,0 +1,132 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.jaspi.provider;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.ClientAuthConfig;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.module.ServerAuthModule;
import org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A Jetty implementation of the {@link AuthConfigProvider} to allow registration of a {@link ServerAuthModule}
* directly without having to write a custom {@link AuthConfigProvider}.</p>
* <p>If this is being constructed by an {@link AuthConfigFactory} after being passed in as a className, then
* you will need to provide the property {@code ServerAuthModule} containing the fully qualified name of
* the {@link ServerAuthModule} class you wish to use.</p>
*/
@SuppressWarnings("rawtypes")
public class JaspiAuthConfigProvider implements AuthConfigProvider
{
private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthConfigProvider.class);
private final Map providerProperties;
private final ServerAuthModule serverAuthModule;
/**
* <p>Constructor with signature and implementation that's required by API.</p>
* <p>The property map must contain the {@code ServerAuthModule} property containing the fully qualified name of
* the {@link ServerAuthModule} class you wish to use. If this constructor is being used for self-registration an
* optional property of {@code appContext} can be used specify the appContext value to register the provider.</p>
*
* @param properties A Map of initialization properties.
* @param factory The {@link AuthConfigFactory} to register on.
*/
public JaspiAuthConfigProvider(Map properties, AuthConfigFactory factory)
{
if (properties == null || !properties.containsKey("ServerAuthModule"))
throw new IllegalArgumentException("Missing property 'ServerAuthModule', cannot create JaspiAuthConfigProvider");
this.providerProperties = Map.copyOf(properties);
this.serverAuthModule = createServerAuthModule((String)properties.get("ServerAuthModule"));
// API requires self registration if factory is provided.
if (factory != null)
factory.registerConfigProvider(this, JaspiAuthenticatorFactory.MESSAGE_LAYER, (String)properties.get("appContext"), "Self Registration");
}
/**
* @param className The fully qualified name of a {@link ServerAuthModule} class.
*/
public JaspiAuthConfigProvider(String className)
{
this(className, null);
}
/**
* @param className The fully qualified name of a {@link ServerAuthModule} class.
* @param properties A Map of initialization properties.
*/
public JaspiAuthConfigProvider(String className, Map properties)
{
this(createServerAuthModule(className), properties);
}
/**
* @param serverAuthModule The instance of {@link ServerAuthModule} to use.
*/
public JaspiAuthConfigProvider(ServerAuthModule serverAuthModule)
{
this.serverAuthModule = Objects.requireNonNull(serverAuthModule);
this.providerProperties = Collections.emptyMap();
}
/**
* @param serverAuthModule The instance of {@link ServerAuthModule} to use.
* @param properties A Map of initialization properties.
*/
public JaspiAuthConfigProvider(ServerAuthModule serverAuthModule, Map properties)
{
this.serverAuthModule = Objects.requireNonNull(serverAuthModule);
this.providerProperties = properties == null ? Collections.emptyMap() : Map.copyOf(properties);
}
@Override
public ClientAuthConfig getClientAuthConfig(String layer, String appContext, CallbackHandler handler)
{
return null;
}
@Override
public ServerAuthConfig getServerAuthConfig(String layer, String appContext, CallbackHandler handler)
{
if (LOG.isDebugEnabled())
LOG.debug("getServerAuthConfig");
return new SimpleAuthConfig(layer, appContext, handler, providerProperties, serverAuthModule);
}
@Override
public void refresh()
{
}
private static ServerAuthModule createServerAuthModule(String serverAuthModuleClassName)
{
try
{
return (ServerAuthModule)Class.forName(serverAuthModuleClassName).getDeclaredConstructor().newInstance();
}
catch (ReflectiveOperationException e)
{
throw new IllegalStateException(e);
}
}
}

View File

@ -0,0 +1,83 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.jaspi.provider;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
/**
* Simple implementation of the {@link ServerAuthConfig} interface.
*
* This implementation wires up the given {@link ServerAuthModule} to the appropriate Javax Authentication {@link ServerAuthContext} responsible
* for providing it.
*/
@SuppressWarnings("rawtypes")
class SimpleAuthConfig implements ServerAuthConfig
{
private final String _messageLayer;
private final String _appContext;
private final CallbackHandler _callbackHandler;
private final Map _properties;
private final ServerAuthModule _serverAuthModule;
public SimpleAuthConfig(String messageLayer, String appContext, CallbackHandler callbackHandler, Map properties, ServerAuthModule serverAuthModule)
{
_messageLayer = messageLayer;
_appContext = appContext;
_callbackHandler = callbackHandler;
_properties = properties;
_serverAuthModule = serverAuthModule;
}
@Override
public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties) throws AuthException
{
return new SimpleServerAuthContext(_callbackHandler, _serverAuthModule, _properties);
}
@Override
public String getAppContext()
{
return _appContext;
}
@Override
public String getAuthContextID(MessageInfo messageInfo)
{
return null;
}
@Override
public String getMessageLayer()
{
return _messageLayer;
}
@Override
public boolean isProtected()
{
return true;
}
@Override
public void refresh()
{
}
}

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.jaspi.provider;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
/**
* Simple bridge implementation of the Javax Authentication {@link ServerAuthContext} interface.
*
* This implementation will only delegate to the provided {@link ServerAuthModule} implementation.
*/
class SimpleServerAuthContext implements ServerAuthContext
{
private final ServerAuthModule serverAuthModule;
@SuppressWarnings("rawtypes")
public SimpleServerAuthContext(CallbackHandler callbackHandler, ServerAuthModule serverAuthModule, Map properties) throws AuthException
{
this.serverAuthModule = serverAuthModule;
serverAuthModule.initialize(null, null, callbackHandler, properties);
}
@Override
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
{
return serverAuthModule.validateRequest(messageInfo, clientSubject, serviceSubject);
}
@Override
public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException
{
return serverAuthModule.secureResponse(messageInfo, serviceSubject);
}
@Override
public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException
{
serverAuthModule.cleanSubject(messageInfo, subject);
}
}

View File

@ -1 +1 @@
org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory
org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory

View File

@ -0,0 +1,115 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.jaspi;
import java.util.Map;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class DefaultAuthConfigFactoryTest
{
private static final String MESSAGE_LAYER = "HttpServlet";
private final String jettyAuthConfigProvider = "org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider";
private final String appContext = "server /test";
private final Map<String, String> serverAuthModuleProperties = Map.of("ServerAuthModule",
"org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule", "AppContextID", appContext,
"org.eclipse.jetty.security.jaspi.modules.RealmName", "TestRealm");
private final String serverAuthModuleClassName = "org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule";
@Test
public void testRegisterConfigProviderByClassName() throws Exception
{
AuthConfigFactory factory = new DefaultAuthConfigFactory();
String registrationId = factory.registerConfigProvider(jettyAuthConfigProvider,
serverAuthModuleProperties, MESSAGE_LAYER, appContext, "a test provider");
AuthConfigProvider registeredProvider = factory.getConfigProvider(MESSAGE_LAYER, appContext, null);
assertThat(registeredProvider, instanceOf(JaspiAuthConfigProvider.class));
assertThat(registeredProvider.getServerAuthConfig(MESSAGE_LAYER, appContext, null), notNullValue());
assertThat(factory.getRegistrationContext(registrationId), notNullValue());
assertThat(factory.getRegistrationIDs(registeredProvider), arrayContaining(registrationId));
}
@Test
public void testRegisterAuthConfigProviderDirect() throws Exception
{
AuthConfigProvider provider = new JaspiAuthConfigProvider(
serverAuthModuleClassName,
serverAuthModuleProperties);
AuthConfigFactory factory = new DefaultAuthConfigFactory();
String registrationId = factory.registerConfigProvider(provider, MESSAGE_LAYER, appContext, "a test provider");
AuthConfigProvider registeredProvider = factory.getConfigProvider(MESSAGE_LAYER, appContext, null);
assertThat(registeredProvider, instanceOf(JaspiAuthConfigProvider.class));
assertThat(registeredProvider.getServerAuthConfig(MESSAGE_LAYER, appContext, null), notNullValue());
assertThat(factory.getRegistrationContext(registrationId), notNullValue());
assertThat(factory.getRegistrationIDs(registeredProvider), arrayContaining(registrationId));
}
@Test
public void testRemoveRegistration() throws Exception
{
// Arrange
AuthConfigProvider provider = new JaspiAuthConfigProvider(
serverAuthModuleClassName,
serverAuthModuleProperties);
AuthConfigFactory factory = new DefaultAuthConfigFactory();
String registrationId = factory.registerConfigProvider(provider, MESSAGE_LAYER, appContext, "a test provider");
DummyRegistrationListener dummyListener = new DummyRegistrationListener();
assertThat(factory.getConfigProvider(MESSAGE_LAYER, appContext, dummyListener), notNullValue());
// Act
factory.removeRegistration(registrationId);
// Assert config provider removed
assertThat(factory.getConfigProvider(MESSAGE_LAYER, appContext, null), nullValue());
// Assert listeners invoked
assertThat(dummyListener.appContext, equalTo(appContext));
assertThat(dummyListener.layer, equalTo(MESSAGE_LAYER));
}
static class DummyRegistrationListener implements RegistrationListener
{
String layer;
String appContext;
@Override
public void notify(String layer, String appContext)
{
this.layer = layer;
this.appContext = appContext;
}
}
}

View File

@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -39,7 +40,9 @@ import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.util.security.Password;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -52,7 +55,7 @@ public class JaspiTest
Server _server;
LocalConnector _connector;
public class TestLoginService extends AbstractLoginService
public static class TestLoginService extends AbstractLoginService
{
protected Map<String, UserPrincipal> _users = new HashMap<>();
protected Map<String, List<RolePrincipal>> _roles = new HashMap<>();
@ -86,10 +89,34 @@ public class JaspiTest
}
}
@BeforeAll
public static void beforeAll() throws Exception
{
AuthConfigFactory factory = new DefaultAuthConfigFactory();
factory.registerConfigProvider("org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider",
Map.of("ServerAuthModule", "org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule",
"AppContextID", "server /ctx",
"org.eclipse.jetty.security.jaspi.modules.RealmName", "TestRealm"),
"HttpServlet", "server /ctx", "a test provider");
factory.registerConfigProvider("org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider",
Map.of("ServerAuthModule", "org.eclipse.jetty.security.jaspi.HttpHeaderAuthModule",
"AppContextID", "server /other"),
"HttpServlet", "server /other", "another test provider");
AuthConfigFactory.setFactory(factory);
}
@AfterAll
public static void afterAll() throws Exception
{
AuthConfigFactory.setFactory(null);
}
@BeforeEach
public void before() throws Exception
{
System.setProperty("org.apache.geronimo.jaspic.configurationFile", "src/test/resources/jaspi.xml");
_server = new Server();
_connector = new LocalConnector(_server);
_server.addConnector(_connector);
@ -98,8 +125,8 @@ public class JaspiTest
_server.setHandler(contexts);
TestLoginService loginService = new TestLoginService("TestRealm");
loginService.putUser("user", new Password("password"), new String[]{"users"});
loginService.putUser("admin", new Password("secret"), new String[]{"users", "admins"});
loginService.putUser("user", new Password("password"), new String[] {"users"});
loginService.putUser("admin", new Password("secret"), new String[] {"users", "admins"});
_server.addBean(loginService);
ContextHandler context = new ContextHandler();
@ -159,9 +186,8 @@ public class JaspiTest
@Test
public void testConstraintWrongAuth() throws Exception
{
String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" +
"Authorization: Basic " + Base64.getEncoder().encodeToString("user:wrong".getBytes(ISO_8859_1)) +
"\n\n");
String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" + "Authorization: Basic " +
Base64.getEncoder().encodeToString("user:wrong".getBytes(ISO_8859_1)) + "\n\n");
assertThat(response, startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
}
@ -169,9 +195,8 @@ public class JaspiTest
@Test
public void testConstraintAuth() throws Exception
{
String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" +
"Authorization: Basic " + Base64.getEncoder().encodeToString("user:password".getBytes(ISO_8859_1)) +
"\n\n");
String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" + "Authorization: Basic " +
Base64.getEncoder().encodeToString("user:password".getBytes(ISO_8859_1)) + "\n\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
}
@ -185,8 +210,7 @@ public class JaspiTest
@Test
public void testOtherAuth() throws Exception
{
String response = _connector.getResponse("GET /other/test HTTP/1.0\n" +
"X-Forwarded-User: user\n\n");
String response = _connector.getResponse("GET /other/test HTTP/1.0\n" + "X-Forwarded-User: user\n\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
}
@ -194,7 +218,8 @@ public class JaspiTest
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(200);

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<jaspi xmlns="http://geronimo.apache.org/xml/ns/geronimo-jaspi">
<configProvider>
<messageLayer>HTTP</messageLayer>
<appContext>server /ctx</appContext>
<description>description</description>
<serverAuthConfig>
<authenticationContextID>authenticationContextID1</authenticationContextID>
<protected>true</protected>
<serverAuthContext>
<serverAuthModule>
<className>org.eclipse.jetty.security.jaspi.BasicAuthModule</className>
<options>
org.eclipse.jetty.security.jaspi.modules.RealmName=TestRealm
</options>
</serverAuthModule>
</serverAuthContext>
</serverAuthConfig>
<persistent>true</persistent>
</configProvider>
<configProvider>
<messageLayer>HTTP</messageLayer>
<appContext>server /other</appContext>
<description>description</description>
<serverAuthConfig>
<authenticationContextID>authenticationContextID2</authenticationContextID>
<protected>true</protected>
<serverAuthContext>
<serverAuthModule>
<className>org.eclipse.jetty.security.jaspi.HttpHeaderAuthModule</className>
<options>
</options>
</serverAuthModule>
</serverAuthContext>
</serverAuthConfig>
<persistent>true</persistent>
</configProvider>
</jaspi>

View File

@ -125,6 +125,11 @@
<artifactId>maven-artifact-transfer</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>

View File

@ -28,7 +28,6 @@ import java.util.jar.Manifest;
import org.codehaus.plexus.util.SelectorUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.JarResource;
import org.slf4j.Logger;
@ -87,7 +86,7 @@ public class SelectiveJarResource extends JarResource
{
for (String include : _includes)
{
if (SelectorUtils.matchPath(include, name, _caseSensitive))
if (SelectorUtils.matchPath(include, name, "/", _caseSensitive))
{
return true;
}
@ -99,7 +98,7 @@ public class SelectiveJarResource extends JarResource
{
for (String exclude : _excludes)
{
if (SelectorUtils.matchPath(exclude, name, _caseSensitive))
if (SelectorUtils.matchPath(exclude, name, "/", _caseSensitive))
{
return true;
}
@ -140,8 +139,8 @@ public class SelectiveJarResource extends JarResource
String entryName = entry.getName();
LOG.debug("Looking at {}", entryName);
String dotCheck = StringUtil.replace(entryName, '\\', '/');
dotCheck = URIUtil.canonicalPath(dotCheck);
// make sure no access out of the root entry is present
String dotCheck = URIUtil.canonicalPath(entryName);
if (dotCheck == null)
{
LOG.info("Invalid entry: {}", entryName);

View File

@ -13,96 +13,81 @@
package org.eclipse.jetty.maven.plugin;
import java.io.File;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
*
*
*/
@ExtendWith(WorkDirExtension.class)
public class TestSelectiveJarResource
{
File unpackParent;
@BeforeEach
public void setUp() throws Exception
{
unpackParent = MavenTestingUtils.getTargetTestingDir("selective-jar-resource");
unpackParent.mkdirs();
}
public WorkDir workDir;
@Test
public void testIncludesNoExcludes() throws Exception
{
File unpackDir = File.createTempFile("inc", "exc", unpackParent);
unpackDir.delete();
unpackDir.mkdirs();
Path unpackDir = workDir.getEmptyPathDir();
File testJar = MavenTestingUtils.getTestResourceFile("selective-jar-test.jar");
try (SelectiveJarResource sjr = new SelectiveJarResource(new URL("jar:" + Resource.toURL(testJar).toString() + "!/"));)
Path testJar = MavenTestingUtils.getTestResourcePathFile("selective-jar-test.jar");
try (SelectiveJarResource sjr = new SelectiveJarResource(new URL("jar:" + testJar.toUri().toASCIIString() + "!/")))
{
sjr.setCaseSensitive(false);
List<String> includes = new ArrayList<>();
includes.add("**/*.html");
sjr.setIncludes(includes);
sjr.copyTo(unpackDir);
assertTrue(Files.exists(unpackDir.toPath().resolve("top.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("aa/a1.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("aa/a2.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("aa/deep/a3.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("bb/b1.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("bb/b2.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("cc/c1.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("cc/c2.html")));
sjr.copyTo(unpackDir.toFile());
assertTrue(Files.exists(unpackDir.resolve("top.html")));
assertTrue(Files.exists(unpackDir.resolve("aa/a1.html")));
assertTrue(Files.exists(unpackDir.resolve("aa/a2.html")));
assertTrue(Files.exists(unpackDir.resolve("aa/deep/a3.html")));
assertTrue(Files.exists(unpackDir.resolve("bb/b1.html")));
assertTrue(Files.exists(unpackDir.resolve("bb/b2.html")));
assertTrue(Files.exists(unpackDir.resolve("cc/c1.html")));
assertTrue(Files.exists(unpackDir.resolve("cc/c2.html")));
}
}
@Test
public void testExcludesNoIncludes() throws Exception
{
File unpackDir = File.createTempFile("exc", "inc", unpackParent);
unpackDir.delete();
unpackDir.mkdirs();
File testJar = MavenTestingUtils.getTestResourceFile("selective-jar-test.jar");
try (SelectiveJarResource sjr = new SelectiveJarResource(new URL("jar:" + Resource.toURL(testJar).toString() + "!/"));)
Path unpackDir = workDir.getEmptyPathDir();
Path testJar = MavenTestingUtils.getTestResourcePathFile("selective-jar-test.jar");
try (SelectiveJarResource sjr = new SelectiveJarResource(new URL("jar:" + testJar.toUri().toASCIIString() + "!/")))
{
sjr.setCaseSensitive(false);
List<String> excludes = new ArrayList<>();
excludes.add("**/*");
sjr.setExcludes(excludes);
sjr.copyTo(unpackDir);
assertFalse(Files.exists(unpackDir.toPath().resolve("top.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("aa/a1.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("aa/a2.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("aa/deep/a3.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("bb/b1.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("bb/b2.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("cc/c1.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("cc/c2.html")));
sjr.copyTo(unpackDir.toFile());
assertFalse(Files.exists(unpackDir.resolve("top.html")));
assertFalse(Files.exists(unpackDir.resolve("aa/a1.html")));
assertFalse(Files.exists(unpackDir.resolve("aa/a2.html")));
assertFalse(Files.exists(unpackDir.resolve("aa/deep/a3.html")));
assertFalse(Files.exists(unpackDir.resolve("bb/b1.html")));
assertFalse(Files.exists(unpackDir.resolve("bb/b2.html")));
assertFalse(Files.exists(unpackDir.resolve("cc/c1.html")));
assertFalse(Files.exists(unpackDir.resolve("cc/c2.html")));
}
}
@Test
public void testIncludesExcludes() throws Exception
{
File unpackDir = File.createTempFile("exc", "andinc", unpackParent);
unpackDir.delete();
unpackDir.mkdirs();
File testJar = MavenTestingUtils.getTestResourceFile("selective-jar-test.jar");
try (SelectiveJarResource sjr = new SelectiveJarResource(new URL("jar:" + Resource.toURL(testJar).toString() + "!/"));)
Path unpackDir = workDir.getEmptyPathDir();
Path testJar = MavenTestingUtils.getTestResourcePathFile("selective-jar-test.jar");
try (SelectiveJarResource sjr = new SelectiveJarResource(new URL("jar:" + testJar.toUri().toASCIIString() + "!/")))
{
sjr.setCaseSensitive(false);
List<String> excludes = new ArrayList<>();
@ -111,15 +96,15 @@ public class TestSelectiveJarResource
List<String> includes = new ArrayList<>();
includes.add("bb/*");
sjr.setIncludes(includes);
sjr.copyTo(unpackDir);
assertFalse(Files.exists(unpackDir.toPath().resolve("top.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("aa/a1.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("aa/a2.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("aa/deep/a3.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("bb/b1.html")));
assertTrue(Files.exists(unpackDir.toPath().resolve("bb/b2.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("cc/c1.html")));
assertFalse(Files.exists(unpackDir.toPath().resolve("cc/c2.html")));
sjr.copyTo(unpackDir.toFile());
assertFalse(Files.exists(unpackDir.resolve("top.html")));
assertFalse(Files.exists(unpackDir.resolve("aa/a1.html")));
assertFalse(Files.exists(unpackDir.resolve("aa/a2.html")));
assertFalse(Files.exists(unpackDir.resolve("aa/deep/a3.html")));
assertTrue(Files.exists(unpackDir.resolve("bb/b1.html")));
assertTrue(Files.exists(unpackDir.resolve("bb/b2.html")));
assertFalse(Files.exists(unpackDir.resolve("cc/c1.html")));
assertFalse(Files.exists(unpackDir.resolve("cc/c2.html")));
}
}
}

View File

@ -0,0 +1,5 @@
# Jetty Logging using jetty-slf4j-impl
#org.eclipse.jetty.maven.plugin.LEVEL=DEBUG
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.http.LEVEL=DEBUG

View File

@ -26,6 +26,7 @@
<Arg><Property name="jetty.openid.provider.tokenEndpoint"/></Arg>
<Arg><Property name="jetty.openid.clientId"/></Arg>
<Arg><Property name="jetty.openid.clientSecret"/></Arg>
<Arg><Property name="jetty.openid.authMethod" default="client_secret_post"/></Arg>
<Arg><Ref refid="HttpClient"/></Arg>
<Call name="addScopes">
<Arg>

View File

@ -42,3 +42,6 @@ etc/jetty-openid.xml
## True if all certificates should be trusted by the default SslContextFactory
# jetty.openid.sslContextFactory.trustAll=false
## What authentication method to use with the Token Endpoint (client_secret_post, client_secret_basic).
# jetty.openid.authMethod=client_secret_post

View File

@ -35,6 +35,7 @@ public class JwtDecoder
* @param jwt the JWT to decode.
* @return the map of claims encoded in the JWT.
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> decode(String jwt)
{
if (LOG.isDebugEnabled())
@ -54,7 +55,6 @@ public class JwtDecoder
Object parsedJwtHeader = json.fromJSON(jwtHeaderString);
if (!(parsedJwtHeader instanceof Map))
throw new IllegalStateException("Invalid JWT header");
@SuppressWarnings("unchecked")
Map<String, Object> jwtHeader = (Map<String, Object>)parsedJwtHeader;
if (LOG.isDebugEnabled())
LOG.debug("JWT Header: {}", jwtHeader);

View File

@ -45,6 +45,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
private final String clientId;
private final String clientSecret;
private final List<String> scopes = new ArrayList<>();
private final String authMethod;
private String authEndpoint;
private String tokenEndpoint;
@ -70,6 +71,22 @@ public class OpenIdConfiguration extends ContainerLifeCycle
*/
public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
String clientId, String clientSecret, HttpClient httpClient)
{
this(issuer, authorizationEndpoint, tokenEndpoint, clientId, clientSecret, "client_secret_post", httpClient);
}
/**
* Create an OpenID configuration for a specific OIDC provider.
* @param issuer The URL of the OpenID provider.
* @param authorizationEndpoint the URL of the OpenID provider's authorization endpoint if configured.
* @param tokenEndpoint the URL of the OpenID provider's token endpoint if configured.
* @param clientId OAuth 2.0 Client Identifier valid at the Authorization Server.
* @param clientSecret The client secret known only by the Client and the Authorization Server.
* @param authMethod Authentication method to use with the Token Endpoint.
* @param httpClient The {@link HttpClient} instance to use.
*/
public OpenIdConfiguration(String issuer, String authorizationEndpoint, String tokenEndpoint,
String clientId, String clientSecret, String authMethod, HttpClient httpClient)
{
this.issuer = issuer;
this.clientId = clientId;
@ -77,6 +94,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle
this.authEndpoint = authorizationEndpoint;
this.tokenEndpoint = tokenEndpoint;
this.httpClient = httpClient != null ? httpClient : newHttpClient();
this.authMethod = authMethod;
if (this.issuer == null)
throw new IllegalArgumentException("Issuer was not configured");
@ -177,6 +195,11 @@ public class OpenIdConfiguration extends ContainerLifeCycle
return tokenEndpoint;
}
public String getAuthMethod()
{
return authMethod;
}
public void addScopes(String... scopes)
{
if (scopes != null)

View File

@ -14,12 +14,16 @@
package org.eclipse.jetty.security.openid;
import java.io.Serializable;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.FormRequestContent;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.ajax.JSON;
@ -45,6 +49,14 @@ public class OpenIdCredentials implements Serializable
private String authCode;
private Map<String, Object> response;
private Map<String, Object> claims;
private boolean verified = false;
public OpenIdCredentials(Map<String, Object> claims)
{
this.redirectUri = null;
this.authCode = null;
this.claims = claims;
}
public OpenIdCredentials(String authCode, String redirectUri)
{
@ -95,7 +107,6 @@ public class OpenIdCredentials implements Serializable
claims = JwtDecoder.decode(idToken);
if (LOG.isDebugEnabled())
LOG.debug("claims {}", claims);
validateClaims(configuration);
}
finally
{
@ -103,6 +114,12 @@ public class OpenIdCredentials implements Serializable
authCode = null;
}
}
if (!verified)
{
validateClaims(configuration);
verified = true;
}
}
private void validateClaims(OpenIdConfiguration configuration) throws Exception
@ -138,10 +155,11 @@ public class OpenIdCredentials implements Serializable
throw new AuthenticationException("Audience Claim MUST contain the client_id value");
else if (isList)
{
if (!Arrays.asList((Object[])aud).contains(clientId))
List<Object> list = Arrays.asList((Object[])aud);
if (!list.contains(clientId))
throw new AuthenticationException("Audience Claim MUST contain the client_id value");
if (claims.get("azp") == null)
if (list.size() > 1 && claims.get("azp") == null)
throw new AuthenticationException("A multi-audience ID token needs to contain an azp claim");
}
else if (!isValidType)
@ -153,14 +171,27 @@ public class OpenIdCredentials implements Serializable
{
Fields fields = new Fields();
fields.add("code", authCode);
fields.add("client_id", configuration.getClientId());
fields.add("client_secret", configuration.getClientSecret());
fields.add("redirect_uri", redirectUri);
fields.add("grant_type", "authorization_code");
Request request = configuration.getHttpClient().POST(configuration.getTokenEndpoint());
switch (configuration.getAuthMethod())
{
case "client_secret_basic":
URI uri = URI.create(configuration.getTokenEndpoint());
Authentication.Result authentication = new BasicAuthentication.BasicResult(uri, configuration.getClientId(), configuration.getClientSecret());
authentication.apply(request);
break;
case "client_secret_post":
fields.add("client_id", configuration.getClientId());
fields.add("client_secret", configuration.getClientSecret());
break;
default:
throw new IllegalStateException(configuration.getAuthMethod());
}
FormRequestContent formContent = new FormRequestContent(fields);
Request request = configuration.getHttpClient().POST(configuration.getTokenEndpoint())
.body(formContent)
.timeout(10, TimeUnit.SECONDS);
request = request.body(formContent).timeout(10, TimeUnit.SECONDS);
ContentResponse response = request.send();
String responseBody = response.getContentAsString();
if (LOG.isDebugEnabled())

View File

@ -0,0 +1,40 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.openid;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.client.HttpClient;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
public class OpenIdCredentialsTest
{
@Test
public void testSingleAudienceValueInArray() throws Exception
{
String issuer = "myIssuer123";
String clientId = "myClientId456";
OpenIdConfiguration configuration = new OpenIdConfiguration(issuer, "", "", clientId, "", new HttpClient());
Map<String, Object> claims = new HashMap<>();
claims.put("iss", issuer);
claims.put("aud", new String[]{clientId});
claims.put("exp", System.currentTimeMillis() + 5000);
assertDoesNotThrow(() -> new OpenIdCredentials(claims).redeemAuthCode(configuration));
}
}

View File

@ -1290,7 +1290,7 @@ public class AsyncMiddleManServletTest
startClient();
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.timeout(10, TimeUnit.SECONDS)
.send();
assertEquals(200, response.getStatus());

View File

@ -62,7 +62,7 @@ public class DefaultAuthenticatorFactory implements Authenticator.Factory
String auth = configuration.getAuthMethod();
Authenticator authenticator = null;
if (auth == null || Constraint.__BASIC_AUTH.equalsIgnoreCase(auth))
if (Constraint.__BASIC_AUTH.equalsIgnoreCase(auth))
authenticator = new BasicAuthenticator();
else if (Constraint.__DIGEST_AUTH.equalsIgnoreCase(auth))
authenticator = new DigestAuthenticator();

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security;
import javax.servlet.ServletRequest;
import org.eclipse.jetty.server.UserIdentity;
/**
* LoginService implementation which always denies any attempt to login.
*/
public class EmptyLoginService implements LoginService
{
@Override
public String getName()
{
return null;
}
@Override
public UserIdentity login(String username, Object credentials, ServletRequest request)
{
return null;
}
@Override
public boolean validate(UserIdentity user)
{
return false;
}
@Override
public IdentityService getIdentityService()
{
return null;
}
@Override
public void setIdentityService(IdentityService service)
{
}
@Override
public void logout(UserIdentity user)
{
}
}

View File

@ -293,9 +293,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
return getServer().getBean(IdentityService.class);
}
/**
*
*/
@Override
protected void doStart()
throws Exception
@ -334,11 +331,8 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
if (_identityService == null)
{
if (_realmName != null)
{
setIdentityService(new DefaultIdentityService());
manage(_identityService);
}
setIdentityService(new DefaultIdentityService());
manage(_identityService);
}
else
unmanage(_identityService);
@ -352,7 +346,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
throw new IllegalStateException("LoginService has different IdentityService to " + this);
}
if (_authenticator == null && _identityService != null)
if (_authenticator == null)
{
// If someone has set an authenticator factory only use that, otherwise try the list of discovered factories.
if (_authenticatorFactory != null)
@ -399,7 +393,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
}
@Override
protected void doStop() throws Exception
{
//if we discovered the services (rather than had them explicitly configured), remove them.
@ -572,6 +565,11 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
authenticator.secureResponse(request, response, isAuthMandatory, null);
}
}
else if (isAuthMandatory)
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "unauthenticated");
baseRequest.setHandled(true);
}
else
{
baseRequest.setAuthentication(authentication);

View File

@ -0,0 +1,74 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security;
import java.util.Set;
import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
/**
* A wrapper for {@link AuthConfiguration}. This allows you create a new AuthConfiguration which can
* override a method to change a value from an another instance of AuthConfiguration.
*/
public class WrappedAuthConfiguration implements AuthConfiguration
{
private final AuthConfiguration _configuration;
public WrappedAuthConfiguration(AuthConfiguration configuration)
{
_configuration = configuration;
}
@Override
public String getAuthMethod()
{
return _configuration.getAuthMethod();
}
@Override
public String getRealmName()
{
return _configuration.getRealmName();
}
@Override
public String getInitParameter(String param)
{
return _configuration.getInitParameter(param);
}
@Override
public Set<String> getInitParameterNames()
{
return _configuration.getInitParameterNames();
}
@Override
public LoginService getLoginService()
{
return _configuration.getLoginService();
}
@Override
public IdentityService getIdentityService()
{
return _configuration.getIdentityService();
}
@Override
public boolean isSessionRenewedOnAuthentication()
{
return _configuration.isSessionRenewedOnAuthentication();
}
}

View File

@ -0,0 +1,89 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Server;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
public class DefaultIdentityServiceTest
{
@Test
public void testDefaultIdentityService() throws Exception
{
Server server = new Server();
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
TestAuthenticator authenticator = new TestAuthenticator();
securityHandler.setAuthenticator(authenticator);
try
{
server.setHandler(securityHandler);
server.start();
// The DefaultIdentityService should have been created by default.
assertThat(securityHandler.getIdentityService(), instanceOf(DefaultIdentityService.class));
assertThat(authenticator.getIdentityService(), instanceOf(DefaultIdentityService.class));
}
finally
{
server.stop();
}
}
public static class TestAuthenticator implements Authenticator
{
private IdentityService _identityService;
public IdentityService getIdentityService()
{
return _identityService;
}
@Override
public void setConfiguration(AuthConfiguration configuration)
{
_identityService = configuration.getIdentityService();
}
@Override
public String getAuthMethod()
{
return getClass().getSimpleName();
}
@Override
public void prepareRequest(ServletRequest request)
{
}
@Override
public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
{
return null;
}
@Override
public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException
{
return false;
}
}
}

View File

@ -48,7 +48,6 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.condition.OS.MAC;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
@ExtendWith(WorkDirExtension.class)
public class PropertyUserStoreTest
@ -277,7 +276,6 @@ public class PropertyUserStoreTest
}
@Test
@DisabledOnOs({MAC, WINDOWS}) // File is locked on OS, cannot change.
public void testPropertyUserStoreLoadRemoveUser() throws Exception
{
testdir.ensureEmpty();

View File

@ -0,0 +1,145 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.security.Constraint;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
public class UnauthenticatedTest
{
private LocalConnector connector;
private TestAuthenticator authenticator;
@BeforeEach
public void beforeEach() throws Exception
{
Server server = new Server();
connector = new LocalConnector(server);
server.addConnector(connector);
// Authenticator that always returns UNAUTHENTICATED.
authenticator = new TestAuthenticator();
// Add a security handler which requires paths under /requireAuth to be authenticated.
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
Constraint requireAuthentication = new Constraint();
requireAuthentication.setRoles(new String[]{"**"});
requireAuthentication.setAuthenticate(true);
ConstraintMapping constraintMapping = new ConstraintMapping();
constraintMapping.setPathSpec("/requireAuth/*");
constraintMapping.setConstraint(requireAuthentication);
securityHandler.addConstraintMapping(constraintMapping);
securityHandler.setAuthenticator(authenticator);
securityHandler.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.OK_200);
response.getWriter().println("authentication: " + baseRequest.getAuthentication());
}
});
server.setHandler(securityHandler);
server.start();
}
@Test
public void testUnauthenticated() throws Exception
{
TestAuthenticator.AUTHENTICATION.set(Authentication.UNAUTHENTICATED);
// Request to URI which doesn't require authentication can get through even though auth is UNAUTHENTICATED.
String response = connector.getResponse("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("authentication: UNAUTHENTICATED"));
// This URI requires just that the request is authenticated.
response = connector.getResponse("GET /requireAuth/test HTTP/1.1\r\nHost: localhost\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 401 Unauthorized"));
}
@Test
public void testDeferredAuth() throws Exception
{
TestAuthenticator.AUTHENTICATION.set(new DeferredAuthentication(authenticator));
// Request to URI which doesn't require authentication can get through even though auth is UNAUTHENTICATED.
String response = connector.getResponse("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("DeferredAuthentication"));
// This URI requires just that the request is authenticated. But DeferredAuthentication can bypass this.
response = connector.getResponse("GET /requireAuth/test HTTP/1.1\r\nHost: localhost\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("DeferredAuthentication"));
}
public static class TestAuthenticator extends LoginAuthenticator
{
static AtomicReference<Authentication> AUTHENTICATION = new AtomicReference<>();
@Override
public void setConfiguration(AuthConfiguration configuration)
{
// Do nothing.
}
@Override
public String getAuthMethod()
{
return this.getClass().getSimpleName();
}
@Override
public void prepareRequest(ServletRequest request)
{
// Do nothing.
}
@Override
public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
{
return AUTHENTICATION.get();
}
@Override
public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException
{
return true;
}
}
}

View File

@ -56,6 +56,8 @@ import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.util.thread.Invocable.InvocationType.NON_BLOCKING;
/**
* HttpChannel represents a single endpoint for HTTP semantic processing.
* The HttpChannel is both an HttpParser.RequestHandler, where it passively receives events from
@ -540,7 +542,7 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
break;
// Set a close callback on the HttpOutput to make it an async callback
_response.completeOutput(Callback.from(() -> _state.completed(null), _state::completed));
_response.completeOutput(Callback.from(NON_BLOCKING, () -> _state.completed(null), _state::completed));
break;
}

View File

@ -725,7 +725,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
if (content == null)
{
new AsyncFlush(false).iterate();
}
else
{
try
@ -1534,7 +1536,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
final boolean _last;
ChannelWriteCB(boolean last)
private ChannelWriteCB(boolean last)
{
_last = last;
}
@ -1560,14 +1562,20 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private abstract class NestedChannelWriteCB extends ChannelWriteCB
{
final Callback _callback;
private final Callback _callback;
NestedChannelWriteCB(Callback callback, boolean last)
private NestedChannelWriteCB(Callback callback, boolean last)
{
super(last);
_callback = callback;
}
@Override
public InvocationType getInvocationType()
{
return _callback.getInvocationType();
}
@Override
protected void onCompleteSuccess()
{
@ -1602,9 +1610,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private class AsyncFlush extends ChannelWriteCB
{
volatile boolean _flushed;
private volatile boolean _flushed;
AsyncFlush(boolean last)
private AsyncFlush(boolean last)
{
super(last);
}
@ -1637,7 +1645,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private final int _len;
private boolean _completed;
AsyncWrite(byte[] b, int off, int len, boolean last)
private AsyncWrite(byte[] b, int off, int len, boolean last)
{
super(last);
_buffer = ByteBuffer.wrap(b, off, len);
@ -1646,7 +1654,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_slice = _len < getBufferSize() ? null : _buffer.duplicate();
}
AsyncWrite(ByteBuffer buffer, boolean last)
private AsyncWrite(ByteBuffer buffer, boolean last)
{
super(last);
_buffer = buffer;
@ -1734,7 +1742,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private boolean _eof;
private boolean _closed;
InputStreamWritingCB(InputStream in, Callback callback)
private InputStreamWritingCB(InputStream in, Callback callback)
{
super(callback, true);
_in = in;
@ -1810,7 +1818,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private boolean _eof;
private boolean _closed;
ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
private ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
{
super(callback, true);
_in = in;
@ -1882,5 +1890,11 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
onWriteComplete(true, x);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
}
}

View File

@ -19,6 +19,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
@ -297,6 +298,16 @@ public class ResourceService
// Send the data
releaseContent = sendData(request, response, included, content, reqRanges);
}
// Can be thrown from contentFactory.getContent() call when using invalid characters
catch (InvalidPathException e)
{
if (LOG.isDebugEnabled())
LOG.debug("InvalidPathException for pathInContext: {}", pathInContext, e);
if (included)
throw new FileNotFoundException("!" + pathInContext);
notFound(request, response);
return response.isCommitted();
}
catch (IllegalArgumentException e)
{
LOG.warn("Failed to serve resource: {}", pathInContext, e);
@ -717,6 +728,12 @@ public class ResourceService
content.release();
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
@Override
public String toString()
{

View File

@ -19,6 +19,7 @@ import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketOption;
import java.net.StandardSocketOptions;
import java.nio.channels.Channel;
import java.nio.channels.SelectableChannel;
@ -332,10 +333,10 @@ public class ServerConnector extends AbstractNetworkConnector
{
InetSocketAddress bindAddress = getHost() == null ? new InetSocketAddress(getPort()) : new InetSocketAddress(getHost(), getPort());
serverChannel = ServerSocketChannel.open();
setSocketOption(serverChannel, StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
setSocketOption(serverChannel, StandardSocketOptions.SO_REUSEPORT, isReusePort());
try
{
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, getReuseAddress());
serverChannel.setOption(StandardSocketOptions.SO_REUSEPORT, isReusePort());
serverChannel.bind(bindAddress, getAcceptQueueSize());
}
catch (Throwable e)
@ -348,6 +349,19 @@ public class ServerConnector extends AbstractNetworkConnector
return serverChannel;
}
private <T> void setSocketOption(ServerSocketChannel channel, SocketOption<T> option, T value)
{
try
{
channel.setOption(option, value);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not configure {} to {} on {}", option, value, channel, x);
}
}
@Override
public void close()
{

View File

@ -114,7 +114,9 @@ public class FileBufferedResponseHandler extends BufferedResponseHandler
}
catch (Throwable t)
{
LOG.warn("Could not delete file {}", _filePath, t);
if (LOG.isDebugEnabled())
LOG.debug("Could not immediately delete file (delaying to jvm exit) {}", _filePath, t);
_filePath.toFile().deleteOnExit();
}
_filePath = null;
}

View File

@ -149,7 +149,7 @@ public class GracefulStopTest
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertThat(response.getStatus(), is(200));
assertThat(response.getContent(), is("read 10/10\n"));
assertThat(response.getContent(), is("read [10/10]"));
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
return client;
@ -164,7 +164,7 @@ public class GracefulStopTest
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
assertThat(response.getStatus(), is(200));
assertThat(response.getContent(), is("read 10/10\n"));
assertThat(response.getContent(), is("read [10/10]"));
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
}
@ -224,7 +224,7 @@ public class GracefulStopTest
assertThat(response.get(HttpHeader.CONNECTION), is("close"));
else
assertThat(response.get(HttpHeader.CONNECTION), nullValue());
assertThat(response.getContent(), is("read 10/10\n"));
assertThat(response.getContent(), is("read [10/10]"));
}
void assert500Response(Socket client) throws Exception
@ -414,7 +414,7 @@ public class GracefulStopTest
}
}
response.getWriter().printf("read %d/%d%n", c, contentLength);
response.getWriter().printf("read [%d/%d]", c, contentLength);
}
catch (Throwable th)
{

View File

@ -45,6 +45,8 @@ import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
@ -214,6 +216,7 @@ public class ServerConnectorTest
}
@Test
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "SO_REUSEPORT not available on windows")
public void testReusePort() throws Exception
{
int port;

View File

@ -30,10 +30,8 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -42,11 +40,8 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.condition.OS.MAC;
@Disabled
@Tag("stress")
@DisabledOnOs(MAC) // TODO: needs investigation
public class StressTest
{
private static final Logger LOG = LoggerFactory.getLogger(StressTest.class);
@ -129,6 +124,7 @@ public class StressTest
}
@Test
@Tag("Slow")
public void testNonPersistent() throws Throwable
{
doThreads(20, 20, false);
@ -145,6 +141,7 @@ public class StressTest
}
@Test
@Tag("Slow")
public void testPersistent() throws Throwable
{
doThreads(40, 40, true);

View File

@ -45,10 +45,14 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -60,10 +64,13 @@ import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class FileBufferedResponseHandlerTest
{
private static final Logger LOG = LoggerFactory.getLogger(FileBufferedResponseHandlerTest.class);
public WorkDir _workDir;
private final CountDownLatch _disposeLatch = new CountDownLatch(1);
private Server _server;
private LocalConnector _localConnector;
@ -74,8 +81,7 @@ public class FileBufferedResponseHandlerTest
@BeforeEach
public void before() throws Exception
{
_testDir = MavenTestingUtils.getTargetTestingPath(FileBufferedResponseHandlerTest.class.getName());
FS.ensureDirExists(_testDir);
_testDir = _workDir.getEmptyPathDir();
_server = new Server();
HttpConfiguration config = new HttpConfiguration();
@ -109,8 +115,6 @@ public class FileBufferedResponseHandlerTest
_bufferedHandler.getPathIncludeExclude().exclude("*.exclude");
_bufferedHandler.getMimeIncludeExclude().exclude("text/excluded");
_server.setHandler(_bufferedHandler);
FS.ensureEmpty(_testDir);
}
@AfterEach
@ -175,8 +179,13 @@ public class FileBufferedResponseHandlerTest
assertThat(responseContent, containsString("Committed: false"));
assertThat(responseContent, containsString("NumFiles: 1"));
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
// Unable to verify file deletion on windows, as immediate delete not possible.
// only after a GC has occurred.
if (!OS.WINDOWS.isCurrentOs())
{
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
}
}
@Test
@ -269,8 +278,13 @@ public class FileBufferedResponseHandlerTest
assertThat(responseContent, containsString("Committed: false"));
assertThat(responseContent, containsString("NumFiles: 1"));
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
// Unable to verify file deletion on windows, as immediate delete not possible.
// only after a GC has occurred.
if (!OS.WINDOWS.isCurrentOs())
{
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
}
}
@Test
@ -301,8 +315,13 @@ public class FileBufferedResponseHandlerTest
assertThat(responseContent, not(containsString("writtenAfterClose")));
assertThat(responseContent, containsString("NumFiles: 1"));
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
// Unable to verify file deletion on windows, as immediate delete not possible.
// only after a GC has occurred.
if (!OS.WINDOWS.isCurrentOs())
{
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
}
}
@Test
@ -363,8 +382,13 @@ public class FileBufferedResponseHandlerTest
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(responseContent, containsString("NumFiles: 0"));
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
// Unable to verify file deletion on windows, as immediate delete not possible.
// only after a GC has occurred.
if (!OS.WINDOWS.isCurrentOs())
{
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
}
}
@Test
@ -400,12 +424,18 @@ public class FileBufferedResponseHandlerTest
// Resetting the response buffer will delete the file.
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(responseContent, not(containsString("THIS WILL BE RESET")));
assertThat(responseContent, containsString("NumFilesBeforeReset: 1"));
assertThat(responseContent, containsString("NumFilesAfterReset: 0"));
assertThat(responseContent, containsString("NumFilesAfterWrite: 1"));
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
// Unable to verify file deletion on windows, as immediate delete not possible.
// only after a GC has occurred.
if (!OS.WINDOWS.isCurrentOs())
{
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
}
}
@Test
@ -479,8 +509,13 @@ public class FileBufferedResponseHandlerTest
assertThat(response.get("FileSize"), is(Long.toString(fileSize)));
assertThat(received.get(), is(fileSize));
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
// Unable to verify file deletion on windows, as immediate delete not possible.
// only after a GC has occurred.
if (!OS.WINDOWS.isCurrentOs())
{
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
}
}
@Test
@ -553,9 +588,14 @@ public class FileBufferedResponseHandlerTest
Throwable error = errorFuture.get(5, TimeUnit.SECONDS);
assertThat(error.getMessage(), containsString("intentionally throwing from interceptor"));
// All files were deleted.
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
// Unable to verify file deletion on windows, as immediate delete not possible.
// only after a GC has occurred.
if (!OS.WINDOWS.isCurrentOs())
{
// All files were deleted.
assertTrue(_disposeLatch.await(5, TimeUnit.SECONDS));
assertThat(getNumFiles(), is(0));
}
}
@Test

View File

@ -17,7 +17,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
@ -26,7 +25,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import javax.servlet.ServletException;
@ -49,7 +47,6 @@ import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,7 +56,6 @@ import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
/**
* HttpServer Tester for SSL based ServerConnector
@ -124,43 +120,6 @@ public class ServerConnectorSslServerTest extends HttpServerTestBase
return socket;
}
@Override
@DisabledOnOs(WINDOWS) // Don't run on Windows (buggy JVM)
public void testFullMethod() throws Exception
{
try
{
super.testFullMethod();
}
catch (SocketException e)
{
// TODO This needs to be investigated #2244
LOG.warn("Close overtook 400 response", e);
}
catch (SSLException e)
{
// TODO This needs to be investigated #2244
if (e.getCause() instanceof SocketException)
LOG.warn("Close overtook 400 response", e);
else
throw e;
}
}
@Override
@DisabledOnOs(WINDOWS) // Don't run on Windows (buggy JVM)
public void testFullURI() throws Exception
{
try
{
super.testFullURI();
}
catch (SocketException e)
{
LOG.warn("Close overtook 400 response", e);
}
}
@Override
public void testFullHeader() throws Exception
{

View File

@ -235,8 +235,8 @@ public class SniSslConnectionFactoryTest
assertThat(response, Matchers.containsString("Invalid SNI"));
}
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "See Issue #6609 - TLSv1.3 behavior differences between Linux and Windows")
@Test
@DisabledOnOs(OS.WINDOWS)
public void testWrongSNIRejectedConnection() throws Exception
{
start(ssl ->
@ -275,8 +275,8 @@ public class SniSslConnectionFactoryTest
assertThat(response.getStatus(), is(400));
}
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "See Issue #6609 - TLSv1.3 behavior differences between Linux and Windows")
@Test
@DisabledOnOs(OS.WINDOWS)
public void testWrongSNIRejectedFunction() throws Exception
{
start((ssl, customizer) ->
@ -302,8 +302,8 @@ public class SniSslConnectionFactoryTest
assertThat(response.getStatus(), is(400));
}
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "See Issue #6609 - TLSv1.3 behavior differences between Linux and Windows")
@Test
@DisabledOnOs(OS.WINDOWS)
public void testWrongSNIRejectedConnectionWithNonSNIKeystore() throws Exception
{
start(ssl ->

View File

@ -37,6 +37,7 @@ import org.eclipse.jetty.util.URIUtil;
* will be handled by any servlets mapped to that URL.
*
* Requests to "/some/directory" will be redirected to "/some/directory/".
* @deprecated no replacement is offered, use standard Servlet web.xml welcome features
*/
@Deprecated
public class WelcomeFilter implements Filter

View File

@ -45,6 +45,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -289,7 +290,8 @@ public abstract class AbstractDoSFilterTest
String responses = doRequests(request1 + request2 + request1 + request2 + request1, 2, 1100, 1100, last);
assertEquals(11, count(responses, "HTTP/1.1 200 OK"));
assertEquals(0, count(responses, "DoSFilter: delayed"));
// This test is system speed dependent, so allow some (20%-ish) requests to be delayed, but not more.
assertThat("delayed count", count(responses, "DoSFilter: delayed"), lessThan(2));
// alternate between sessions
responses = doRequests(request1 + request2 + request1 + request2 + request1, 2, 250, 250, last);

View File

@ -25,6 +25,7 @@ import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.webapp.WebAppContext;
@ -80,6 +81,8 @@ public class WelcomeFilterTest
}
WebAppContext context = new WebAppContext(server, directoryPath.toString(), "/");
// Turn off memory-mapped behavior in DefaultServlet for Windows testing reasons.
context.setInitParameter(DefaultServlet.CONTEXT_INIT + "useFileMappedBuffer", "false");
server.setHandler(context);
String concatPath = "/*";

View File

@ -24,10 +24,8 @@ public class JettyLoggingServiceProvider implements SLF4JServiceProvider
{
/**
* Declare the version of the SLF4J API this implementation is compiled against.
* The value of this field is modified with each major release.
*/
// to avoid constant folding by the compiler, this field must *not* be final
public static String REQUESTED_API_VERSION = "1.8.99"; // !final
private static final String REQUESTED_API_VERSION = "2.0";
private JettyLoggerFactory loggerFactory;
private BasicMarkerFactory markerFactory;
@ -66,7 +64,7 @@ public class JettyLoggingServiceProvider implements SLF4JServiceProvider
}
@Override
public String getRequesteApiVersion()
public String getRequestedApiVersion()
{
return REQUESTED_API_VERSION;
}

View File

@ -22,12 +22,12 @@ EXISTS|maindir/
EXISTS|start.ini
# Output Assertions [regex!] (order is irrelevant)
OUTPUT|INFO : mkdir ..jetty.base./start.d
OUTPUT|INFO : extra initialized in \$\{jetty.base\}/start.d/extra.ini
OUTPUT|INFO : mkdir ..jetty.base.[/\\]start.d
OUTPUT|INFO : extra initialized in \$\{jetty.base\}[/\\]start.d[/\\]extra.ini
OUTPUT|INFO : main transitively enabled, ini template available with --add-module=main
OUTPUT|INFO : optional initialized in \$\{jetty.base\}/start.d/optional.ini
OUTPUT|INFO : optional initialized in \$\{jetty.base\}[/\\]start.d[/\\]optional.ini
OUTPUT|INFO : base transitively enabled
OUTPUT|INFO : mkdir ..jetty.base./maindir
OUTPUT|INFO : mkdir ..jetty.base.[/\\]maindir
OUTPUT|INFO : Base directory was modified

View File

@ -22,12 +22,12 @@ EXISTS|maindir/
EXISTS|start.ini
# Output Assertions [regex!] (order is irrelevant)
OUTPUT|INFO : create ..jetty.base./start.ini
OUTPUT|INFO : extra initialized in ..jetty.base./start.ini
OUTPUT|INFO : create ..jetty.base.[/\\]start.ini
OUTPUT|INFO : extra initialized in ..jetty.base.[/\\]start.ini
OUTPUT|INFO : main transitively enabled, ini template available with --add-module=main
OUTPUT|INFO : optional initialized in ..jetty.base./start.ini
OUTPUT|INFO : optional initialized in ..jetty.base.[/\\]start.ini
OUTPUT|INFO : base transitively enabled
OUTPUT|INFO : mkdir ..jetty.base./maindir
OUTPUT|INFO : mkdir ..jetty.base.[/\\]maindir
OUTPUT|INFO : Base directory was modified

View File

@ -14,4 +14,4 @@ PROP|main.prop=value0
EXISTS|maindir/
EXISTS|start.d/main.ini
OUTPUT|INFO : main already enabled by \[\$\{jetty.base}[\\/]start.d/main.ini\]
OUTPUT|INFO : main already enabled by \[\$\{jetty.base}[/\\]start.d[/\\]main.ini\]

View File

@ -17,7 +17,7 @@ PROP|optional.prop=value0
EXISTS|maindir/
EXISTS|start.ini
OUTPUT|INFO : copy ..jetty.base./start.d/main.ini into ..jetty.base./start.ini
OUTPUT|INFO : optional initialized in ..jetty.base./start.ini
OUTPUT|INFO : mkdir ..jetty.base./maindir
OUTPUT|INFO : copy ..jetty.base.[/\\]start.d[/\\]main.ini into ..jetty.base.[/\\]start.ini
OUTPUT|INFO : optional initialized in ..jetty.base.[/\\]start.ini
OUTPUT|INFO : mkdir ..jetty.base.[/\\]maindir
OUTPUT|INFO : Base directory was modified

View File

@ -108,13 +108,26 @@ public interface Callback extends Invocable
}
/**
* Creates a callback from the passed success and failure
* Creates a callback from the given success and failure lambdas.
*
* @param success Called when the callback succeeds
* @param failure Called when the callback fails
* @return a new Callback
*/
static Callback from(Runnable success, Consumer<Throwable> failure)
{
return from(InvocationType.BLOCKING, success, failure);
}
/**
* Creates a callback with the given InvocationType from the given success and failure lambdas.
*
* @param invocationType the Callback invocation type
* @param success Called when the callback succeeds
* @param failure Called when the callback fails
* @return a new Callback
*/
static Callback from(InvocationType invocationType, Runnable success, Consumer<Throwable> failure)
{
return new Callback()
{
@ -129,6 +142,12 @@ public interface Callback extends Invocable
{
failure.accept(x);
}
@Override
public InvocationType getInvocationType()
{
return invocationType;
}
};
}
@ -339,10 +358,6 @@ public interface Callback extends Invocable
}
}
interface InvocableCallback extends Invocable, Callback
{
}
static Callback combine(Callback cb1, Callback cb2)
{
if (cb1 == null || cb1 == cb2)
@ -350,7 +365,7 @@ public interface Callback extends Invocable
if (cb2 == null)
return cb1;
return new InvocableCallback()
return new Callback()
{
@Override
public void succeeded()

View File

@ -2178,9 +2178,9 @@ public abstract class SslContextFactory extends AbstractLifeCycle implements Dum
String host = sslEngine.getPeerHost();
if (host != null)
{
// TODO Must handle : somehow as java17 SNIHostName never handles: See #6624
// Must use the byte[] constructor, because the character ':' is forbidden when
// using the String constructor (but typically present in IPv6 addresses).
// Since Java 17, only letter|digit|hyphen characters are allowed, even by the byte[] constructor.
return List.of(new SNIHostName(host.getBytes(StandardCharsets.US_ASCII)));
}
}

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.util.thread;
import java.io.IOException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@ -21,6 +22,7 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.eclipse.jetty.util.AtomicBiInteger;
import org.eclipse.jetty.util.ProcessorUtils;
@ -38,14 +40,14 @@ import static org.eclipse.jetty.util.AtomicBiInteger.getLo;
/**
* An Executor using pre-allocated/reserved Threads from a wrapped Executor.
* <p>Calls to {@link #execute(Runnable)} on a {@link ReservedThreadExecutor} will either succeed
* with a Thread immediately being assigned the Runnable task, or fail if no Thread is
* available.
* <p>Threads are reserved lazily, with a new reserved threads being allocated from the
* {@link Executor} passed to the constructor. Whenever 1 or more reserved threads have been
* <p>A TryExecutor using pre-allocated/reserved threads from an external Executor.</p>
* <p>Calls to {@link #tryExecute(Runnable)} on ReservedThreadExecutor will either
* succeed with a reserved thread immediately being assigned the task, or fail if
* no reserved thread is available.</p>
* <p>Threads are reserved lazily, with new reserved threads being allocated from the external
* {@link Executor} passed to the constructor. Whenever 1 or more reserved threads have been
* idle for more than {@link #getIdleTimeoutMs()} then one reserved thread will return to
* the executor.
* the external Executor.</p>
*/
@ManagedObject("A pool for reserved threads")
public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExecutor, Dumpable
@ -62,7 +64,7 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
@Override
public String toString()
{
return "STOP!";
return "STOP";
}
};
@ -72,7 +74,6 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
private final SynchronousQueue<Runnable> _queue = new SynchronousQueue<>(false);
private final AtomicBiInteger _count = new AtomicBiInteger(); // hi=pending; lo=size;
private final AtomicLong _lastEmptyTime = new AtomicLong(System.nanoTime());
private ThreadPoolBudget.Lease _lease;
private long _idleTimeNanos = DEFAULT_IDLE_TIMEOUT;
@ -175,20 +176,25 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
super.doStop();
// Offer STOP task to all waiting reserved threads.
for (int i = _count.getAndSetLo(-1); i-- > 0;)
// Mark this instance as stopped.
int size = _count.getAndSetLo(-1);
// Offer the STOP task to all waiting reserved threads.
for (int i = 0; i < size; ++i)
{
// yield to wait for any reserved threads that have incremented the size but not yet polled
// Yield to wait for any reserved threads that
// have incremented the size but not yet polled.
Thread.yield();
_queue.offer(STOP);
}
// Interrupt any reserved thread missed the offer so it doesn't wait too long.
for (ReservedThread reserved : _threads)
{
Thread thread = reserved._thread;
if (thread != null)
thread.interrupt();
}
// Interrupt any reserved thread missed the offer,
// so they do not wait for the whole idle timeout.
_threads.stream()
.filter(ReservedThread::isReserved)
.map(t -> t._thread)
.filter(Objects::nonNull)
.forEach(Thread::interrupt);
_threads.clear();
_count.getAndSetHi(0);
}
@ -264,13 +270,16 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this,
new DumpableCollection("reserved", _threads));
new DumpableCollection("threads",
_threads.stream()
.filter(ReservedThread::isReserved)
.collect(Collectors.toList())));
}
@Override
public String toString()
{
return String.format("%s@%x{s=%d/%d,p=%d}",
return String.format("%s@%x{reserved=%d/%d,pending=%d}",
getClass().getSimpleName(),
hashCode(),
_count.getLo(),
@ -293,6 +302,11 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
private volatile State _state = State.PENDING;
private volatile Thread _thread;
private boolean isReserved()
{
return _state == State.RESERVED;
}
private Runnable reservedWait()
{
if (LOG.isDebugEnabled())
@ -321,7 +335,6 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
}
_state = size >= 0 ? State.IDLE : State.STOPPED;
return STOP;
}
catch (InterruptedException e)
{

View File

@ -362,7 +362,7 @@ public class PathWatcherTest
capture.finishedLatch.await(LONG_TIME, TimeUnit.MILLISECONDS);
long end = System.nanoTime();
capture.assertEvents(expected);
assertThat(end - start, greaterThan(TimeUnit.MILLISECONDS.toNanos(2 * QUIET_TIME)));
assertThat(end - start, greaterThan(TimeUnit.MILLISECONDS.toNanos(2L * QUIET_TIME)));
Thread.sleep(WAIT_TIME);
capture.assertEvents(expected);

View File

@ -17,6 +17,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
@ -28,40 +29,38 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Scanner.Notification;
import org.eclipse.jetty.util.component.LifeCycle;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
@ExtendWith(WorkDirExtension.class)
public class ScannerTest
{
static File _directory;
static Scanner _scanner;
static BlockingQueue<Event> _queue = new LinkedBlockingQueue<>();
static BlockingQueue<Set<String>> _bulk = new LinkedBlockingQueue<>();
public WorkDir workDir;
private Path _directory;
private Scanner _scanner;
private BlockingQueue<Event> _queue = new LinkedBlockingQueue<>();
private BlockingQueue<Set<String>> _bulk = new LinkedBlockingQueue<>();
@BeforeAll
public static void setUpBeforeClass() throws Exception
@BeforeEach
public void setupScanner() throws Exception
{
File testDir = MavenTestingUtils.getTargetTestingDir(ScannerTest.class.getSimpleName());
FS.ensureEmpty(testDir);
// Use full path, pointing to a real directory (for FileSystems that are case-insensitive, like Windows and OSX to use)
// This is only needed for the various comparisons below to make sense.
_directory = testDir.toPath().toRealPath().toFile();
_directory = workDir.getEmptyPathDir();
_scanner = new Scanner();
_scanner.addDirectory(_directory.toPath());
_scanner.addDirectory(_directory);
_scanner.setScanInterval(0);
_scanner.setReportDirs(false);
_scanner.setReportExistingFilesOnStartup(false);
@ -94,11 +93,10 @@ public class ScannerTest
assertTrue(_bulk.isEmpty());
}
@AfterAll
public static void tearDownAfterClass() throws Exception
@AfterEach
public void cleanup() throws Exception
{
_scanner.stop();
IO.delete(_directory);
LifeCycle.stop(_scanner);
}
static class Event
@ -139,7 +137,7 @@ public class ScannerTest
@Test
public void testDepth() throws Exception
{
File root = new File(_directory, "root");
File root = new File(_directory.toFile(), "root");
FS.ensureDirExists(root);
FS.touch(new File(root, "foo.foo"));
FS.touch(new File(root, "foo2.foo"));
@ -215,7 +213,7 @@ public class ScannerTest
public void testPatterns() throws Exception
{
//test include and exclude patterns
File root = new File(_directory, "proot");
File root = new File(_directory.toFile(), "proot");
FS.ensureDirExists(root);
File ttt = new File(root, "ttt.txt");
@ -288,7 +286,7 @@ public class ScannerTest
}
@Test
@DisabledOnOs(WINDOWS) // TODO: needs review
@Tag("Slow")
public void testAddedChangeRemove() throws Exception
{
touch("a0");
@ -299,7 +297,7 @@ public class ScannerTest
Event event = _queue.poll(5, TimeUnit.SECONDS);
assertNotNull(event, "Event should not be null");
assertEquals(_directory + "/a0", event._filename);
assertEquals(_directory.resolve("a0").toString(), event._filename);
assertEquals(Notification.ADDED, event._notification);
// add 3 more files
@ -323,8 +321,8 @@ public class ScannerTest
List<Event> actualEvents = new ArrayList<>();
_queue.drainTo(actualEvents);
assertEquals(2, actualEvents.size());
Event a1 = new Event(_directory + "/a1", Notification.ADDED);
Event a3 = new Event(_directory + "/a3", Notification.REMOVED);
Event a1 = new Event(_directory.resolve("a1").toString(), Notification.ADDED);
Event a3 = new Event(_directory.resolve("a3").toString(), Notification.REMOVED);
assertThat(actualEvents, Matchers.containsInAnyOrder(a1, a3));
assertTrue(_queue.isEmpty());
@ -332,7 +330,7 @@ public class ScannerTest
_scanner.scan();
event = _queue.poll();
assertNotNull(event);
assertEquals(_directory + "/a2", event._filename);
assertEquals(_directory.resolve("a2").toString(), event._filename);
assertEquals(Notification.ADDED, event._notification);
assertTrue(_queue.isEmpty());
@ -353,7 +351,7 @@ public class ScannerTest
_scanner.scan();
event = _queue.poll();
assertNotNull(event);
assertEquals(_directory + "/a1", event._filename);
assertEquals(_directory.resolve("a1").toString(), event._filename);
assertEquals(Notification.CHANGED, event._notification);
assertTrue(_queue.isEmpty());
@ -361,7 +359,7 @@ public class ScannerTest
_scanner.scan();
event = _queue.poll();
assertNotNull(event);
assertEquals(_directory + "/a2", event._filename);
assertEquals(_directory.resolve("a2").toString(), event._filename);
assertEquals(Notification.CHANGED, event._notification);
assertTrue(_queue.isEmpty());
@ -371,8 +369,8 @@ public class ScannerTest
//Immediate notification of deletes.
_scanner.scan();
a1 = new Event(_directory + "/a1", Notification.REMOVED);
Event a2 = new Event(_directory + "/a2", Notification.REMOVED);
a1 = new Event(_directory.resolve("a1").toString(), Notification.REMOVED);
Event a2 = new Event(_directory.resolve("a2").toString(), Notification.REMOVED);
actualEvents = new ArrayList<>();
_queue.drainTo(actualEvents);
assertEquals(2, actualEvents.size());
@ -392,13 +390,12 @@ public class ScannerTest
_scanner.scan();
event = _queue.poll();
assertNotNull(event);
assertEquals(_directory + "/a2", event._filename);
assertEquals(_directory.resolve("a2").toString(), event._filename);
assertEquals(Notification.ADDED, event._notification);
assertTrue(_queue.isEmpty());
}
@Test
@DisabledOnOs(WINDOWS) // TODO: needs review
public void testSizeChange() throws Exception
{
touch("tsc0");
@ -408,12 +405,12 @@ public class ScannerTest
// takes 2 scans to notice tsc0 and check that it is stable.
Event event = _queue.poll();
assertNotNull(event);
assertEquals(_directory + "/tsc0", event._filename);
assertEquals(_directory.resolve("tsc0").toString(), event._filename);
assertEquals(Notification.ADDED, event._notification);
// Create a new file by writing to it.
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
File file = new File(_directory, "st");
File file = new File(_directory.toFile(), "st");
try (OutputStream out = new FileOutputStream(file, true))
{
out.write('x');
@ -439,7 +436,7 @@ public class ScannerTest
_scanner.scan();
event = _queue.poll();
assertNotNull(event);
assertEquals(_directory + "/st", event._filename);
assertEquals(_directory.resolve("st").toString(), event._filename);
assertEquals(Notification.ADDED, event._notification);
// Modify size only
@ -456,21 +453,20 @@ public class ScannerTest
_scanner.scan();
event = _queue.poll();
assertNotNull(event);
assertEquals(_directory + "/st", event._filename);
assertEquals(_directory.resolve("st").toString(), event._filename);
assertEquals(Notification.CHANGED, event._notification);
}
}
private void delete(String string)
private void delete(String string) throws IOException
{
File file = new File(_directory, string);
if (file.exists())
IO.delete(file);
Path file = _directory.resolve(string);
Files.deleteIfExists(file);
}
private void touch(String string) throws IOException
{
File file = new File(_directory, string);
File file = new File(_directory.toFile(), string);
if (file.exists())
file.setLastModified(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()));
else

View File

@ -46,7 +46,6 @@ import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
@ -641,7 +640,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@MethodSource("fsResourceProvider")
@DisabledOnOs(WINDOWS)
public void testSymlink(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -649,18 +647,20 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo");
Path bar = dir.resolve("bar");
boolean symlinkSupported;
try
{
Files.createFile(foo);
Files.createSymbolicLink(bar, foo);
symlinkSupported = true;
}
catch (UnsupportedOperationException | FileSystemException e)
{
// if unable to create symlink, no point testing the rest
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported");
symlinkSupported = false;
}
assumeTrue(symlinkSupported, "Symlink not supported");
try (Resource base = newResource(resourceClass, dir.toFile()))
{
Resource resFoo = base.addPath("foo");
@ -683,7 +683,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@ValueSource(classes = PathResource.class) // FileResource does not support this
@DisabledOnOs(WINDOWS)
public void testNonExistantSymlink(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -692,17 +691,19 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo");
Path bar = dir.resolve("bar");
boolean symlinkSupported;
try
{
Files.createSymbolicLink(bar, foo);
symlinkSupported = true;
}
catch (UnsupportedOperationException | FileSystemException e)
{
// if unable to create symlink, no point testing the rest
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported");
symlinkSupported = false;
}
assumeTrue(symlinkSupported, "Symlink not supported");
try (Resource base = newResource(resourceClass, dir.toFile()))
{
Resource resFoo = base.addPath("foo");
@ -835,8 +836,7 @@ public class FileSystemResourceTest
}
catch (InvalidPathException e)
{
// NTFS filesystem streams are unsupported on some platforms.
assumeTrue(true, "Not supported");
assumeTrue(false, "NTFS simple streams not supported");
}
}
}
@ -883,8 +883,7 @@ public class FileSystemResourceTest
}
catch (InvalidPathException e)
{
// NTFS filesystem streams are unsupported on some platforms.
assumeTrue(true, "Not supported");
assumeTrue(false, "NTFS $DATA streams not supported");
}
}
}
@ -929,15 +928,13 @@ public class FileSystemResourceTest
}
catch (InvalidPathException e)
{
// NTFS filesystem streams are unsupported on some platforms.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "NTFS $DATA streams not supported");
}
}
}
@ParameterizedTest
@MethodSource("fsResourceProvider")
@DisabledOnOs(WINDOWS)
public void testSemicolon(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -948,11 +945,9 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo;");
Files.createFile(foo);
}
catch (Exception e)
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create file with semicolon");
}
try (Resource base = newResource(resourceClass, dir.toFile()))
@ -964,7 +959,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@MethodSource("fsResourceProvider")
@DisabledOnOs(WINDOWS)
public void testSingleQuote(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -976,11 +970,9 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo' bar");
Files.createFile(foo);
}
catch (Exception e)
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create file with single quote");
}
try (Resource base = newResource(resourceClass, dir.toFile()))
@ -992,7 +984,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@MethodSource("fsResourceProvider")
@DisabledOnOs(WINDOWS)
public void testSingleBackTick(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -1004,11 +995,9 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo` bar");
Files.createFile(foo);
}
catch (Exception e)
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create file with single back tick");
}
try (Resource base = newResource(resourceClass, dir.toFile()))
@ -1020,7 +1009,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@MethodSource("fsResourceProvider")
@DisabledOnOs(WINDOWS)
public void testBrackets(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -1032,11 +1020,9 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo[1]");
Files.createFile(foo);
}
catch (Exception e)
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create file with square brackets");
}
try (Resource base = newResource(resourceClass, dir.toFile()))
@ -1048,7 +1034,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@ValueSource(classes = PathResource.class) // FileResource does not support this
@DisabledOnOs(WINDOWS)
public void testBraces(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -1060,11 +1045,9 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo.{bar}.txt");
Files.createFile(foo);
}
catch (Exception e)
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create file with squiggle braces");
}
try (Resource base = newResource(resourceClass, dir.toFile()))
@ -1076,7 +1059,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@ValueSource(classes = PathResource.class) // FileResource does not support this
@DisabledOnOs(WINDOWS)
public void testCaret(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -1088,11 +1070,9 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo^3.txt");
Files.createFile(foo);
}
catch (Exception e)
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create file with caret");
}
try (Resource base = newResource(resourceClass, dir.toFile()))
@ -1104,7 +1084,6 @@ public class FileSystemResourceTest
@ParameterizedTest
@ValueSource(classes = PathResource.class) // FileResource does not support this
@DisabledOnOs(WINDOWS)
public void testPipe(Class<PathResource> resourceClass) throws Exception
{
Path dir = workDir.getEmptyPathDir();
@ -1116,11 +1095,9 @@ public class FileSystemResourceTest
Path foo = dir.resolve("foo|bar.txt");
Files.createFile(foo);
}
catch (Exception e)
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create file with pipe symbol");
}
try (Resource base = newResource(resourceClass, dir.toFile()))
@ -1477,10 +1454,7 @@ public class FileSystemResourceTest
}
catch (InvalidPathException e)
{
// if unable to create file, no point testing the rest.
// this is the path that occurs if you have a system that doesn't support UTF-8
// directory names (or you simply don't have a Locale set properly)
assumeTrue(true, "Not supported on this OS");
assumeTrue(false, "Unable to create directory with utf-8 character");
return;
}

View File

@ -19,6 +19,7 @@ import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.stream.Stream;
@ -29,7 +30,6 @@ import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
@ -43,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class ResourceTest
{
@ -287,15 +288,23 @@ public class ResourceTest
}
@Test
@DisabledOnOs(OS.WINDOWS) // this uses forbidden characters on some Windows Environments
public void testGlobPath() throws IOException
{
Path testDir = MavenTestingUtils.getTargetTestingPath("testGlobPath");
FS.ensureEmpty(testDir);
String globReference = testDir.toAbsolutePath().toString() + File.separator + '*';
Resource globResource = Resource.newResource(globReference);
assertNotNull(globResource, "Should have produced a Resource");
try
{
String globReference = testDir.toAbsolutePath() + File.separator + '*';
Resource globResource = Resource.newResource(globReference);
assertNotNull(globResource, "Should have produced a Resource");
}
catch (InvalidPathException e)
{
// if unable to reference the glob file, no point testing the rest.
// this is the path that Microsoft Windows takes.
assumeTrue(false, "Glob not supported on this OS");
}
}
@Test

View File

@ -831,7 +831,7 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
dump = pool.dump();
assertThat(count(dump, " - STARTED"), is(2));
assertThat(dump, containsString(",3<=3<=4,i=2,r=2,q=0"));
assertThat(dump, containsString("s=0/2"));
assertThat(dump, containsString("reserved=0/2"));
assertThat(dump, containsString("[ReservedThreadExecutor@"));
assertThat(count(dump, " IDLE"), is(2));
assertThat(count(dump, " WAITING"), is(1));
@ -846,7 +846,7 @@ public class QueuedThreadPoolTest extends AbstractThreadPoolTest
dump = pool.dump();
assertThat(count(dump, " - STARTED"), is(2));
assertThat(dump, containsString(",3<=3<=4,i=1,r=2,q=0"));
assertThat(dump, containsString("s=1/2"));
assertThat(dump, containsString("reserved=1/2"));
assertThat(dump, containsString("[ReservedThreadExecutor@"));
assertThat(count(dump, " IDLE"), is(1));
assertThat(count(dump, " WAITING"), is(1));

View File

@ -359,6 +359,5 @@ public class ReservedThreadExecutorTest
assertThat(usedReserved.get(), greaterThan(0));
assertThat(usedReserved.get() + usedPool.get(), is(LOOPS));
// System.err.printf("reserved=%d pool=%d total=%d%n", usedReserved.get(), usedPool.get(), LOOPS);
}
}

View File

@ -27,6 +27,7 @@ module org.eclipse.jetty.webapp
provides Configuration with
org.eclipse.jetty.webapp.FragmentConfiguration,
org.eclipse.jetty.webapp.JaasConfiguration,
org.eclipse.jetty.webapp.JaspiConfiguration,
org.eclipse.jetty.webapp.JettyWebXmlConfiguration,
org.eclipse.jetty.webapp.JmxConfiguration,
org.eclipse.jetty.webapp.JndiConfiguration,

View File

@ -0,0 +1,50 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.webapp;
import org.eclipse.jetty.util.Loader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>JASPI Configuration</p>
* <p>This configuration configures the WebAppContext server/system classes to
* not be able to see the {@code javax.security.auth.message} package.</p>
*/
public class JaspiConfiguration extends AbstractConfiguration
{
private static final Logger LOG = LoggerFactory.getLogger(JaspiConfiguration.class);
public JaspiConfiguration()
{
addDependencies(WebXmlConfiguration.class, MetaInfConfiguration.class, WebInfConfiguration.class, FragmentConfiguration.class);
addDependents(WebAppConfiguration.class);
hide("javax.security.auth.message.");
}
@Override
public boolean isAvailable()
{
try
{
return Loader.loadClass("org.eclipse.jetty.security.jaspi.JaspiAuthenticator") != null;
}
catch (Throwable e)
{
LOG.trace("IGNORED", e);
return false;
}
}
}

View File

@ -1,6 +1,7 @@
org.eclipse.jetty.webapp.FragmentConfiguration
org.eclipse.jetty.webapp.JettyWebXmlConfiguration
org.eclipse.jetty.webapp.JaasConfiguration
org.eclipse.jetty.webapp.JaspiConfiguration
org.eclipse.jetty.webapp.JmxConfiguration
org.eclipse.jetty.webapp.JndiConfiguration
org.eclipse.jetty.webapp.JspConfiguration

View File

@ -123,9 +123,22 @@ public class HugeResourceTest
@AfterAll
public static void cleanupTestFiles()
{
FS.ensureDeleted(staticBase);
FS.ensureDeleted(outputDir);
FS.ensureDeleted(multipartTempDir);
quietlyDelete(staticBase);
quietlyDelete(outputDir);
quietlyDelete(multipartTempDir);
}
private static void quietlyDelete(Path path)
{
try
{
if (path != null)
FS.ensureDeleted(path);
}
catch (Throwable ignore)
{
// ignore
}
}
private static void makeStaticFile(Path staticFile, long size) throws IOException

View File

@ -25,8 +25,10 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ -289,9 +291,12 @@ public class TempDirTest
/**
* ServletContext.TEMPDIR has invalid <code>String</code> directory value (wrong permission to write into it)
* IllegalStateException
*
* Note that if run in the CI environment, the test will fail, because it runs as root,
* so we _will_ have permission to write to this directory.
*/
@Disabled("Jenkins will run as root so we do have permission to write to this directory.")
@DisabledIfSystemProperty(named = "env", matches = "ci")
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Test/Temp directory is always writable")
@Test
public void attributeWithInvalidPermissions()
{

View File

@ -23,10 +23,11 @@ import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -35,8 +36,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ExtendWith(WorkDirExtension.class)
public class WebAppDefaultServletTest
{
public WorkDir workDir;
private Server server;
private LocalConnector connector;
@ -48,9 +51,7 @@ public class WebAppDefaultServletTest
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986);
server.addConnector(connector);
Path directoryPath = MavenTestingUtils.getTargetTestingDir().toPath();
IO.delete(directoryPath.toFile());
Files.createDirectories(directoryPath);
Path directoryPath = workDir.getEmptyPathDir();
Path welcomeResource = directoryPath.resolve("index.html");
try (OutputStream output = Files.newOutputStream(welcomeResource))
{

View File

@ -29,6 +29,7 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@ -90,9 +91,17 @@ public class ServerUpgradeRequest
{
if (cookies == null)
{
cookies = Arrays.stream(request.getCookies())
.map(c -> new HttpCookie(c.getName(), c.getValue()))
.collect(Collectors.toList());
Cookie[] reqCookies = request.getCookies();
if (reqCookies != null)
{
cookies = Arrays.stream(reqCookies)
.map(c -> new HttpCookie(c.getName(), c.getValue()))
.collect(Collectors.toList());
}
else
{
cookies = Collections.emptyList();
}
}
return cookies;
@ -135,10 +144,13 @@ public class ServerUpgradeRequest
{
Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements())
if (headerNames != null)
{
String name = headerNames.nextElement();
headers.put(name, Collections.list(request.getHeaders(name)));
while (headerNames.hasMoreElements())
{
String name = headerNames.nextElement();
headers.put(name, Collections.list(request.getHeaders(name)));
}
}
return headers;
}

View File

@ -63,6 +63,8 @@ import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerI
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
@ -234,6 +236,7 @@ public class WebSocketOverHTTP2Test
}
@Test
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Issue #6660 - Windows does not throw ConnectException")
public void testWebSocketConnectPortDoesNotExist() throws Exception
{
startServer();

14
pom.xml
View File

@ -20,9 +20,9 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<build-support.version>1.5</build-support.version>
<checkstyle.version>8.37</checkstyle.version>
<slf4j.version>2.0.0-alpha1</slf4j.version>
<slf4j.version>2.0.0-alpha4</slf4j.version>
<log4j2.version>2.14.0</log4j2.version>
<logback.version>1.3.0-alpha5</logback.version>
<logback.version>1.3.0-alpha9</logback.version>
<disruptor.version>3.4.2</disruptor.version>
<jetty-test-policy.version>1.2</jetty-test-policy.version>
<servlet.api.version>4.0.6</servlet.api.version>
@ -79,7 +79,8 @@
<settingsPath>src/it/settings.xml</settingsPath>
<invoker.mergeUserSettings>false</invoker.mergeUserSettings>
<surefire.rerunFailingTestsCount>0</surefire.rerunFailingTestsCount>
<testcontainers.version>1.15.1</testcontainers.version>
<testcontainers.version>1.16.0</testcontainers.version>
<jna.version>5.8.0</jna.version>
<mariadb.version>2.7.0</mariadb.version>
</properties>
@ -981,7 +982,7 @@
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>h2spec-maven-plugin</artifactId>
<version>1.0.5</version>
<version>1.0.6</version>
</plugin>
</plugins>
</pluginManagement>
@ -1151,7 +1152,7 @@
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.6.0</version>
<version>${jna.version}</version>
</dependency>
<!-- Old Deps -->
<dependency>
@ -1458,6 +1459,9 @@
<version>${maven.surefire.version}</version>
<configuration>
<excludedGroups>external, large-disk-resource</excludedGroups>
<systemPropertyVariables>
<env>ci</env>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>

View File

@ -191,6 +191,11 @@
<artifactId>gcloud</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>

Some files were not shown because too many files have changed in this diff Show More