From 40c79346c1246accab589ba429775f88391a4564 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 29 Jul 2021 20:17:34 +1000 Subject: [PATCH 01/25] Issue #6553 - give 403 response if UNAUTHENTICATED and auth is mandatory Signed-off-by: Lachlan Roberts --- .../java/org/eclipse/jetty/security/SecurityHandler.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index 6fb32155cb6..0a63e17b256 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -572,6 +572,11 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti authenticator.secureResponse(request, response, isAuthMandatory, null); } } + else if ((authentication == Authentication.UNAUTHENTICATED) && isAuthMandatory) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "unauthenticated"); + baseRequest.setHandled(true); + } else { baseRequest.setAuthentication(authentication); From 29a8d5d2bde421fb58a2f547e1ecd6d9ee4105d3 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 2 Aug 2021 15:22:43 +1000 Subject: [PATCH 02/25] Issue #6554 - create the DefaultIdentityService even if no realmName is provided Signed-off-by: Lachlan Roberts --- .../java/org/eclipse/jetty/security/SecurityHandler.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index 6fb32155cb6..c235eabbaf7 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -334,11 +334,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); From d35ff03e61cccdfaf311e237c89a89a490ccedfe Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 2 Aug 2021 15:23:28 +1000 Subject: [PATCH 03/25] Issue #6554 - DefaultAuthenticatorFactory should not create BasicAuthenticator for null AuthMethod Signed-off-by: Lachlan Roberts --- .../eclipse/jetty/security/DefaultAuthenticatorFactory.java | 2 +- .../java/org/eclipse/jetty/security/SecurityHandler.java | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java index 722599c47d1..e25bd9954e3 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java @@ -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(); diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index c235eabbaf7..f502898aac2 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -293,9 +293,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti return getServer().getBean(IdentityService.class); } - /** - * - */ @Override protected void doStart() throws Exception @@ -349,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) @@ -396,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. From 9af67f8bff2b7b339df674ea29790d21ec366567 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 16 Aug 2021 10:45:26 +1000 Subject: [PATCH 04/25] Issue #6618 - azp claim should not be required for single value aud array Signed-off-by: Lachlan Roberts --- .../jetty/security/openid/JwtDecoder.java | 2 +- .../security/openid/OpenIdCredentials.java | 14 ++++--- .../openid/OpenIdCredentialsTest.java | 40 +++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java index dfdbd511f8f..69d03a478bc 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java @@ -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 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 jwtHeader = (Map)parsedJwtHeader; if (LOG.isDebugEnabled()) LOG.debug("JWT Header: {}", jwtHeader); diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java index c87ef1604f2..2dee0d32faf 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.security.openid; import java.io.Serializable; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -95,7 +96,7 @@ public class OpenIdCredentials implements Serializable claims = JwtDecoder.decode(idToken); if (LOG.isDebugEnabled()) LOG.debug("claims {}", claims); - validateClaims(configuration); + validateClaims(claims, configuration); } finally { @@ -105,14 +106,14 @@ public class OpenIdCredentials implements Serializable } } - private void validateClaims(OpenIdConfiguration configuration) throws Exception + static void validateClaims(Map claims, OpenIdConfiguration configuration) throws Exception { // Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim. if (!configuration.getIssuer().equals(claims.get("iss"))) throw new AuthenticationException("Issuer Identifier MUST exactly match the iss Claim"); // The aud (audience) Claim MUST contain the client_id value. - validateAudience(configuration); + validateAudience(claims, configuration); // If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value. Object azp = claims.get("azp"); @@ -126,7 +127,7 @@ public class OpenIdCredentials implements Serializable throw new AuthenticationException("ID Token has expired"); } - private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException + private static void validateAudience(Map claims, OpenIdConfiguration configuration) throws AuthenticationException { Object aud = claims.get("aud"); String clientId = configuration.getClientId(); @@ -138,10 +139,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 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) diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java new file mode 100644 index 00000000000..b816d975236 --- /dev/null +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java @@ -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 claims = new HashMap<>(); + claims.put("iss", issuer); + claims.put("aud", new String[]{clientId}); + claims.put("exp", System.currentTimeMillis() + 5000); + + assertDoesNotThrow(() -> OpenIdCredentials.validateClaims(claims, configuration)); + } +} From b91c3fdbb044e62d713ab0b581267dc91d9efcd3 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 16 Aug 2021 15:43:19 +1000 Subject: [PATCH 05/25] Issue #6617 - add support for the client_secret_basic authentication method Signed-off-by: Lachlan Roberts --- .../src/main/config/etc/jetty-openid.xml | 1 + .../src/main/config/modules/openid.mod | 3 +++ .../security/openid/OpenIdConfiguration.java | 23 ++++++++++++++++ .../security/openid/OpenIdCredentials.java | 26 +++++++++++++++---- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/jetty-openid/src/main/config/etc/jetty-openid.xml b/jetty-openid/src/main/config/etc/jetty-openid.xml index 5072c160495..4e792549121 100644 --- a/jetty-openid/src/main/config/etc/jetty-openid.xml +++ b/jetty-openid/src/main/config/etc/jetty-openid.xml @@ -26,6 +26,7 @@ + diff --git a/jetty-openid/src/main/config/modules/openid.mod b/jetty-openid/src/main/config/modules/openid.mod index 85f0de4c889..2c2072bb900 100644 --- a/jetty-openid/src/main/config/modules/openid.mod +++ b/jetty-openid/src/main/config/modules/openid.mod @@ -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.basicAuth=client_secret_post \ No newline at end of file diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java index a1a82fe2b0e..043a5f4ee80 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java @@ -45,6 +45,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle private final String clientId; private final String clientSecret; private final List 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, "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) diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java index c87ef1604f2..de90391a6dc 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -14,12 +14,15 @@ package org.eclipse.jetty.security.openid; import java.io.Serializable; +import java.net.URI; import java.util.Arrays; 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; @@ -153,14 +156,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()) From 518c13a636f06dcbed7abb505b5af93360d8c94c Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 16 Aug 2021 10:46:18 -0500 Subject: [PATCH 06/25] Bump slf4j to 2.0.0-alpha4 Also logback to 1.3.0-alpha9 Signed-off-by: Joakim Erdfelt --- .../eclipse/jetty/logging/JettyLoggingServiceProvider.java | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java index a5a41e29926..2b1f7a21932 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java @@ -66,7 +66,7 @@ public class JettyLoggingServiceProvider implements SLF4JServiceProvider } @Override - public String getRequesteApiVersion() + public String getRequestedApiVersion() { return REQUESTED_API_VERSION; } diff --git a/pom.xml b/pom.xml index 2cfbc481c77..e46b9815336 100644 --- a/pom.xml +++ b/pom.xml @@ -20,9 +20,9 @@ UTF-8 1.5 8.37 - 2.0.0-alpha1 + 2.0.0-alpha4 2.14.0 - 1.3.0-alpha5 + 1.3.0-alpha9 3.4.2 1.2 4.0.6 From b0503091c223194b79425fc9a8a481f908120e6d Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 17 Aug 2021 09:24:02 +1000 Subject: [PATCH 07/25] Use correct auth method string in OpenIdConfiguration default constructor Signed-off-by: Lachlan Roberts --- .../org/eclipse/jetty/security/openid/OpenIdConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java index 043a5f4ee80..8a60ec3418c 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java @@ -72,7 +72,7 @@ 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, "post", httpClient); + this(issuer, authorizationEndpoint, tokenEndpoint, clientId, clientSecret, "client_secret_post", httpClient); } /** From af316e58ffda20cd575fe48c43c317917293bd1f Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 17 Aug 2021 13:31:49 +1000 Subject: [PATCH 08/25] Issue #6618 - Use a new OpenIdCredentials constructor instead of static method. Signed-off-by: Lachlan Roberts --- .../security/openid/OpenIdCredentials.java | 21 +++++++++++++++---- .../openid/OpenIdCredentialsTest.java | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java index 2dee0d32faf..ccc26d70989 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -46,6 +46,14 @@ public class OpenIdCredentials implements Serializable private String authCode; private Map response; private Map claims; + private boolean verified = false; + + public OpenIdCredentials(Map claims) + { + this.redirectUri = null; + this.authCode = null; + this.claims = claims; + } public OpenIdCredentials(String authCode, String redirectUri) { @@ -96,7 +104,6 @@ public class OpenIdCredentials implements Serializable claims = JwtDecoder.decode(idToken); if (LOG.isDebugEnabled()) LOG.debug("claims {}", claims); - validateClaims(claims, configuration); } finally { @@ -104,16 +111,22 @@ public class OpenIdCredentials implements Serializable authCode = null; } } + + if (!verified) + { + validateClaims(configuration); + verified = true; + } } - static void validateClaims(Map claims, OpenIdConfiguration configuration) throws Exception + private void validateClaims(OpenIdConfiguration configuration) throws Exception { // Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim. if (!configuration.getIssuer().equals(claims.get("iss"))) throw new AuthenticationException("Issuer Identifier MUST exactly match the iss Claim"); // The aud (audience) Claim MUST contain the client_id value. - validateAudience(claims, configuration); + validateAudience(configuration); // If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value. Object azp = claims.get("azp"); @@ -127,7 +140,7 @@ public class OpenIdCredentials implements Serializable throw new AuthenticationException("ID Token has expired"); } - private static void validateAudience(Map claims, OpenIdConfiguration configuration) throws AuthenticationException + private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException { Object aud = claims.get("aud"); String clientId = configuration.getClientId(); diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java index b816d975236..18ac12841f4 100644 --- a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java @@ -35,6 +35,6 @@ public class OpenIdCredentialsTest claims.put("aud", new String[]{clientId}); claims.put("exp", System.currentTimeMillis() + 5000); - assertDoesNotThrow(() -> OpenIdCredentials.validateClaims(claims, configuration)); + assertDoesNotThrow(() -> new OpenIdCredentials(claims).redeemAuthCode(configuration)); } } From 50798ff504d64f9290be70f5649518ce1f581cb7 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 18 Aug 2021 10:43:50 +1000 Subject: [PATCH 09/25] Issue #6617 - change jetty property name to jetty.openid.authMethod Signed-off-by: Lachlan Roberts --- jetty-openid/src/main/config/etc/jetty-openid.xml | 2 +- jetty-openid/src/main/config/modules/openid.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-openid/src/main/config/etc/jetty-openid.xml b/jetty-openid/src/main/config/etc/jetty-openid.xml index 4e792549121..8e252c22f80 100644 --- a/jetty-openid/src/main/config/etc/jetty-openid.xml +++ b/jetty-openid/src/main/config/etc/jetty-openid.xml @@ -26,7 +26,7 @@ - + diff --git a/jetty-openid/src/main/config/modules/openid.mod b/jetty-openid/src/main/config/modules/openid.mod index 2c2072bb900..7f04767a2d9 100644 --- a/jetty-openid/src/main/config/modules/openid.mod +++ b/jetty-openid/src/main/config/modules/openid.mod @@ -44,4 +44,4 @@ etc/jetty-openid.xml # jetty.openid.sslContextFactory.trustAll=false ## What authentication method to use with the Token Endpoint (client_secret_post, client_secret_basic). -# jetty.openid.basicAuth=client_secret_post \ No newline at end of file +# jetty.openid.authMethod=client_secret_post From efb09607f1bbfde276ad574b0f3b07d86d415e40 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 18 Aug 2021 05:44:51 -0500 Subject: [PATCH 10/25] Bump slf4j to 2.0.0-alpha4 Also logback to 1.3.0-alpha9 Signed-off-by: Joakim Erdfelt --- jetty-home/src/main/resources/modules/logging-jul.mod | 2 +- jetty-home/src/main/resources/modules/logging-logback.mod | 2 +- jetty-home/src/main/resources/modules/logging/slf4j.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-home/src/main/resources/modules/logging-jul.mod b/jetty-home/src/main/resources/modules/logging-jul.mod index 1fd1cc7cb47..e105cd5c97d 100644 --- a/jetty-home/src/main/resources/modules/logging-jul.mod +++ b/jetty-home/src/main/resources/modules/logging-jul.mod @@ -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. diff --git a/jetty-home/src/main/resources/modules/logging-logback.mod b/jetty-home/src/main/resources/modules/logging-logback.mod index 50e578b72ca..ea10e350d84 100644 --- a/jetty-home/src/main/resources/modules/logging-logback.mod +++ b/jetty-home/src/main/resources/modules/logging-logback.mod @@ -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] diff --git a/jetty-home/src/main/resources/modules/logging/slf4j.mod b/jetty-home/src/main/resources/modules/logging/slf4j.mod index 413878c8406..d3c4035ae5d 100644 --- a/jetty-home/src/main/resources/modules/logging/slf4j.mod +++ b/jetty-home/src/main/resources/modules/logging/slf4j.mod @@ -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. From 1acf54ba59d61d5fb90a40124df237a87717d311 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Thu, 19 Aug 2021 09:06:52 +1000 Subject: [PATCH 11/25] Upgrade h2spec-maven-plugin to 1.0.6 (#6616) Signed-off-by: Olivier Lamy --- pom.xml | 2 +- .../org/eclipse/jetty/tests/distribution/DistributionTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2cfbc481c77..eee4038483d 100644 --- a/pom.xml +++ b/pom.xml @@ -981,7 +981,7 @@ org.mortbay.jetty h2spec-maven-plugin - 1.0.5 + 1.0.6 diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 41129f76dcb..1201ac5c170 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -843,7 +843,7 @@ public class DistributionTests extends AbstractJettyHomeTest // Protocol "h2" must not be enabled because the // http2 Jetty module was not explicitly enabled. assertFalse(run3.getLogs().stream() - .anyMatch(log -> log.contains("h2"))); + .anyMatch(log -> log.contains("h2")), "Full logs: " + String.join("", run3.getLogs())); } } } From 9f896c6390a64e2d4f1f0e7cd2bb8bc243679bd0 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 17 Aug 2021 18:35:04 +0200 Subject: [PATCH 12/25] Fixes #6624 - Non-domain SNI on java17 Java 17 only allows letter|digit|hyphen characters for SNI names. While we could bypass this restriction on the client, when the SNI bytes arrive to the server they will be verified and if not allowed the TLS handshake will fail. Signed-off-by: Simone Bordet (cherry picked from commit 693663a4ce3ed0f35cc7da66760e02c9e3bc2d97) --- .../jetty/client/HttpClientTLSTest.java | 49 ++++++++++++++----- .../jetty/util/ssl/SslContextFactory.java | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index 1efb74bb83e..d7de9d72e18 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -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 diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java index 8132414e778..c3470f4124a 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java @@ -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))); } } From b2c420a124392b3e79a6abf263ec00f51e387409 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 20 Aug 2021 11:40:18 +1000 Subject: [PATCH 13/25] Issue #6327 Ensure TempDirTest runs in envs other than CI (#6638) * Issue #6327 Ensure TempDirTest runs in envs other than CI Signed-off-by: Jan Bartel * pass sysproperty env=ci to surefire run Signed-off-by: Olivier Lamy Co-authored-by: Olivier Lamy --- .../test/java/org/eclipse/jetty/webapp/TempDirTest.java | 7 +++++-- pom.xml | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java index 97803758d4a..4f95f09fd1d 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java @@ -27,6 +27,7 @@ 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.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -289,9 +290,11 @@ public class TempDirTest /** * ServletContext.TEMPDIR has invalid String 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") @Test public void attributeWithInvalidPermissions() { diff --git a/pom.xml b/pom.xml index eee4038483d..13cbedc949b 100644 --- a/pom.xml +++ b/pom.xml @@ -1458,6 +1458,9 @@ ${maven.surefire.version} external, large-disk-resource + + ci + From a27018bc39f56bda248a2b2931e5df7d7a7c6c9c Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 20 Aug 2021 20:00:31 +1000 Subject: [PATCH 14/25] Issue #6553 - add tests, use 401 code instead of 403, add same check for DeferredAuth Signed-off-by: Lachlan Roberts --- .../jetty/security/SecurityHandler.java | 10 +- .../jetty/security/UnauthenticatedTest.java | 144 ++++++++++++++++++ 2 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index 0a63e17b256..d3b89c03bce 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -546,6 +546,11 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti if (authenticator != null) authenticator.secureResponse(request, response, isAuthMandatory, userAuth); } + else if (isAuthMandatory) + { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "unauthenticated"); + baseRequest.setHandled(true); + } else if (authentication instanceof Authentication.Deferred) { DeferredAuthentication deferred = (DeferredAuthentication)authentication; @@ -572,11 +577,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti authenticator.secureResponse(request, response, isAuthMandatory, null); } } - else if ((authentication == Authentication.UNAUTHENTICATED) && isAuthMandatory) - { - response.sendError(HttpServletResponse.SC_FORBIDDEN, "unauthenticated"); - baseRequest.setHandled(true); - } else { baseRequest.setAuthentication(authentication); diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java new file mode 100644 index 00000000000..1d9f1bcd017 --- /dev/null +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java @@ -0,0 +1,144 @@ +// +// ======================================================================== +// 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. + response = connector.getResponse("GET /requireAuth/test HTTP/1.1\r\nHost: localhost\r\n\r\n"); + assertThat(response, containsString("HTTP/1.1 401 Unauthorized")); + } + + public static class TestAuthenticator extends LoginAuthenticator + { + static AtomicReference 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; + } + } +} From b854b0b1db885d3cf1a7898b2000c16db7801c90 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 20 Aug 2021 20:17:25 +1000 Subject: [PATCH 15/25] Issue #6553 - revert behaviour for DeferredAuthentication Signed-off-by: Lachlan Roberts --- .../org/eclipse/jetty/security/SecurityHandler.java | 10 +++++----- .../eclipse/jetty/security/UnauthenticatedTest.java | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index d3b89c03bce..3a8a0f9cde2 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -546,11 +546,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti if (authenticator != null) authenticator.secureResponse(request, response, isAuthMandatory, userAuth); } - else if (isAuthMandatory) - { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "unauthenticated"); - baseRequest.setHandled(true); - } else if (authentication instanceof Authentication.Deferred) { DeferredAuthentication deferred = (DeferredAuthentication)authentication; @@ -577,6 +572,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); diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java index 1d9f1bcd017..3d51575015c 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/UnauthenticatedTest.java @@ -102,9 +102,10 @@ public class UnauthenticatedTest assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, containsString("DeferredAuthentication")); - // This URI requires just that the request is authenticated. + // 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 401 Unauthorized")); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("DeferredAuthentication")); } public static class TestAuthenticator extends LoginAuthenticator From 401f1959d2ec2ef1f540abb3fce621027d238c09 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 23 Aug 2021 11:38:15 -0500 Subject: [PATCH 16/25] Issue #6654 - Fix NPEs from bad Servlet API use Signed-off-by: Joakim Erdfelt --- .../core/server/ServerUpgradeRequest.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java index bf24cd2824c..d51258e52e6 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java @@ -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,13 @@ 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()); + } } return cookies; @@ -135,10 +140,13 @@ public class ServerUpgradeRequest { Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); Enumeration 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; } From 32e6698f0b9dc6c3c45a6010bf697a56cfd19659 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 23 Aug 2021 16:01:32 -0500 Subject: [PATCH 17/25] Issue #6654 - Fix NPEs from bad Servlet API use Signed-off-by: Joakim Erdfelt --- .../jetty/websocket/core/server/ServerUpgradeRequest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java index d51258e52e6..33dece4b8aa 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java @@ -98,6 +98,10 @@ public class ServerUpgradeRequest .map(c -> new HttpCookie(c.getName(), c.getValue())) .collect(Collectors.toList()); } + else + { + cookies = List.of(); + } } return cookies; From a82b45c5fb9acde241585e0481af1b9fcf652d5e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 24 Aug 2021 09:05:47 -0500 Subject: [PATCH 18/25] Issue #6654 - Fix NPEs from bad Servlet API use Signed-off-by: Joakim Erdfelt --- .../jetty/websocket/core/server/ServerUpgradeRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java index 33dece4b8aa..eb43ae00dfb 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java @@ -100,7 +100,7 @@ public class ServerUpgradeRequest } else { - cookies = List.of(); + cookies = Collections.emptyList(); } } From ec842078a2fba249f2921ce3ece60dae6e77449c Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 24 Aug 2021 16:18:34 -0500 Subject: [PATCH 19/25] Issue #6661 - Make ServerConnector socket options setting optional --- .../eclipse/jetty/server/ServerConnector.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java index 25592a2260d..8375c898f5d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java @@ -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,18 @@ public class ServerConnector extends AbstractNetworkConnector return serverChannel; } + private void setSocketOption(ServerSocketChannel channel, SocketOption name, T value) + { + try + { + channel.setOption(name, value); + } + catch (Throwable t) + { + LOG.warn("Unable to set socket option {} to {}", name, value, t); + } + } + @Override public void close() { From 54edcefd66b334cc0d3725d7295567407dedb1fc Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 24 Aug 2021 16:45:06 -0500 Subject: [PATCH 20/25] Issue #6661 - Make ServerConnector socket options setting optional --- .../main/java/org/eclipse/jetty/io/ClientConnector.java | 2 +- .../java/org/eclipse/jetty/server/ServerConnector.java | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java index 414db062336..c62310d84a8 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java @@ -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); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java index 8375c898f5d..4468c21fe02 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java @@ -349,15 +349,16 @@ public class ServerConnector extends AbstractNetworkConnector return serverChannel; } - private void setSocketOption(ServerSocketChannel channel, SocketOption name, T value) + private void setSocketOption(ServerSocketChannel channel, SocketOption option, T value) { try { - channel.setOption(name, value); + channel.setOption(option, value); } - catch (Throwable t) + catch (Throwable x) { - LOG.warn("Unable to set socket option {} to {}", name, value, t); + if (LOG.isDebugEnabled()) + LOG.debug("Could not configure {} to {} on {}", option, value, channel, x); } } From 705fe19e2af61f65303bf0335e5168c2be0becbd Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 25 Aug 2021 12:10:06 +1000 Subject: [PATCH 21/25] Issue #6554 - add test for creation of DefaultIdentityService Signed-off-by: Lachlan Roberts --- .../security/DefaultIdentityServiceTest.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 jetty-security/src/test/java/org/eclipse/jetty/security/DefaultIdentityServiceTest.java diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/DefaultIdentityServiceTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/DefaultIdentityServiceTest.java new file mode 100644 index 00000000000..735ee27eab1 --- /dev/null +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/DefaultIdentityServiceTest.java @@ -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; + } + } +} From e8beb58baf5221430c8b2b993613181fda2a6519 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 24 Aug 2021 23:12:08 +0200 Subject: [PATCH 22/25] Fixes #6652 - Improve ReservedThreadExecutor dump. (#6653) Fixes #6652 - Improve ReservedThreadExecutor dump. Filtering out non-reserved threads in dump() and doStop(). Signed-off-by: Simone Bordet Co-authored-by: Greg Wilkins (cherry picked from commit b2a023675c4cfd0c1bc54ab8df22630953465eea) --- .../util/thread/ReservedThreadExecutor.java | 57 ++++++++++++------- .../util/thread/QueuedThreadPoolTest.java | 4 +- .../thread/ReservedThreadExecutorTest.java | 1 - 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java index 81d41d0c7a2..42446e9b37a 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java @@ -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. - *

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. - *

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 + *

A TryExecutor using pre-allocated/reserved threads from an external Executor.

+ *

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.

+ *

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.

*/ @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 _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) { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java index bb6d3a5af43..c065eb38578 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java @@ -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)); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java index e3a08b876bf..58835091022 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/ReservedThreadExecutorTest.java @@ -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); } } From 2689cf75d1e30f1b1e3ae4a1d6c491d0feeac6c4 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 26 Aug 2021 01:28:33 +0200 Subject: [PATCH 23/25] Fixes #6646 - Propagate callback invocation type to avoid deadlock in SmallThreadPoolLoadTest Fixed occurrences of Callbacks that did not override getInvocationType() to properly declare whether they block or not. Added test case for blocking writes for both client and server. Signed-off-by: Simone Bordet (cherry picked from commit 9897c1b06e83bdbe37ba21d7f469f0ea53ff8f04) --- .../eclipse/jetty/demos/FastFileServer.java | 6 + .../client/HTTP2ClientConnectionFactory.java | 6 + .../org/eclipse/jetty/http2/HTTP2Flusher.java | 12 + .../org/eclipse/jetty/http2/HTTP2Session.java | 5 +- .../org/eclipse/jetty/http2/HTTP2Stream.java | 9 + .../http2-http-client-transport/pom.xml | 5 + .../BlockedWritesWithSmallThreadPoolTest.java | 281 ++++++++++++++++++ .../org/eclipse/jetty/server/HttpChannel.java | 4 +- .../org/eclipse/jetty/server/HttpOutput.java | 32 +- .../eclipse/jetty/server/ResourceService.java | 6 + .../java/org/eclipse/jetty/util/Callback.java | 27 +- 11 files changed, 375 insertions(+), 18 deletions(-) create mode 100644 jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/BlockedWritesWithSmallThreadPoolTest.java diff --git a/demos/embedded/src/main/java/org/eclipse/jetty/demos/FastFileServer.java b/demos/embedded/src/main/java/org/eclipse/jetty/demos/FastFileServer.java index 2bd217d118a..8cd69fa09ba 100644 --- a/demos/embedded/src/main/java/org/eclipse/jetty/demos/FastFileServer.java +++ b/demos/embedded/src/main/java/org/eclipse/jetty/demos/FastFileServer.java @@ -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 diff --git a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java index 2a6be2b23be..7d198e7f2d1 100644 --- a/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java +++ b/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java @@ -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 diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java index aedaa2e0bef..62dd5caca42 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java @@ -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 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) { diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index 802718af54b..3f05639f6a4 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -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); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index 11f1c1056e9..a7783f57f7b 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -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()) diff --git a/jetty-http2/http2-http-client-transport/pom.xml b/jetty-http2/http2-http-client-transport/pom.xml index 0048ecb9d03..2aafe38d95b 100644 --- a/jetty-http2/http2-http-client-transport/pom.xml +++ b/jetty-http2/http2-http-client-transport/pom.xml @@ -63,6 +63,11 @@ ${project.version} test + + org.awaitility + awaitility + test + diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/BlockedWritesWithSmallThreadPoolTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/BlockedWritesWithSmallThreadPoolTest.java new file mode 100644 index 00000000000..fc9e20d8e6c --- /dev/null +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/BlockedWritesWithSmallThreadPoolTest.java @@ -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 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 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 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 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(); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 632929d8f46..f7d3ab5671c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -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; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index f8df5ea27a2..42ed01b7b68 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -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; + } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java index 5172c42386b..afab62eed49 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java @@ -717,6 +717,12 @@ public class ResourceService content.release(); } + @Override + public InvocationType getInvocationType() + { + return InvocationType.NON_BLOCKING; + } + @Override public String toString() { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java index 634b1aa3ea9..d5c2fd60e3a 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java @@ -108,13 +108,26 @@ public interface Callback extends Invocable } /** - * Create 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 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 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() From c5a33e27d2a21dc90b0d9fc871f8c3d64a893cf6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 26 Aug 2021 11:26:46 -0500 Subject: [PATCH 24/25] Issue #5684 - Windows test overhaul (#6581) Issue #5684 - Window's test overhaul + Migrate from @DisabledOnOs(WINDOWS) to assumptions on capabilities instead. + Fix other outstanding windows testing issues. + Cleanup FileBufferedResponseHandlerTest expectations on Windows. + PathWatcher scan interval is variable on windows + If unable to start testcase based on assumption, the stop shouldn't fail testcase + Increase various wait timeouts + Make tests less strict due to system speed issues + Disable Sni tests due to TLS behaviors differences in Windows + Windows TLSv1.3 seems to introduce this difference + If we restrict to TLSv1.2 this passes. + On Linux TLSv.13 on client side will always return a + javax.net.ssl.SSLHandshakeException in those test cases that expect it. + However, on Windows, Only the TLSv1.2 implementation will return a javax.net.ssl.SSLHandshakeException, + All other TLS versions on Windows will result in a + javax.net.ssl.SSLException: Software caused connection abort: recv failed + Disable ConcurrentStreamCreationTest + Not possible to create all of these streams. + Fixing DeploymentTempDirTest + Using unique workdir per testcase. + Don't expect to delete files / directories between tests (not supported on windows due to file locking anyway) + Fixing line ending difference on windows + InvalidPathException is a 404 Not Found + Cannot reuse test directory between runs due to memory mapped files that are still in use from previous run. + java.nio.file.FileSystemException: C:\code\jetty.project\jetty-webapp\target\tests\welcome#\index.html: The requested operation cannot be performed on a file with a user-mapped section open. at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108) at java.base/sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:235) at java.base/java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:478) at java.base/java.nio.file.Files.newOutputStream(Files.java:220) at org.eclipse.jetty.webapp/org.eclipse.jetty.webapp.WebAppDefaultServletTest.prepareServer(WebAppDefaultServletTest.java:84) + As is typical on windows, we are often unable to delete a file due to file locking issues. + Use a unique resource base between tests. This is to avoid file locking behaviors that prevent the resource base from being reused too quickly on windows. + Prevent test run if symlinks not supported + Allowing for Windows slosh char as well in asserts + SelectorUtils is File.separator dependent + Regex is now FS.separator independent + Using SelectorUtils from plexus correctly for include/exclude + Turning off mapped files for testing reasons. + Fix and re-enable RFC2616NIOHttpsTest + Issue #6552 - Fix test failures due to slf4j dep + Issue #6552 - upgrade testcontainers + Issue #6552 - move to assumption based docker existence + Issue #6552 - Fix enforcer rule violation on jna. Addresses the following side effect of upgrading testcontainers. [WARNING] Rule 3: org.apache.maven.plugins.enforcer.RequireUpperBoundDeps failed with message: Failed while enforcing RequireUpperBoundDeps. The error(s) are [ Require upper bound dependencies error for net.java.dev.jna:jna:5.6.0 paths to dependency are: +-org.eclipse.jetty:infinispan-remote-query:10.0.7-SNAPSHOT +-org.testcontainers:testcontainers:1.16.0 +-com.github.docker-java:docker-java-transport-zerodep:3.2.11 +-net.java.dev.jna:jna:5.6.0 (managed) <-- net.java.dev.jna:jna:5.8.0 + use annotation to disable test when docker not available and needed + Disabling FileSessionDistributionTests.stopRestartWebappTestSessionContentSaved on Windows + Using TLS basic + Programmatic removal of memory mapped behavior during testing + Fixing slf4j warning Signed-off-by: Joakim Erdfelt Co-authored-by: Olivier Lamy --- .../jetty/client/ssl/SslBytesServerTest.java | 4 - .../jetty/deploy/DeploymentTempDirTest.java | 33 ++++-- ...ScanningAppProviderRuntimeUpdatesTest.java | 5 - .../deploy/providers/WebAppProviderTest.java | 2 + .../client/ConcurrentStreamCreationTest.java | 3 + .../http2/client/SmallThreadPoolLoadTest.java | 10 +- jetty-maven-plugin/pom.xml | 5 + .../maven/plugin/SelectiveJarResource.java | 9 +- .../plugin/TestSelectiveJarResource.java | 105 ++++++++---------- .../test/resources/jetty-logging.properties | 5 + .../proxy/AsyncMiddleManServletTest.java | 2 +- .../jetty/security/PropertyUserStoreTest.java | 2 - .../eclipse/jetty/server/ResourceService.java | 11 ++ .../handler/FileBufferedResponseHandler.java | 4 +- .../jetty/server/GracefulStopTest.java | 8 +- .../jetty/server/ServerConnectorTest.java | 3 + .../org/eclipse/jetty/server/StressTest.java | 7 +- .../FileBufferedResponseHandlerTest.java | 78 +++++++++---- .../ssl/ServerConnectorSslServerTest.java | 41 ------- .../ssl/SniSslConnectionFactoryTest.java | 6 +- .../eclipse/jetty/servlets/WelcomeFilter.java | 1 + .../jetty/servlets/AbstractDoSFilterTest.java | 4 +- .../jetty/servlets/WelcomeFilterTest.java | 3 + .../logging/JettyLoggingServiceProvider.java | 4 +- .../usecases/empty.addModule.assert.txt | 8 +- .../empty.addModuleCreateStartIni.assert.txt | 8 +- .../minimal-start-d.alreadyEnabled.assert.txt | 2 +- .../minimal-start-d.createStartIni.assert.txt | 6 +- .../eclipse/jetty/util/PathWatcherTest.java | 2 +- .../org/eclipse/jetty/util/ScannerTest.java | 86 +++++++------- .../util/resource/FileSystemResourceTest.java | 82 +++++--------- .../jetty/util/resource/ResourceTest.java | 19 +++- .../jetty/webapp/HugeResourceTest.java | 19 +++- .../org/eclipse/jetty/webapp/TempDirTest.java | 4 +- .../webapp/WebAppDefaultServletTest.java | 11 +- .../tests/WebSocketOverHTTP2Test.java | 3 + pom.xml | 5 +- tests/test-distribution/pom.xml | 5 + .../tests/distribution/DistributionTests.java | 2 +- .../distribution/LoggingOptionsTests.java | 40 +++---- .../AbstractSessionDistributionTests.java | 2 + .../session/FileSessionDistributionTests.java | 16 ++- ...eSessionWithMemcacheDistributionTests.java | 1 + .../HazelcastSessionDistributionTests.java | 52 +++++---- .../MongodbSessionDistributionTests.java | 7 +- .../jetty/test/KeyStoreScannerTest.java | 35 +++++- .../jetty/test/rfcs/RFC2616BaseTest.java | 1 - .../jetty/test/rfcs/RFC2616NIOHttpsTest.java | 4 +- .../test/support/XmlBasedJettyServer.java | 4 + .../test/support/rawhttp/HttpsSocketImpl.java | 16 --- .../src/test/resources/NIOHttps.xml | 45 ++++---- .../src/test/resources/ssl.xml | 24 ++-- .../RFC2616/rfc2616-webapp.xml | 8 ++ .../sessions/MemcachedTestHelper.java | 3 +- .../jetty/nosql/mongodb/MongoTestHelper.java | 7 +- 55 files changed, 461 insertions(+), 421 deletions(-) create mode 100644 jetty-maven-plugin/src/test/resources/jetty-logging.properties diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java index 013dfe7c039..746cc87fdfb 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java @@ -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(); diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentTempDirTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentTempDirTest.java index 1ae25e5479a..6512cd5eaaa 100644 --- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentTempDirTest.java +++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentTempDirTest.java @@ -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()); diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java index 0480afc22e3..855271d6340 100644 --- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java +++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java @@ -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"); diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java index e0ec2a8e8c4..f081b183485 100644 --- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java +++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java @@ -99,6 +99,8 @@ public class WebAppProviderTest @Test public void testStartupContext() { + assumeTrue(symlinkSupported); + // Check Server for Handlers jetty.assertWebAppContextsExists("/bar", "/foo", "/bob"); diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java index 26b7f459aff..902d69985b8 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConcurrentStreamCreationTest.java @@ -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 diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SmallThreadPoolLoadTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SmallThreadPoolLoadTest.java index cba850eac9a..ab36b21f4a8 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SmallThreadPoolLoadTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/SmallThreadPoolLoadTest.java @@ -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(); } diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml index 5dac615e963..f42407280fd 100644 --- a/jetty-maven-plugin/pom.xml +++ b/jetty-maven-plugin/pom.xml @@ -125,6 +125,11 @@ maven-artifact-transfer 0.12.0 + + org.codehaus.plexus + plexus-utils + 3.4.0 + org.apache.maven maven-plugin-api diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SelectiveJarResource.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SelectiveJarResource.java index a0ff34731ca..860db92485f 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SelectiveJarResource.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SelectiveJarResource.java @@ -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); diff --git a/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/TestSelectiveJarResource.java b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/TestSelectiveJarResource.java index 249e7a3cb16..c08849ee221 100644 --- a/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/TestSelectiveJarResource.java +++ b/jetty-maven-plugin/src/test/java/org/eclipse/jetty/maven/plugin/TestSelectiveJarResource.java @@ -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 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 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 excludes = new ArrayList<>(); @@ -111,15 +96,15 @@ public class TestSelectiveJarResource List 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"))); } } } diff --git a/jetty-maven-plugin/src/test/resources/jetty-logging.properties b/jetty-maven-plugin/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..c1e6a540a53 --- /dev/null +++ b/jetty-maven-plugin/src/test/resources/jetty-logging.properties @@ -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 diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java index 0058c37bfab..6dda747734e 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java @@ -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()); diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java index 9aa2a715c45..926bfcad8cc 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java @@ -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(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java index afab62eed49..fd31944a40f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java @@ -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); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java index 201f8da802e..a849f5bb217 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java @@ -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; } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java index 034162db4f5..8a182f5768d 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java @@ -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) { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java index cd5db4be13e..9d061212edf 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java @@ -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; diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java index 606cfddc63d..43fa2319fa8 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java @@ -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); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandlerTest.java index 2b00666b7bb..c0427b95947 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandlerTest.java @@ -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 diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java index 4a8d0af7a9b..8570c6ebfd5 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java @@ -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 { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java index 91954a40065..2440fe8b734 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java @@ -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 -> diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java index d5abac44bda..2a6c899a906 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java @@ -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 diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java index 265e3382af8..06905344f5f 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java @@ -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); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java index 2e3cbae9952..7f5a1733a76 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java @@ -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 = "/*"; diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java index 2b1f7a21932..fa48a15dcae 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java @@ -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; diff --git a/jetty-start/src/test/resources/usecases/empty.addModule.assert.txt b/jetty-start/src/test/resources/usecases/empty.addModule.assert.txt index 5b85abf8491..f861cba93cf 100644 --- a/jetty-start/src/test/resources/usecases/empty.addModule.assert.txt +++ b/jetty-start/src/test/resources/usecases/empty.addModule.assert.txt @@ -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 diff --git a/jetty-start/src/test/resources/usecases/empty.addModuleCreateStartIni.assert.txt b/jetty-start/src/test/resources/usecases/empty.addModuleCreateStartIni.assert.txt index 8c767ba8ff4..010e760a6f2 100644 --- a/jetty-start/src/test/resources/usecases/empty.addModuleCreateStartIni.assert.txt +++ b/jetty-start/src/test/resources/usecases/empty.addModuleCreateStartIni.assert.txt @@ -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 diff --git a/jetty-start/src/test/resources/usecases/minimal-start-d.alreadyEnabled.assert.txt b/jetty-start/src/test/resources/usecases/minimal-start-d.alreadyEnabled.assert.txt index dcd3d29c6bf..546e1fca67a 100644 --- a/jetty-start/src/test/resources/usecases/minimal-start-d.alreadyEnabled.assert.txt +++ b/jetty-start/src/test/resources/usecases/minimal-start-d.alreadyEnabled.assert.txt @@ -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\] diff --git a/jetty-start/src/test/resources/usecases/minimal-start-d.createStartIni.assert.txt b/jetty-start/src/test/resources/usecases/minimal-start-d.createStartIni.assert.txt index 5e6bd45e669..c4c75dc8bcc 100644 --- a/jetty-start/src/test/resources/usecases/minimal-start-d.createStartIni.assert.txt +++ b/jetty-start/src/test/resources/usecases/minimal-start-d.createStartIni.assert.txt @@ -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 diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java index 65cacdcd07c..b2093dba4e3 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/PathWatcherTest.java @@ -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); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java index 534f65c30fa..aca6710e800 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java @@ -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 _queue = new LinkedBlockingQueue<>(); - static BlockingQueue> _bulk = new LinkedBlockingQueue<>(); + public WorkDir workDir; + private Path _directory; + private Scanner _scanner; + private BlockingQueue _queue = new LinkedBlockingQueue<>(); + private BlockingQueue> _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 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 diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java index 75e9ced163b..420ea8c3af4 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java @@ -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 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 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 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 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 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 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 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 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 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; } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java index b1787fe2d72..6e44ceb17da 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java @@ -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 diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java index 2fa8c222e53..c129dfb0479 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/HugeResourceTest.java @@ -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 diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java index 4f95f09fd1d..e31399daf11 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/TempDirTest.java @@ -25,9 +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; @@ -295,6 +296,7 @@ public class TempDirTest * so we _will_ 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() { diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java index 2ab5aeb110b..b28a0ad4818 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java @@ -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)) { diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java index d2d76a79463..875e184e6de 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java @@ -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(); diff --git a/pom.xml b/pom.xml index 9ed71656e0f..7f3a5be5a4e 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,8 @@ src/it/settings.xml false 0 - 1.15.1 + 1.16.0 + 5.8.0 2.7.0 @@ -1151,7 +1152,7 @@ net.java.dev.jna jna - 5.6.0 + ${jna.version} diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index 304f6da8356..a533301f2d1 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -191,6 +191,11 @@ gcloud test + + org.testcontainers + junit-jupiter + test + org.mariadb.jdbc mariadb-java-client diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 1201ac5c170..a051e06e42b 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -275,7 +275,7 @@ public class DistributionTests extends AbstractJettyHomeTest } @Test - @DisabledOnOs(OS.WINDOWS) // jnr not supported on windows + @DisabledOnOs(value = OS.WINDOWS, disabledReason = "jnr not supported on windows") public void testUnixSocket() throws Exception { String dir = System.getProperty("jetty.unixdomain.dir"); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/LoggingOptionsTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/LoggingOptionsTests.java index 3d3278b4399..c21e49eb0d7 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/LoggingOptionsTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/LoggingOptionsTests.java @@ -42,8 +42,8 @@ public class LoggingOptionsTests extends AbstractJettyHomeTest return Stream.of( Arguments.of("logging-jetty", Arrays.asList( - "\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar", - "\\$\\{jetty.home\\}/lib/logging/jetty-slf4j-impl-.*\\.jar"), + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-api-.*\\.jar", + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]jetty-slf4j-impl-.*\\.jar"), Arrays.asList( "logging/slf4j", "logging-jetty" @@ -51,9 +51,9 @@ public class LoggingOptionsTests extends AbstractJettyHomeTest ), Arguments.of("logging-logback", Arrays.asList( - "\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/logback-classic-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/logback-core-.*\\.jar" + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-api-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]logback-classic-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]logback-core-.*\\.jar" ), Arrays.asList( "logging/slf4j", @@ -62,8 +62,8 @@ public class LoggingOptionsTests extends AbstractJettyHomeTest ), Arguments.of("logging-jul", Arrays.asList( - "\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/slf4j-jdk14-.*\\.jar" + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-api-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-jdk14-.*\\.jar" ), Arrays.asList( "logging/slf4j", @@ -72,8 +72,8 @@ public class LoggingOptionsTests extends AbstractJettyHomeTest ), Arguments.of("logging-log4j1", Arrays.asList( - "\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/slf4j-log4j12-.*\\.jar" + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-api-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-log4j12-.*\\.jar" ), Arrays.asList( "logging/slf4j", @@ -82,10 +82,10 @@ public class LoggingOptionsTests extends AbstractJettyHomeTest ), Arguments.of("logging-log4j2", Arrays.asList( - "\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/log4j-slf4j18-impl-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/log4j-api-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/log4j-core-.*\\.jar" + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-api-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]log4j-slf4j18-impl-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]log4j-api-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]log4j-core-.*\\.jar" ), Arrays.asList( "logging/slf4j", @@ -95,7 +95,7 @@ public class LoggingOptionsTests extends AbstractJettyHomeTest // Disabled, as slf4j noop is not supported by output/log monitoring of AbstractJettyHomeTest /* Arguments.of("logging-noop", Arrays.asList( - "\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar" + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-api-.*\\.jar" ), Arrays.asList( "logging/slf4j", "logging-log4j2" @@ -103,12 +103,12 @@ public class LoggingOptionsTests extends AbstractJettyHomeTest ),*/ Arguments.of("logging-logback,logging-jcl-capture,logging-jul-capture,logging-log4j1-capture", Arrays.asList( - "\\$\\{jetty.home\\}/lib/logging/slf4j-api-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/logback-classic-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/logback-core-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/jcl-over-slf4j-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/jul-to-slf4j-.*\\.jar", - "\\$\\{jetty.base\\}/lib/logging/log4j-over-slf4j-.*\\.jar" + "\\$\\{jetty.home\\}[/\\\\]lib[/\\\\]logging[/\\\\]slf4j-api-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]logback-classic-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]logback-core-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]jcl-over-slf4j-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]jul-to-slf4j-.*\\.jar", + "\\$\\{jetty.base\\}[/\\\\]lib[/\\\\]logging[/\\\\]log4j-over-slf4j-.*\\.jar" ), Arrays.asList( "logging/slf4j", diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/AbstractSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/AbstractSessionDistributionTests.java index c35171cd5fa..6871d2eb1c0 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/AbstractSessionDistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/AbstractSessionDistributionTests.java @@ -32,12 +32,14 @@ import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; import org.eclipse.jetty.tests.distribution.JettyHomeTester; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Testcontainers; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +@Testcontainers(disabledWithoutDocker = true) public abstract class AbstractSessionDistributionTests extends AbstractJettyHomeTest { diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionDistributionTests.java index 025bee15b69..614b89a5e47 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionDistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionDistributionTests.java @@ -16,9 +16,12 @@ package org.eclipse.jetty.tests.distribution.session; import java.util.Collections; import java.util.List; -/** - * - */ +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers(disabledWithoutDocker = false) public class FileSessionDistributionTests extends AbstractSessionDistributionTests { @@ -52,4 +55,11 @@ public class FileSessionDistributionTests extends AbstractSessionDistributionTes return Collections.emptyList(); } + @Override + @Test + @DisabledOnOs(value = OS.WINDOWS, disabledReason = "File always locked between stop/start") + public void stopRestartWebappTestSessionContentSaved() throws Exception + { + super.stopRestartWebappTestSessionContentSaved(); + } } diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionWithMemcacheDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionWithMemcacheDistributionTests.java index 70973d96415..63e4b8616fd 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionWithMemcacheDistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/FileSessionWithMemcacheDistributionTests.java @@ -43,6 +43,7 @@ public class FileSessionWithMemcacheDistributionTests extends AbstractSessionDis { memcached = new GenericContainer("memcached:" + System.getProperty("memcached.docker.version", "1.6.6")) + .withExposedPorts(11211) .withLogConsumer(new Slf4jLogConsumer(MEMCACHED_LOG)); memcached.start(); this.host = memcached.getContainerIpAddress(); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/HazelcastSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/HazelcastSessionDistributionTests.java index 338391e766f..7e541697e2d 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/HazelcastSessionDistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/HazelcastSessionDistributionTests.java @@ -30,9 +30,13 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.tests.distribution.JettyHomeTester; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; @@ -43,22 +47,24 @@ import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * This simulate the onlyClient option which means the JVM running Jetty is only an Hazelcast client and not part - * of the cluster - */ public class HazelcastSessionDistributionTests extends AbstractSessionDistributionTests { private static final Logger HAZELCAST_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.session.HazelcastLogs"); private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastSessionDistributionTests.class); - private GenericContainer hazelcast = new GenericContainer("hazelcast/hazelcast:" + System.getProperty("hazelcast.version", "4.1")) + private GenericContainer hazelcast; + + private Path hazelcastJettyPath; + + @BeforeEach + public void setupHazelcast() + { + hazelcast = new GenericContainer<>("hazelcast/hazelcast:" + System.getProperty("hazelcast.version", "4.1")) .withExposedPorts(5701) .waitingFor(Wait.forListeningPort()) .withLogConsumer(new Slf4jLogConsumer(HAZELCAST_LOG)); - - private Path hazelcastJettyPath; + } @Override public void startExternalSessionStorage() throws Exception @@ -75,12 +81,12 @@ public class HazelcastSessionDistributionTests extends AbstractSessionDistributi tokenValues.put("hazelcast_port", Integer.toString(hazelcastPort)); this.hazelcastJettyPath = Paths.get("target/hazelcast-client.xml"); transformFileWithHostAndPort(Paths.get("src/test/resources/hazelcast-client.xml"), - hazelcastJettyPath, - tokenValues); + hazelcastJettyPath, + tokenValues); } @Override - public void stopExternalSessionStorage() throws Exception + public void stopExternalSessionStorage() { hazelcast.stop(); } @@ -101,31 +107,32 @@ public class HazelcastSessionDistributionTests extends AbstractSessionDistributi public List getSecondStartExtraArgs() { return Arrays.asList( - "jetty.session.hazelcast.configurationLocation=" + hazelcastJettyPath.toAbsolutePath(), - "jetty.session.hazelcast.onlyClient=true" - ); + "jetty.session.hazelcast.configurationLocation=" + hazelcastJettyPath.toAbsolutePath(), + "jetty.session.hazelcast.onlyClient=true" + ); } - @Disabled("not working see https://github.com/hazelcast/hazelcast/issues/18508") /** * This test simulate Hazelcast instance within Jetty a cluster member with an external Hazelcast instance */ + @Test + @Disabled("not working see https://github.com/hazelcast/hazelcast/issues/18508") public void testHazelcastRemoteAndPartOfCluster() throws Exception { Map env = new HashMap<>(); // -Dhazelcast.local.publicAddress=127.0.0.1:5701 env.put("JAVA_OPTS", "-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml"); - try (GenericContainer hazelcast = - new GenericContainer("hazelcast/hazelcast:" + System.getProperty("hazelcast.version", "4.1")) + try (GenericContainer hazelcast = + new GenericContainer<>("hazelcast/hazelcast:" + System.getProperty("hazelcast.version", "4.1")) .withExposedPorts(5701, 5705) .withEnv(env) .waitingFor(Wait.forLogMessage(".*is STARTED.*", 1)) //.withNetworkMode("host") //.waitingFor(Wait.forListeningPort()) .withClasspathResourceMapping("hazelcast-server.xml", - "/opt/hazelcast/config_ext/hazelcast.xml", - BindMode.READ_ONLY) + "/opt/hazelcast/config_ext/hazelcast.xml", + BindMode.READ_ONLY) .withLogConsumer(new Slf4jLogConsumer(HAZELCAST_LOG))) { hazelcast.start(); @@ -141,8 +148,8 @@ public class HazelcastSessionDistributionTests extends AbstractSessionDistributi // tokenValues.put("hazelcast_multicast_port", Integer.toString(hazelcastMultiCastPort)); Path hazelcastJettyPath = Paths.get("target/hazelcast-jetty.xml"); transformFileWithHostAndPort(Paths.get("src/test/resources/hazelcast-jetty.xml"), - hazelcastJettyPath, - tokenValues); + hazelcastJettyPath, + tokenValues); String jettyVersion = System.getProperty("jettyVersion"); JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() @@ -195,7 +202,6 @@ public class HazelcastSessionDistributionTests extends AbstractSessionDistributi assertThat(response.getContentAsString(), containsString("SESSION READ CHOCOLATE THE BEST:FRENCH")); } } - } } @@ -220,13 +226,11 @@ public class HazelcastSessionDistributionTests extends AbstractSessionDistributi newLine.setLength(0); newLine.append(interpolated); }); - fileContent.append(newLine.toString()); + fileContent.append(newLine); fileContent.append(System.lineSeparator()); }); - outputStream.write(fileContent.toString().getBytes(StandardCharsets.UTF_8)); } } - } diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/MongodbSessionDistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/MongodbSessionDistributionTests.java index 80558f61d3c..56a0a6d32df 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/MongodbSessionDistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/session/MongodbSessionDistributionTests.java @@ -32,12 +32,13 @@ public class MongodbSessionDistributionTests extends AbstractSessionDistribution private static final Logger MONGO_LOG = LoggerFactory.getLogger("org.eclipse.jetty.tests.distribution.session.mongo"); + private static final int MONGO_PORT = 27017; + final String imageName = "mongo:" + System.getProperty("mongo.docker.version", "2.2.7"); final GenericContainer mongoDBContainer = new GenericContainer(imageName) .withLogConsumer(new Slf4jLogConsumer(MONGO_LOG)) - .waitingFor(new LogMessageWaitStrategy() - .withRegEx(".*waiting for connections.*")); + .withExposedPorts(MONGO_PORT); private String host; private int port; @@ -46,7 +47,7 @@ public class MongodbSessionDistributionTests extends AbstractSessionDistribution { mongoDBContainer.start(); host = mongoDBContainer.getHost(); - port = mongoDBContainer.getMappedPort(27017); + port = mongoDBContainer.getMappedPort(MONGO_PORT); } @Override diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java index c264b14e5ae..d5d78f26b95 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java @@ -13,7 +13,9 @@ package org.eclipse.jetty.test; +import java.io.IOException; import java.net.URL; +import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -35,22 +37,23 @@ 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.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.component.LifeCycle; import org.eclipse.jetty.util.ssl.KeyStoreScanner; import org.eclipse.jetty.util.ssl.SslContextFactory; 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 static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.condition.OS.WINDOWS; +import static org.junit.jupiter.api.Assumptions.assumeFalse; @ExtendWith(WorkDirExtension.class) public class KeyStoreScannerTest @@ -107,7 +110,7 @@ public class KeyStoreScannerTest @AfterEach public void stop() throws Exception { - server.stop(); + LifeCycle.stop(server); } @Test @@ -176,9 +179,9 @@ public class KeyStoreScannerTest } @Test - @DisabledOnOs(WINDOWS) // does not support symbolic link public void testReloadChangingSymbolicLink() throws Exception { + assumeFileSystemSupportsSymlink(); Path keystorePath = keystoreDir.resolve("symlinkKeystore"); start(sslContextFactory -> { @@ -203,9 +206,9 @@ public class KeyStoreScannerTest } @Test - @DisabledOnOs(WINDOWS) // does not support symbolic link public void testReloadChangingTargetOfSymbolicLink() throws Exception { + assumeFileSystemSupportsSymlink(); Path keystoreLink = keystoreDir.resolve("symlinkKeystore"); Path oldKeystoreSrc = MavenTestingUtils.getTestResourcePathFile("oldKeystore"); Path newKeystoreSrc = MavenTestingUtils.getTestResourcePathFile("newKeystore"); @@ -273,6 +276,28 @@ public class KeyStoreScannerTest return (X509Certificate)certs[0]; } + private void assumeFileSystemSupportsSymlink() throws IOException + { + // Make symlink + Path dir = MavenTestingUtils.getTargetTestingPath("symlink-test"); + FS.ensureEmpty(dir); + + Path foo = dir.resolve("foo"); + Path bar = dir.resolve("bar"); + + try + { + Files.createFile(foo); + Files.createSymbolicLink(bar, foo); + } + catch (UnsupportedOperationException | FileSystemException e) + { + // if unable to create symlink, no point testing the rest + // this is the path that Microsoft Windows takes. + assumeFalse(true, "Not supported"); + } + } + private static class DefaultTrustManager implements X509TrustManager { @Override diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java index 66d37d262e9..14b91147467 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java @@ -96,7 +96,6 @@ public abstract class RFC2616BaseTest server = testableserver; server.load(); server.start(); - //server.getServer().dumpStdErr(); } @BeforeEach diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java index 6681f4ebb7a..5f8d06d8aa9 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java @@ -18,13 +18,10 @@ import org.eclipse.jetty.test.support.XmlBasedJettyServer; import org.eclipse.jetty.test.support.rawhttp.HttpSocket; import org.eclipse.jetty.test.support.rawhttp.HttpsSocketImpl; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; /** * Perform the RFC2616 tests against a server running with the Jetty NIO Connector and listening on HTTPS (HTTP over SSL). - * TODO */ -@Disabled("TODO") public class RFC2616NIOHttpsTest extends RFC2616BaseTest { @BeforeAll @@ -35,6 +32,7 @@ public class RFC2616NIOHttpsTest extends RFC2616BaseTest server.addXmlConfiguration("RFC2616Base.xml"); server.addXmlConfiguration("RFC2616_Redirects.xml"); server.addXmlConfiguration("RFC2616_Filters.xml"); + server.addXmlConfiguration("ssl.xml"); server.addXmlConfiguration("NIOHttps.xml"); setUpServer(server, RFC2616NIOHttpsTest.class); } diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java index 060c44b4ba4..e19387c95fd 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java @@ -77,6 +77,10 @@ public class XmlBasedJettyServer Path webappsDir = MavenTestingUtils.getTargetPath("webapps"); properties.setProperty("test.webapps", webappsDir.toString()); + Path keystorePath = MavenTestingUtils.getTestResourcePathFile("keystore.p12"); + properties.setProperty("jetty.sslContext.keyStorePath", keystorePath.toString()); + properties.setProperty("jetty.sslContext.keyStorePassword", "storepwd"); + // Write out configuration for use by ConfigurationManager. Path testConfig = targetDir.resolve("testable-jetty-server-config.properties"); try (OutputStream out = Files.newOutputStream(testConfig)) diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java index 424bb7d1f05..1ebe471dc2c 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java @@ -18,9 +18,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -40,21 +38,8 @@ public class HttpsSocketImpl implements HttpSocket public HttpsSocketImpl() throws Exception { - @SuppressWarnings("unused") - HostnameVerifier hostnameVerifier = new HostnameVerifier() - { - @Override - public boolean verify(String urlHostName, SSLSession session) - { - LOG.warn("Warning: URL Host: " + urlHostName + " vs." + session.getPeerHost()); - return true; - } - }; - - // Install the all-trusting trust manager try { - // TODO real trust manager this.sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom()); } @@ -70,7 +55,6 @@ public class HttpsSocketImpl implements HttpSocket public Socket connect(InetAddress host, int port) throws IOException { SSLSocket sslsock = (SSLSocket)sslfactory.createSocket(); - sslsock.setEnabledProtocols(new String[]{"TLSv1"}); SocketAddress address = new InetSocketAddress(host, port); sslsock.connect(address); return sslsock; diff --git a/tests/test-integration/src/test/resources/NIOHttps.xml b/tests/test-integration/src/test/resources/NIOHttps.xml index b08409060f8..db2513ef8b9 100644 --- a/tests/test-integration/src/test/resources/NIOHttps.xml +++ b/tests/test-integration/src/test/resources/NIOHttps.xml @@ -1,36 +1,31 @@ - + - - - + + + - - - - - http/1.1 - - - - - - - - - - - - 30000 - + + + + + http/1.1 + + + + + + + + + + + - - - diff --git a/tests/test-integration/src/test/resources/ssl.xml b/tests/test-integration/src/test/resources/ssl.xml index df45814de18..88d6cf3e16f 100644 --- a/tests/test-integration/src/test/resources/ssl.xml +++ b/tests/test-integration/src/test/resources/ssl.xml @@ -1,22 +1,9 @@ - / + + - - / - - - - SSL_RSA_WITH_DES_CBC_SHA - SSL_DHE_RSA_WITH_DES_CBC_SHA - SSL_DHE_DSS_WITH_DES_CBC_SHA - SSL_RSA_EXPORT_WITH_RC4_40_MD5 - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA - - @@ -27,7 +14,12 @@ - + + + false + false + + diff --git a/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml b/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml index 55cd5a5c847..7c7a5934411 100644 --- a/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml +++ b/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml @@ -10,4 +10,12 @@
+ + org.slf4j. + org.eclipse.jetty.logging. + + + -org.slf4j. + -org.eclipse.jetty.logging. + diff --git a/tests/test-sessions/test-memcached-sessions/src/test/java/org/eclipse/jetty/memcached/sessions/MemcachedTestHelper.java b/tests/test-sessions/test-memcached-sessions/src/test/java/org/eclipse/jetty/memcached/sessions/MemcachedTestHelper.java index a9407c92376..0d5e3662971 100644 --- a/tests/test-sessions/test-memcached-sessions/src/test/java/org/eclipse/jetty/memcached/sessions/MemcachedTestHelper.java +++ b/tests/test-sessions/test-memcached-sessions/src/test/java/org/eclipse/jetty/memcached/sessions/MemcachedTestHelper.java @@ -149,7 +149,8 @@ public class MemcachedTestHelper @SuppressWarnings({"rawtypes", "unchecked"}) static GenericContainer memcached = new GenericContainer("memcached:" + System.getProperty("memcached.docker.version", "1.6.6")) - .withLogConsumer(new Slf4jLogConsumer(MEMCACHED_LOG)); + .withExposedPorts(11211) + .withLogConsumer(new Slf4jLogConsumer(MEMCACHED_LOG)); static { diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java index 6c5dd31c072..ef3f8188798 100644 --- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java @@ -47,12 +47,13 @@ public class MongoTestHelper public static final String DB_NAME = "HttpSessions"; public static final String COLLECTION_NAME = "testsessions"; + + private static final int MONGO_PORT = 27017; static GenericContainer mongo = new GenericContainer("mongo:" + System.getProperty("mongo.docker.version", "2.2.7")) .withLogConsumer(new Slf4jLogConsumer(MONGO_LOG)) - .waitingFor(new LogMessageWaitStrategy() - .withRegEx(".*waiting for connections.*")); + .withExposedPorts(MONGO_PORT); static MongoClient mongoClient; @@ -66,7 +67,7 @@ public class MongoTestHelper long start = System.currentTimeMillis(); mongo.start(); mongoHost = mongo.getHost(); - mongoPort = mongo.getMappedPort(27017); + mongoPort = mongo.getMappedPort(MONGO_PORT); LOG.info("Mongo container started for {}:{} - {}ms", mongoHost, mongoPort, System.currentTimeMillis() - start); mongoClient = new MongoClient(mongoHost, mongoPort); From 4d579d682cd17717e9b73535a75127a0d88f5151 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 27 Aug 2021 11:27:57 +1000 Subject: [PATCH 25/25] Issue #6406 - backport jetty-jaspi changes to 10.0.x Co-authored-by: Frode Carlsen Signed-off-by: Lachlan Roberts --- .../old_docs/security/authentication.adoc | 16 ++ .../main/asciidoc/operations-guide/index.adoc | 1 + .../operations-guide/jaspi/chapter.adoc | 68 +++++ jetty-jaspi/pom.xml | 12 - .../etc/jaspi/jaspi-authmoduleconfig.xml | 8 + .../main/config/etc/jaspi/jaspi-default.xml | 20 ++ .../src/main/config/etc/jaspi/jaspi-demo.xml | 48 ++++ .../jaspi-default-auth-config-factory.mod | 16 ++ .../src/main/config/modules/jaspi-demo.mod | 16 ++ jetty-jaspi/src/main/config/modules/jaspi.mod | 11 + jetty-jaspi/src/main/java/module-info.java | 3 +- .../jaspi/DefaultAuthConfigFactory.java | 249 ++++++++++++++++++ .../security/jaspi/JaspiAuthenticator.java | 117 ++++++-- .../jaspi/JaspiAuthenticatorFactory.java | 99 ++++--- .../jaspi/ServletCallbackHandler.java | 8 +- .../security/jaspi/SimpleAuthConfig.java | 7 +- .../jaspi/modules/BaseAuthModule.java | 21 +- .../BasicAuthenticationAuthModule.java} | 31 +-- .../security/jaspi/modules/UserInfo.java | 45 ---- .../provider/JaspiAuthConfigProvider.java | 132 ++++++++++ .../jaspi/provider/SimpleAuthConfig.java | 83 ++++++ .../provider/SimpleServerAuthContext.java | 58 ++++ ...lipse.jetty.security.Authenticator$Factory | 2 +- .../jaspi/DefaultAuthConfigFactoryTest.java | 115 ++++++++ .../jetty/security/jaspi/JaspiTest.java | 51 +++- jetty-jaspi/src/test/resources/jaspi.xml | 39 --- .../jetty/security/EmptyLoginService.java | 58 ++++ .../security/WrappedAuthConfiguration.java | 74 ++++++ jetty-webapp/src/main/java/module-info.java | 1 + .../jetty/webapp/JaspiConfiguration.java | 50 ++++ .../org.eclipse.jetty.webapp.Configuration | 1 + 31 files changed, 1234 insertions(+), 226 deletions(-) create mode 100644 documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaspi/chapter.adoc create mode 100644 jetty-jaspi/src/main/config/etc/jaspi/jaspi-authmoduleconfig.xml create mode 100644 jetty-jaspi/src/main/config/etc/jaspi/jaspi-default.xml create mode 100644 jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml create mode 100644 jetty-jaspi/src/main/config/modules/jaspi-default-auth-config-factory.mod create mode 100644 jetty-jaspi/src/main/config/modules/jaspi-demo.mod create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java rename jetty-jaspi/src/{test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java => main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthenticationAuthModule.java} (79%) delete mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/JaspiAuthConfigProvider.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleAuthConfig.java create mode 100644 jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleServerAuthContext.java create mode 100644 jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactoryTest.java delete mode 100644 jetty-jaspi/src/test/resources/jaspi.xml create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/EmptyLoginService.java create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/WrappedAuthConfiguration.java create mode 100644 jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaspiConfiguration.java diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc index ad1b1e6c49b..221458ac9a3 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc @@ -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. + diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc index aaf01516489..eb3e8507bab 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc @@ -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[] diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaspi/chapter.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaspi/chapter.adoc new file mode 100644 index 00000000000..a6585c957a6 --- /dev/null +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaspi/chapter.adoc @@ -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[] +---- \ No newline at end of file diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml index 7d68e58d295..b79011fc8cd 100644 --- a/jetty-jaspi/pom.xml +++ b/jetty-jaspi/pom.xml @@ -80,17 +80,5 @@ 1.2.0 test - - org.apache.geronimo.components - geronimo-jaspi - 2.0.0 - - - org.apache.geronimo.specs - geronimo-jaspic_1.0_spec - - - test - diff --git a/jetty-jaspi/src/main/config/etc/jaspi/jaspi-authmoduleconfig.xml b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-authmoduleconfig.xml new file mode 100644 index 00000000000..2bece156f6a --- /dev/null +++ b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-authmoduleconfig.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/jetty-jaspi/src/main/config/etc/jaspi/jaspi-default.xml b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-default.xml new file mode 100644 index 00000000000..1742c19568e --- /dev/null +++ b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-default.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + false + + diff --git a/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml new file mode 100644 index 00000000000..a1dde13d2f9 --- /dev/null +++ b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml @@ -0,0 +1,48 @@ + + + + + + + + + org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider + + + + + + + ServerAuthModule + org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule + + + + org.eclipse.jetty.security.jaspi.modules.RealmName + Test Realm + + + + + + HttpServlet + + + server /test + + + A simple provider using HTTP BASIC authentication. + + + \ No newline at end of file diff --git a/jetty-jaspi/src/main/config/modules/jaspi-default-auth-config-factory.mod b/jetty-jaspi/src/main/config/modules/jaspi-default-auth-config-factory.mod new file mode 100644 index 00000000000..70913c907be --- /dev/null +++ b/jetty-jaspi/src/main/config/modules/jaspi-default-auth-config-factory.mod @@ -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 diff --git a/jetty-jaspi/src/main/config/modules/jaspi-demo.mod b/jetty-jaspi/src/main/config/modules/jaspi-demo.mod new file mode 100644 index 00000000000..b0e7fd303e4 --- /dev/null +++ b/jetty-jaspi/src/main/config/modules/jaspi-demo.mod @@ -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 diff --git a/jetty-jaspi/src/main/config/modules/jaspi.mod b/jetty-jaspi/src/main/config/modules/jaspi.mod index d6573f7b8f4..76b2e81406e 100644 --- a/jetty-jaspi/src/main/config/modules/jaspi.mod +++ b/jetty-jaspi/src/main/config/modules/jaspi.mod @@ -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 + diff --git a/jetty-jaspi/src/main/java/module-info.java b/jetty-jaspi/src/main/java/module-info.java index 67117b0c027..a4cade9e4c5 100644 --- a/jetty-jaspi/src/main/java/module-info.java +++ b/jetty-jaspi/src/main/java/module-info.java @@ -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; diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java new file mode 100644 index 00000000000..5c42e15676c --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java @@ -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 _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 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 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 _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); + } + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java index 2a079a01e3a..655f13b8e2e 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java @@ -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 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; + } + } } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java index 7f42cff6c21..e8f3033a33e 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java @@ -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}. + *

+ * 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) + *

+ * To initialize either: + *

    + *
  • invoke {@link AuthConfigFactory#setFactory(AuthConfigFactory)}
  • + *
  • Alternatively: set {@link AuthConfigFactory#DEFAULT_FACTORY_SECURITY_PROPERTY}
  • + *
+ * + */ 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) diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java index 0eb60389f16..037a787889f 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java @@ -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 _callerPrincipals = new ThreadLocal(); - private final ThreadLocal _groupPrincipals = new ThreadLocal(); + private final ThreadLocal _callerPrincipals = new ThreadLocal<>(); + private final ThreadLocal _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); diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java index c2666943ab8..6c257b136fb 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java @@ -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; } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java index 720fd11dc43..71df3bfb588 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java @@ -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}); diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthenticationAuthModule.java similarity index 79% rename from jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java rename to jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthenticationAuthModule.java index 72472e00c4b..88660be93db 100644 --- a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthenticationAuthModule.java @@ -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()); } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java deleted file mode 100644 index 993389a48f9..00000000000 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java +++ /dev/null @@ -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; - } -} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/JaspiAuthConfigProvider.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/JaspiAuthConfigProvider.java new file mode 100644 index 00000000000..a65797a8cbf --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/JaspiAuthConfigProvider.java @@ -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; + +/** + *

A Jetty implementation of the {@link AuthConfigProvider} to allow registration of a {@link ServerAuthModule} + * directly without having to write a custom {@link AuthConfigProvider}.

+ *

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.

+ */ +@SuppressWarnings("rawtypes") +public class JaspiAuthConfigProvider implements AuthConfigProvider +{ + private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthConfigProvider.class); + private final Map providerProperties; + private final ServerAuthModule serverAuthModule; + + /** + *

Constructor with signature and implementation that's required by API.

+ *

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.

+ * + * @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); + } + } +} \ No newline at end of file diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleAuthConfig.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleAuthConfig.java new file mode 100644 index 00000000000..eff1689e556 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleAuthConfig.java @@ -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() + { + } +} \ No newline at end of file diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleServerAuthContext.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleServerAuthContext.java new file mode 100644 index 00000000000..61859eb22a4 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleServerAuthContext.java @@ -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); + } +} \ No newline at end of file diff --git a/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory b/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory index 2e24fa8ca2a..03cf3bbc1a2 100644 --- a/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory +++ b/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory @@ -1 +1 @@ -org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory \ No newline at end of file +org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactoryTest.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactoryTest.java new file mode 100644 index 00000000000..377637de480 --- /dev/null +++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactoryTest.java @@ -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 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; + } + } +} diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java index beffbc39455..12e8e827baf 100644 --- a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java +++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java @@ -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 _users = new HashMap<>(); protected Map> _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); diff --git a/jetty-jaspi/src/test/resources/jaspi.xml b/jetty-jaspi/src/test/resources/jaspi.xml deleted file mode 100644 index 1e31d2c996b..00000000000 --- a/jetty-jaspi/src/test/resources/jaspi.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - HTTP - server /ctx - description - - authenticationContextID1 - true - - - org.eclipse.jetty.security.jaspi.BasicAuthModule - - org.eclipse.jetty.security.jaspi.modules.RealmName=TestRealm - - - - - true - - - HTTP - server /other - description - - authenticationContextID2 - true - - - org.eclipse.jetty.security.jaspi.HttpHeaderAuthModule - - - - - - true - - diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/EmptyLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/EmptyLoginService.java new file mode 100644 index 00000000000..7d42576a94d --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/EmptyLoginService.java @@ -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) + { + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/WrappedAuthConfiguration.java b/jetty-security/src/main/java/org/eclipse/jetty/security/WrappedAuthConfiguration.java new file mode 100644 index 00000000000..f5c93381e95 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/WrappedAuthConfiguration.java @@ -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 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(); + } +} diff --git a/jetty-webapp/src/main/java/module-info.java b/jetty-webapp/src/main/java/module-info.java index 5da2ec13e74..5121f5eca99 100644 --- a/jetty-webapp/src/main/java/module-info.java +++ b/jetty-webapp/src/main/java/module-info.java @@ -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, diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaspiConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaspiConfiguration.java new file mode 100644 index 00000000000..cb9db15ab2e --- /dev/null +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaspiConfiguration.java @@ -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; + +/** + *

JASPI Configuration

+ *

This configuration configures the WebAppContext server/system classes to + * not be able to see the {@code javax.security.auth.message} package.

+ */ +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; + } + } +} diff --git a/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration b/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration index 9ec24ad9484..3b5d9e771de 100644 --- a/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration +++ b/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration @@ -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