From 9756387020d7bb62f4f213ad71355183c824e8ba Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 26 Aug 2024 11:13:24 +1000 Subject: [PATCH 1/3] move jetty-openid from jetty-core to jetty-integrations Signed-off-by: Lachlan Roberts --- jetty-core/pom.xml | 1 - {jetty-core => jetty-integrations}/jetty-openid/pom.xml | 2 +- .../jetty-openid/src/main/config/etc/jetty-openid.xml | 0 .../jetty-openid/src/main/config/modules/openid.mod | 0 .../config/modules/openid/jetty-openid-baseloginservice.xml | 0 .../jetty-openid/src/main/java/module-info.java | 0 .../main/java/org/eclipse/jetty/security/openid/JwtDecoder.java | 0 .../org/eclipse/jetty/security/openid/OpenIdAuthenticator.java | 0 .../jetty/security/openid/OpenIdAuthenticatorConfiguration.java | 0 .../jetty/security/openid/OpenIdAuthenticatorFactory.java | 0 .../org/eclipse/jetty/security/openid/OpenIdConfiguration.java | 0 .../org/eclipse/jetty/security/openid/OpenIdCredentials.java | 0 .../org/eclipse/jetty/security/openid/OpenIdLoginService.java | 0 .../org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java | 0 .../services/org.eclipse.jetty.security.Authenticator$Factory | 0 .../java/org/eclipse/jetty/security/openid/JwtDecoderTest.java | 0 .../test/java/org/eclipse/jetty/security/openid/JwtEncoder.java | 0 .../eclipse/jetty/security/openid/OpenIdAuthenticationTest.java | 0 .../eclipse/jetty/security/openid/OpenIdCredentialsTest.java | 0 .../java/org/eclipse/jetty/security/openid/OpenIdProvider.java | 0 .../org/eclipse/jetty/security/openid/OpenIdRealmNameTest.java | 0 .../jetty-openid/src/test/resources/jetty-logging.properties | 0 jetty-integrations/pom.xml | 1 + 23 files changed, 2 insertions(+), 2 deletions(-) rename {jetty-core => jetty-integrations}/jetty-openid/pom.xml (98%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/config/etc/jetty-openid.xml (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/config/modules/openid.mod (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/config/modules/openid/jetty-openid-baseloginservice.xml (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/module-info.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorConfiguration.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdRealmNameTest.java (100%) rename {jetty-core => jetty-integrations}/jetty-openid/src/test/resources/jetty-logging.properties (100%) diff --git a/jetty-core/pom.xml b/jetty-core/pom.xml index c3670faf6a5..e966acb0a00 100644 --- a/jetty-core/pom.xml +++ b/jetty-core/pom.xml @@ -28,7 +28,6 @@ jetty-jndi jetty-keystore jetty-maven - jetty-openid jetty-osgi jetty-plus jetty-proxy diff --git a/jetty-core/jetty-openid/pom.xml b/jetty-integrations/jetty-openid/pom.xml similarity index 98% rename from jetty-core/jetty-openid/pom.xml rename to jetty-integrations/jetty-openid/pom.xml index d550bb80bdf..5fcdea7b47f 100644 --- a/jetty-core/jetty-openid/pom.xml +++ b/jetty-integrations/jetty-openid/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.eclipse.jetty - jetty-core + jetty-integrations 12.1.0-SNAPSHOT jetty-openid diff --git a/jetty-core/jetty-openid/src/main/config/etc/jetty-openid.xml b/jetty-integrations/jetty-openid/src/main/config/etc/jetty-openid.xml similarity index 100% rename from jetty-core/jetty-openid/src/main/config/etc/jetty-openid.xml rename to jetty-integrations/jetty-openid/src/main/config/etc/jetty-openid.xml diff --git a/jetty-core/jetty-openid/src/main/config/modules/openid.mod b/jetty-integrations/jetty-openid/src/main/config/modules/openid.mod similarity index 100% rename from jetty-core/jetty-openid/src/main/config/modules/openid.mod rename to jetty-integrations/jetty-openid/src/main/config/modules/openid.mod diff --git a/jetty-core/jetty-openid/src/main/config/modules/openid/jetty-openid-baseloginservice.xml b/jetty-integrations/jetty-openid/src/main/config/modules/openid/jetty-openid-baseloginservice.xml similarity index 100% rename from jetty-core/jetty-openid/src/main/config/modules/openid/jetty-openid-baseloginservice.xml rename to jetty-integrations/jetty-openid/src/main/config/modules/openid/jetty-openid-baseloginservice.xml diff --git a/jetty-core/jetty-openid/src/main/java/module-info.java b/jetty-integrations/jetty-openid/src/main/java/module-info.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/module-info.java rename to jetty-integrations/jetty-openid/src/main/java/module-info.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/JwtDecoder.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorConfiguration.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorConfiguration.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorConfiguration.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorConfiguration.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java b/jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java similarity index 100% rename from jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java rename to jetty-integrations/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdUserPrincipal.java diff --git a/jetty-core/jetty-openid/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory b/jetty-integrations/jetty-openid/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory similarity index 100% rename from jetty-core/jetty-openid/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory rename to jetty-integrations/jetty-openid/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java similarity index 100% rename from jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java rename to jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java similarity index 100% rename from jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java rename to jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java similarity index 100% rename from jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java rename to jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java similarity index 100% rename from jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java rename to jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdCredentialsTest.java diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java similarity index 100% rename from jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java rename to jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java diff --git a/jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdRealmNameTest.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdRealmNameTest.java similarity index 100% rename from jetty-core/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdRealmNameTest.java rename to jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdRealmNameTest.java diff --git a/jetty-core/jetty-openid/src/test/resources/jetty-logging.properties b/jetty-integrations/jetty-openid/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-core/jetty-openid/src/test/resources/jetty-logging.properties rename to jetty-integrations/jetty-openid/src/test/resources/jetty-logging.properties diff --git a/jetty-integrations/pom.xml b/jetty-integrations/pom.xml index c1785dba714..c5f9aafc19a 100644 --- a/jetty-integrations/pom.xml +++ b/jetty-integrations/pom.xml @@ -13,6 +13,7 @@ jetty-ethereum + jetty-openid jetty-gcloud jetty-hazelcast jetty-infinispan From 063e839962fd382b48b41982415dc7721237ec42 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 26 Aug 2024 11:48:10 +1000 Subject: [PATCH 2/3] reduce duplication for OpenIdProvider testing class Signed-off-by: Lachlan Roberts --- jetty-ee8/jetty-ee8-openid/pom.xml | 5 + jetty-ee9/jetty-ee9-openid/pom.xml | 5 + .../jetty/ee9/security/openid/JwtEncoder.java | 53 --- .../openid/OpenIdAuthenticationTest.java | 1 + .../ee9/security/openid/OpenIdProvider.java | 402 ---------------- jetty-integrations/jetty-openid/pom.xml | 5 + .../jetty/security/openid/JwtDecoderTest.java | 1 + .../openid/OpenIdAuthenticationTest.java | 1 + .../jetty/security/openid/OpenIdProvider.java | 445 ------------------ pom.xml | 5 + tests/jetty-test-util/pom.xml | 29 ++ .../org/eclipse/jetty/tests}/JwtEncoder.java | 2 +- .../eclipse/jetty/tests/OpenIdProvider.java | 421 +++++++++++++++++ tests/pom.xml | 1 + .../test-ee10-distribution/pom.xml | 5 + .../ee10/tests/distribution/OpenIdTests.java | 2 +- .../tests/distribution/openid/JwtEncoder.java | 53 --- .../distribution/openid/OpenIdProvider.java | 402 ---------------- .../test-ee11-distribution/pom.xml | 5 + .../ee11/tests/distribution/OpenIdTests.java | 2 +- .../tests/distribution/openid/JwtEncoder.java | 53 --- .../distribution/openid/OpenIdProvider.java | 402 ---------------- .../test-ee9-distribution/pom.xml | 5 + .../ee9/tests/distribution/OpenIdTests.java | 2 +- .../tests/distribution/openid/JwtEncoder.java | 53 --- .../distribution/openid/OpenIdProvider.java | 402 ---------------- 26 files changed, 493 insertions(+), 2269 deletions(-) delete mode 100644 jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java delete mode 100644 jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java delete mode 100644 jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java create mode 100644 tests/jetty-test-util/pom.xml rename {jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid => tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests}/JwtEncoder.java (97%) create mode 100644 tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java delete mode 100644 tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/JwtEncoder.java delete mode 100644 tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/OpenIdProvider.java delete mode 100644 tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/JwtEncoder.java delete mode 100644 tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/OpenIdProvider.java delete mode 100644 tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/JwtEncoder.java delete mode 100644 tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/OpenIdProvider.java diff --git a/jetty-ee8/jetty-ee8-openid/pom.xml b/jetty-ee8/jetty-ee8-openid/pom.xml index a2b2bf3bb7f..9474870eb21 100644 --- a/jetty-ee8/jetty-ee8-openid/pom.xml +++ b/jetty-ee8/jetty-ee8-openid/pom.xml @@ -52,6 +52,11 @@ jetty-ee8-servlet test + + org.eclipse.jetty.tests + jetty-test-util + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-ee9/jetty-ee9-openid/pom.xml b/jetty-ee9/jetty-ee9-openid/pom.xml index 5ad8a155b62..8c92210726b 100644 --- a/jetty-ee9/jetty-ee9-openid/pom.xml +++ b/jetty-ee9/jetty-ee9-openid/pom.xml @@ -55,6 +55,11 @@ jetty-ee9-servlet test + + org.eclipse.jetty.tests + jetty-test-util + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java deleted file mode 100644 index 6de75b9e4ac..00000000000 --- a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/JwtEncoder.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee9.security.openid; - -import java.util.Base64; - -/** - * A basic JWT encoder for testing purposes. - */ -public class JwtEncoder -{ - private static final Base64.Encoder ENCODER = Base64.getUrlEncoder(); - private static final String DEFAULT_HEADER = "{\"INFO\": \"this is not used or checked in our implementation\"}"; - private static final String DEFAULT_SIGNATURE = "we do not validate signature as we use the authorization code flow"; - - public static String encode(String idToken) - { - return stripPadding(ENCODER.encodeToString(DEFAULT_HEADER.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(idToken.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(DEFAULT_SIGNATURE.getBytes())); - } - - private static String stripPadding(String paddedBase64) - { - return paddedBase64.split("=")[0]; - } - - /** - * Create a basic JWT for testing using argument supplied attributes. - */ - public static String createIdToken(String provider, String clientId, String subject, String name, long expiry) - { - return "{" + - "\"iss\": \"" + provider + "\"," + - "\"sub\": \"" + subject + "\"," + - "\"aud\": \"" + clientId + "\"," + - "\"exp\": " + expiry + "," + - "\"name\": \"" + name + "\"," + - "\"email\": \"" + name + "@example.com" + "\"" + - "}"; - } -} diff --git a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java index dfa5d06537b..b9893781184 100644 --- a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java +++ b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdAuthenticationTest.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.security.openid.OpenIdConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.session.FileSessionDataStoreFactory; +import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.security.Password; diff --git a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java b/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java deleted file mode 100644 index 21f1ff6b7cb..00000000000 --- a/jetty-ee9/jetty-ee9-openid/src/test/java/org/eclipse/jetty/ee9/security/openid/OpenIdProvider.java +++ /dev/null @@ -1,402 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee9.security.openid; - -import java.io.IOException; -import java.io.PrintWriter; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.ee9.servlet.ServletContextHandler; -import org.eclipse.jetty.ee9.servlet.ServletHolder; -import org.eclipse.jetty.security.openid.OpenIdConfiguration; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.statistic.CounterStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenIdProvider extends ContainerLifeCycle -{ - private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); - - private static final String CONFIG_PATH = "/.well-known/openid-configuration"; - private static final String AUTH_PATH = "/auth"; - private static final String TOKEN_PATH = "/token"; - private static final String END_SESSION_PATH = "/end_session"; - private final Map issuedAuthCodes = new HashMap<>(); - - protected final String clientId; - protected final String clientSecret; - protected final List redirectUris = new ArrayList<>(); - private final ServerConnector connector; - private final Server server; - private int port = 0; - private String provider; - private User preAuthedUser; - private final CounterStatistic loggedInUsers = new CounterStatistic(); - private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); - - public static void main(String[] args) throws Exception - { - String clientId = "CLIENT_ID123"; - String clientSecret = "PASSWORD123"; - int port = 5771; - String redirectUri = "http://localhost:8080/j_security_check"; - - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - openIdProvider.addRedirectUri(redirectUri); - openIdProvider.setPort(port); - openIdProvider.start(); - try - { - openIdProvider.join(); - } - finally - { - openIdProvider.stop(); - } - } - - public OpenIdProvider(String clientId, String clientSecret) - { - this.clientId = clientId; - this.clientSecret = clientSecret; - - server = new Server(); - connector = new ServerConnector(server); - server.addConnector(connector); - - ServletContextHandler contextHandler = new ServletContextHandler(); - contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ConfigServlet()), CONFIG_PATH); - contextHandler.addServlet(new ServletHolder(new AuthEndpoint()), AUTH_PATH); - contextHandler.addServlet(new ServletHolder(new TokenEndpoint()), TOKEN_PATH); - contextHandler.addServlet(new ServletHolder(new EndSessionEndpoint()), END_SESSION_PATH); - server.setHandler(contextHandler); - - addBean(server); - } - - public void setIdTokenDuration(long duration) - { - _idTokenDuration = duration; - } - - public long getIdTokenDuration() - { - return _idTokenDuration; - } - - public void join() throws InterruptedException - { - server.join(); - } - - public OpenIdConfiguration getOpenIdConfiguration() - { - String provider = getProvider(); - String authEndpoint = provider + AUTH_PATH; - String tokenEndpoint = provider + TOKEN_PATH; - return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null); - } - - public CounterStatistic getLoggedInUsers() - { - return loggedInUsers; - } - - @Override - protected void doStart() throws Exception - { - connector.setPort(port); - super.doStart(); - provider = "http://localhost:" + connector.getLocalPort(); - } - - public void setPort(int port) - { - if (isStarted()) - throw new IllegalStateException(); - this.port = port; - } - - public void setUser(User user) - { - this.preAuthedUser = user; - } - - public String getProvider() - { - if (!isStarted() && port == 0) - throw new IllegalStateException("Port of OpenIdProvider not configured"); - return provider; - } - - public void addRedirectUri(String uri) - { - redirectUris.add(uri); - } - - public class AuthEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - if (!clientId.equals(req.getParameter("client_id"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid client_id"); - return; - } - - String redirectUri = req.getParameter("redirect_uri"); - if (!redirectUris.contains(redirectUri)) - { - LOG.warn("invalid redirectUri {}", redirectUri); - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String scopeString = req.getParameter("scope"); - List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(scopeString.split(" ")); - if (!scopes.contains("openid")) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope"); - return; - } - - if (!"code".equals(req.getParameter("response_type"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - if (preAuthedUser == null) - { - PrintWriter writer = resp.getWriter(); - resp.setContentType("text/html"); - writer.println("

Login to OpenID Connect Provider

"); - writer.println("
"); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println("
"); - } - else - { - redirectUser(resp, preAuthedUser, redirectUri, state); - } - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String redirectUri = req.getParameter("redirectUri"); - if (!redirectUris.contains(redirectUri)) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - String username = req.getParameter("username"); - if (username == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no username"); - return; - } - - User user = new User(username); - redirectUser(resp, user, redirectUri, state); - } - - public void redirectUser(HttpServletResponse response, User user, String redirectUri, String state) throws IOException - { - String authCode = UUID.randomUUID().toString().replace("-", ""); - issuedAuthCodes.put(authCode, user); - - try - { - redirectUri += "?code=" + authCode + "&state=" + state; - response.sendRedirect(response.encodeRedirectURL(redirectUri)); - } - catch (Throwable t) - { - issuedAuthCodes.remove(authCode); - throw t; - } - } - } - - private class TokenEndpoint extends HttpServlet - { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - String code = req.getParameter("code"); - - if (!clientId.equals(req.getParameter("client_id")) || - !clientSecret.equals(req.getParameter("client_secret")) || - !redirectUris.contains(req.getParameter("redirect_uri")) || - !"authorization_code".equals(req.getParameter("grant_type")) || - code == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request"); - return; - } - - User user = issuedAuthCodes.remove(code); - if (user == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code"); - return; - } - - String accessToken = "ABCDEFG"; - long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); - String response = "{" + - "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + - "\"expires_in\": " + accessTokenDuration + "," + - "\"token_type\": \"Bearer\"" + - "}"; - - loggedInUsers.increment(); - resp.setContentType("text/plain"); - resp.getWriter().print(response); - } - } - - private class EndSessionEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - doPost(req, resp); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String idToken = req.getParameter("id_token_hint"); - if (idToken == null) - { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no id_token_hint"); - return; - } - - String logoutRedirect = req.getParameter("post_logout_redirect_uri"); - if (logoutRedirect == null) - { - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().println("logout success on end_session_endpoint"); - return; - } - - loggedInUsers.decrement(); - resp.setContentType("text/plain"); - resp.sendRedirect(logoutRedirect); - } - } - - private class ConfigServlet extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String discoveryDocument = "{" + - "\"issuer\": \"" + provider + "\"," + - "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + - "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + - "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + - "}"; - - resp.getWriter().write(discoveryDocument); - } - } - - public static class User - { - private final String subject; - private final String name; - - public User(String name) - { - this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); - } - - public User(String subject, String name) - { - this.subject = subject; - this.name = name; - } - - public String getName() - { - return name; - } - - public String getSubject() - { - return subject; - } - - public String getIdToken(String provider, String clientId, long duration) - { - long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); - return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); - } - - @Override - public boolean equals(Object obj) - { - if (!(obj instanceof User)) - return false; - return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); - } - - @Override - public int hashCode() - { - return Objects.hash(subject, name); - } - } -} diff --git a/jetty-integrations/jetty-openid/pom.xml b/jetty-integrations/jetty-openid/pom.xml index 5fcdea7b47f..f67543aa805 100644 --- a/jetty-integrations/jetty-openid/pom.xml +++ b/jetty-integrations/jetty-openid/pom.xml @@ -47,6 +47,11 @@ jetty-slf4j-impl test
+ + org.eclipse.jetty.tests + jetty-test-util + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java index b343b222e52..32a377d313c 100644 --- a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java +++ b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtDecoderTest.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.security.openid; import java.util.Map; import java.util.stream.Stream; +import org.eclipse.jetty.tests.JwtEncoder; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java index 79baf390aea..e122d9b8e5d 100644 --- a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java +++ b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.session.FileSessionDataStoreFactory; import org.eclipse.jetty.session.SessionHandler; +import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; diff --git a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java b/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java deleted file mode 100644 index 512237b8c6e..00000000000 --- a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java +++ /dev/null @@ -1,445 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.io.IOException; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.statistic.CounterStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenIdProvider extends ContainerLifeCycle -{ - private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); - - private static final String CONFIG_PATH = "/.well-known/openid-configuration"; - private static final String AUTH_PATH = "/auth"; - private static final String TOKEN_PATH = "/token"; - private static final String END_SESSION_PATH = "/end_session"; - private final Map issuedAuthCodes = new HashMap<>(); - - protected final String clientId; - protected final String clientSecret; - protected final List redirectUris = new ArrayList<>(); - private final ServerConnector connector; - private final Server server; - private int port = 0; - private String provider; - private User preAuthedUser; - private final CounterStatistic loggedInUsers = new CounterStatistic(); - private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); - - public static void main(String[] args) throws Exception - { - String clientId = "CLIENT_ID123"; - String clientSecret = "PASSWORD123"; - int port = 5771; - String redirectUri = "http://localhost:8080/j_security_check"; - - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - openIdProvider.addRedirectUri(redirectUri); - openIdProvider.setPort(port); - openIdProvider.start(); - try - { - openIdProvider.join(); - } - finally - { - openIdProvider.stop(); - } - } - - public OpenIdProvider(String clientId, String clientSecret) - { - this.clientId = clientId; - this.clientSecret = clientSecret; - - server = new Server(); - connector = new ServerConnector(server); - server.addConnector(connector); - - ContextHandlerCollection contexts = new ContextHandlerCollection(); - contexts.addHandler(new ConfigServlet(CONFIG_PATH)); - contexts.addHandler(new AuthEndpoint(AUTH_PATH)); - contexts.addHandler(new TokenEndpoint(TOKEN_PATH)); - contexts.addHandler(new EndSessionEndpoint(END_SESSION_PATH)); - server.setHandler(contexts); - - addBean(server); - } - - public void setIdTokenDuration(long duration) - { - _idTokenDuration = duration; - } - - public long getIdTokenDuration() - { - return _idTokenDuration; - } - - public void join() throws InterruptedException - { - server.join(); - } - - public OpenIdConfiguration getOpenIdConfiguration() - { - String provider = getProvider(); - String authEndpoint = provider + AUTH_PATH; - String tokenEndpoint = provider + TOKEN_PATH; - return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null); - } - - public CounterStatistic getLoggedInUsers() - { - return loggedInUsers; - } - - @Override - protected void doStart() throws Exception - { - connector.setPort(port); - super.doStart(); - provider = "http://localhost:" + connector.getLocalPort(); - } - - public void setPort(int port) - { - if (isStarted()) - throw new IllegalStateException(); - this.port = port; - } - - public void setUser(User user) - { - this.preAuthedUser = user; - } - - public String getProvider() - { - if (!isStarted() && port == 0) - throw new IllegalStateException("Port of OpenIdProvider not configured"); - return provider; - } - - public void addRedirectUri(String uri) - { - redirectUris.add(uri); - } - - public class AuthEndpoint extends ContextHandler - { - public AuthEndpoint(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - switch (request.getMethod()) - { - case "GET": - doGet(request, response, callback); - break; - case "POST": - doPost(request, response, callback); - break; - default: - throw new BadMessageException("Unsupported HTTP Method"); - } - - return true; - } - - protected void doGet(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - if (!clientId.equals(parameters.getValue("client_id"))) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid client_id"); - return; - } - - String redirectUri = parameters.getValue("redirect_uri"); - if (!redirectUris.contains(redirectUri)) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); - return; - } - - String scopeString = parameters.getValue("scope"); - List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(StringUtil.csvSplit(scopeString)); - if (!scopes.contains("openid")) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no openid scope"); - return; - } - - if (!"code".equals(parameters.getValue("response_type"))) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "response_type must be code"); - return; - } - - String state = parameters.getValue("state"); - if (state == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); - return; - } - - if (preAuthedUser == null) - { - response.getHeaders().add(HttpHeader.CONTENT_TYPE, "text/html"); - - String content = - "

Login to OpenID Connect Provider

" + - "
" + - "" + - "" + - "" + - "" + - "
"; - response.write(true, BufferUtil.toBuffer(content), callback); - } - else - { - redirectUser(request, response, callback, preAuthedUser, redirectUri, state); - } - } - - protected void doPost(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - - String redirectUri = parameters.getValue("redirectUri"); - if (!redirectUris.contains(redirectUri)) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); - return; - } - - String state = parameters.getValue("state"); - if (state == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); - return; - } - - String username = parameters.getValue("username"); - if (username == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no username"); - return; - } - - User user = new User(username); - redirectUser(request, response, callback, user, redirectUri, state); - } - - public void redirectUser(Request request, Response response, Callback callback, User user, String redirectUri, String state) throws IOException - { - String authCode = UUID.randomUUID().toString().replace("-", ""); - issuedAuthCodes.put(authCode, user); - - try - { - redirectUri += "?code=" + authCode + "&state=" + state; - Response.sendRedirect(request, response, callback, redirectUri); - } - catch (Throwable t) - { - issuedAuthCodes.remove(authCode); - throw t; - } - } - } - - private class TokenEndpoint extends ContextHandler - { - public TokenEndpoint(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - - String code = parameters.getValue("code"); - - if (!clientId.equals(parameters.getValue("client_id")) || - !clientSecret.equals(parameters.getValue("client_secret")) || - !redirectUris.contains(parameters.getValue("redirect_uri")) || - !"authorization_code".equals(parameters.getValue("grant_type")) || - code == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "bad auth request"); - return true; - } - - User user = issuedAuthCodes.remove(code); - if (user == null) - { - Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid auth code"); - return true; - } - - String accessToken = "ABCDEFG"; - long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); - String content = "{" + - "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + - "\"expires_in\": " + accessTokenDuration + "," + - "\"token_type\": \"Bearer\"" + - "}"; - - loggedInUsers.increment(); - response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain"); - response.write(true, BufferUtil.toBuffer(content), callback); - return true; - } - } - - private class EndSessionEndpoint extends ContextHandler - { - public EndSessionEndpoint(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - Fields parameters = Request.getParameters(request); - - String idToken = parameters.getValue("id_token_hint"); - if (idToken == null) - { - Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400, "no id_token_hint"); - return true; - } - - String logoutRedirect = parameters.getValue("post_logout_redirect_uri"); - if (logoutRedirect == null) - { - response.setStatus(HttpStatus.OK_200); - response.write(true, BufferUtil.toBuffer("logout success on end_session_endpoint"), callback); - return true; - } - - loggedInUsers.decrement(); - response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain"); - Response.sendRedirect(request, response, callback, logoutRedirect); - return true; - } - } - - private class ConfigServlet extends ContextHandler - { - public ConfigServlet(String contextPath) - { - super(contextPath); - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - String discoveryDocument = "{" + - "\"issuer\": \"" + provider + "\"," + - "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + - "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + - "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + - "}"; - - response.write(true, BufferUtil.toBuffer(discoveryDocument), callback); - return true; - } - } - - public static class User - { - private final String subject; - private final String name; - - public User(String name) - { - this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); - } - - public User(String subject, String name) - { - this.subject = subject; - this.name = name; - } - - public String getName() - { - return name; - } - - public String getSubject() - { - return subject; - } - - public String getIdToken(String provider, String clientId, long duration) - { - long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); - return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); - } - - @Override - public boolean equals(Object obj) - { - if (!(obj instanceof User)) - return false; - return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); - } - - @Override - public int hashCode() - { - return Objects.hash(subject, name); - } - } -} diff --git a/pom.xml b/pom.xml index 69bc91da4f7..68311850742 100644 --- a/pom.xml +++ b/pom.xml @@ -1060,6 +1060,11 @@ jetty-test-session-common ${project.version}
+ + org.eclipse.jetty.tests + jetty-test-util + ${project.version} + org.eclipse.jetty.tests jetty-testers diff --git a/tests/jetty-test-util/pom.xml b/tests/jetty-test-util/pom.xml new file mode 100644 index 00000000000..cab19941710 --- /dev/null +++ b/tests/jetty-test-util/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + org.eclipse.jetty.tests + tests + 12.1.0-SNAPSHOT + + jetty-test-util + jar + Tests :: Test Utilities + + + ${project.groupId}.testers + + + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty + jetty-util + + + + diff --git a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java b/tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java similarity index 97% rename from jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java rename to tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java index 928a51fd822..d4611f07a77 100644 --- a/jetty-integrations/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java +++ b/tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.security.openid; +package org.eclipse.jetty.tests; import java.util.Base64; diff --git a/tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java b/tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java new file mode 100644 index 00000000000..aba79c2a0c3 --- /dev/null +++ b/tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java @@ -0,0 +1,421 @@ +// +// ======================================================================== +// Copyright (c) 1995 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.tests; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.statistic.CounterStatistic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OpenIdProvider extends ContainerLifeCycle +{ + private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); + + private static final String CONFIG_PATH = "/.well-known/openid-configuration"; + private static final String AUTH_PATH = "/auth"; + private static final String TOKEN_PATH = "/token"; + private static final String END_SESSION_PATH = "/end_session"; + private final Map issuedAuthCodes = new HashMap<>(); + + protected final String clientId; + protected final String clientSecret; + protected final List redirectUris = new ArrayList<>(); + private final ServerConnector connector; + private final Server server; + private int port = 0; + private String provider; + private User preAuthedUser; + private final CounterStatistic loggedInUsers = new CounterStatistic(); + private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); + + public static void main(String[] args) throws Exception + { + String clientId = "CLIENT_ID123"; + String clientSecret = "PASSWORD123"; + int port = 5771; + String redirectUri = "http://localhost:8080/j_security_check"; + + OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); + openIdProvider.addRedirectUri(redirectUri); + openIdProvider.setPort(port); + openIdProvider.start(); + try + { + openIdProvider.join(); + } + finally + { + openIdProvider.stop(); + } + } + + public OpenIdProvider() + { + this("clientId" + StringUtil.randomAlphaNumeric(4), StringUtil.randomAlphaNumeric(10)); + } + + public OpenIdProvider(String clientId, String clientSecret) + { + this.clientId = clientId; + this.clientSecret = clientSecret; + + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + server.setHandler(new OpenIdProviderHandler()); + addBean(server); + } + + public String getClientId() + { + return clientId; + } + + public String getClientSecret() + { + return clientSecret; + } + + public void setIdTokenDuration(long duration) + { + _idTokenDuration = duration; + } + + public long getIdTokenDuration() + { + return _idTokenDuration; + } + + public void join() throws InterruptedException + { + server.join(); + } + + public CounterStatistic getLoggedInUsers() + { + return loggedInUsers; + } + + @Override + protected void doStart() throws Exception + { + connector.setPort(port); + super.doStart(); + provider = "http://localhost:" + connector.getLocalPort(); + } + + public void setPort(int port) + { + if (isStarted()) + throw new IllegalStateException(); + this.port = port; + } + + public void setUser(User user) + { + this.preAuthedUser = user; + } + + public String getProvider() + { + if (!isStarted() && port == 0) + throw new IllegalStateException("Port of OpenIdProvider not configured"); + return provider; + } + + public void addRedirectUri(String uri) + { + redirectUris.add(uri); + } + + public class OpenIdProviderHandler extends Handler.Abstract + { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception + { + String pathInContext = Request.getPathInContext(request); + switch (pathInContext) + { + case CONFIG_PATH -> doGetConfigServlet(request, response, callback); + case AUTH_PATH -> doAuthEndpoint(request, response, callback); + case TOKEN_PATH -> doTokenEndpoint(request, response, callback); + case END_SESSION_PATH -> doEndSessionEndpoint(request, response, callback); + default -> Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404); + } + + return true; + } + } + + protected void doAuthEndpoint(Request request, Response response, Callback callback) throws Exception + { + String method = request.getMethod(); + switch (method) + { + case "GET" -> doGetAuthEndpoint(request, response, callback); + case "POST" -> doPostAuthEndpoint(request, response, callback); + default -> throw new BadMessageException("Unsupported HTTP method: " + method); + } + } + + protected void doGetAuthEndpoint(Request request, Response response, Callback callback) throws Exception + { + Fields parameters = Request.getParameters(request); + + if (!clientId.equals(parameters.getValue("client_id"))) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid client_id"); + return; + } + + String redirectUri = parameters.getValue("redirect_uri"); + if (!redirectUris.contains(redirectUri)) + { + LOG.warn("invalid redirectUri {}", redirectUri); + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); + return; + } + + String scopeString = parameters.getValue("scope"); + List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(scopeString.split(" ")); + if (!scopes.contains("openid")) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no openid scope"); + return; + } + + if (!"code".equals(parameters.getValue("response_type"))) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "response_type must be code"); + return; + } + + String state = parameters.getValue("state"); + if (state == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); + return; + } + + if (preAuthedUser == null) + { + String responseContent = String.format(""" +

Login to OpenID Connect Provider

+
+ + + + +
+ """, AUTH_PATH, redirectUri, state); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/html"); + response.write(true, BufferUtil.toBuffer(responseContent), callback); + } + else + { + redirectUser(request, response, callback, preAuthedUser, redirectUri, state); + } + } + + protected void doPostAuthEndpoint(Request request, Response response, Callback callback) throws Exception + { + Fields parameters = Request.getParameters(request); + String redirectUri = parameters.getValue("redirectUri"); + if (!redirectUris.contains(redirectUri)) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid redirect_uri"); + return; + } + + String state = parameters.getValue("state"); + if (state == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no state param"); + return; + } + + String username = parameters.getValue("username"); + if (username == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "no username"); + return; + } + + User user = new User(username); + redirectUser(request, response, callback, user, redirectUri, state); + } + + public void redirectUser(Request request, Response response, Callback callback, User user, String redirectUri, String state) throws IOException + { + String authCode = UUID.randomUUID().toString().replace("-", ""); + issuedAuthCodes.put(authCode, user); + + try + { + redirectUri += "?code=" + authCode + "&state=" + state; + Response.sendRedirect(request, response, callback, redirectUri); + } + catch (Throwable t) + { + issuedAuthCodes.remove(authCode); + throw t; + } + } + + protected void doTokenEndpoint(Request request, Response response, Callback callback) throws Exception + { + if (!HttpMethod.POST.is(request.getMethod())) + throw new BadMessageException("Unsupported HTTP method for token Endpoint: " + request.getMethod()); + + Fields parameters = Request.getParameters(request); + String code = parameters.getValue("code"); + + if (!clientId.equals(parameters.getValue("client_id")) || + !clientSecret.equals(parameters.getValue("client_secret")) || + !redirectUris.contains(parameters.getValue("redirect_uri")) || + !"authorization_code".equals(parameters.getValue("grant_type")) || + code == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "bad auth request"); + return; + } + + User user = issuedAuthCodes.remove(code); + if (user == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "invalid auth code"); + return; + } + + String accessToken = "ABCDEFG"; + long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); + String responseContent = "{" + + "\"access_token\": \"" + accessToken + "\"," + + "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + + "\"expires_in\": " + accessTokenDuration + "," + + "\"token_type\": \"Bearer\"" + + "}"; + + loggedInUsers.increment(); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain"); + response.write(true, BufferUtil.toBuffer(responseContent), callback); + } + + protected void doEndSessionEndpoint(Request request, Response response, Callback callback) throws Exception + { + Fields parameters = Request.getParameters(request); + String idToken = parameters.getValue("id_token_hint"); + if (idToken == null) + { + Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400, "no id_token_hint"); + return; + } + + String logoutRedirect = parameters.getValue("post_logout_redirect_uri"); + if (logoutRedirect == null) + { + response.setStatus(HttpStatus.OK_200); + response.write(true, BufferUtil.toBuffer("logout success on end_session_endpoint"), callback); + return; + } + + loggedInUsers.decrement(); + Response.sendRedirect(request, response, callback, logoutRedirect); + } + + protected void doGetConfigServlet(Request request, Response response, Callback callback) throws IOException + { + String discoveryDocument = "{" + + "\"issuer\": \"" + provider + "\"," + + "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + + "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + + "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + + "}"; + + response.write(true, BufferUtil.toBuffer(discoveryDocument), callback); + } + + public static class User + { + private final String subject; + private final String name; + + public User(String name) + { + this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); + } + + public User(String subject, String name) + { + this.subject = subject; + this.name = name; + } + + public String getName() + { + return name; + } + + public String getSubject() + { + return subject; + } + + public String getIdToken(String provider, String clientId, long duration) + { + long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); + return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); + } + + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof User)) + return false; + return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); + } + + @Override + public int hashCode() + { + return Objects.hash(subject, name); + } + } +} diff --git a/tests/pom.xml b/tests/pom.xml index 0ec5773e2db..b916216f008 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -15,6 +15,7 @@ jetty-jmh jetty-test-multipart jetty-test-session-common + jetty-test-util test-cross-context-dispatch test-distribution test-integration diff --git a/tests/test-distribution/test-ee10-distribution/pom.xml b/tests/test-distribution/test-ee10-distribution/pom.xml index dd0d2528018..37669d365f2 100644 --- a/tests/test-distribution/test-ee10-distribution/pom.xml +++ b/tests/test-distribution/test-ee10-distribution/pom.xml @@ -60,6 +60,11 @@ war test
+ + org.eclipse.jetty.tests + jetty-test-util + test + org.eclipse.jetty.tests jetty-testers diff --git a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java index 0e06385efd3..d8b3cea3e5d 100644 --- a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java +++ b/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java @@ -17,8 +17,8 @@ import java.nio.file.Path; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.ee10.tests.distribution.openid.OpenIdProvider; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; import org.eclipse.jetty.tests.testers.JettyHomeTester; import org.eclipse.jetty.tests.testers.Tester; diff --git a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/JwtEncoder.java b/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/JwtEncoder.java deleted file mode 100644 index 91c5f9709e0..00000000000 --- a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/JwtEncoder.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee10.tests.distribution.openid; - -import java.util.Base64; - -/** - * A basic JWT encoder for testing purposes. - */ -public class JwtEncoder -{ - private static final Base64.Encoder ENCODER = Base64.getUrlEncoder(); - private static final String DEFAULT_HEADER = "{\"INFO\": \"this is not used or checked in our implementation\"}"; - private static final String DEFAULT_SIGNATURE = "we do not validate signature as we use the authorization code flow"; - - public static String encode(String idToken) - { - return stripPadding(ENCODER.encodeToString(DEFAULT_HEADER.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(idToken.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(DEFAULT_SIGNATURE.getBytes())); - } - - private static String stripPadding(String paddedBase64) - { - return paddedBase64.split("=")[0]; - } - - /** - * Create a basic JWT for testing using argument supplied attributes. - */ - public static String createIdToken(String provider, String clientId, String subject, String name, long expiry) - { - return "{" + - "\"iss\": \"" + provider + "\"," + - "\"sub\": \"" + subject + "\"," + - "\"aud\": \"" + clientId + "\"," + - "\"exp\": " + expiry + "," + - "\"name\": \"" + name + "\"," + - "\"email\": \"" + name + "@example.com" + "\"" + - "}"; - } -} diff --git a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/OpenIdProvider.java b/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/OpenIdProvider.java deleted file mode 100644 index f1e2f27324b..00000000000 --- a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/openid/OpenIdProvider.java +++ /dev/null @@ -1,402 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee10.tests.distribution.openid; - -import java.io.IOException; -import java.io.PrintWriter; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; -import org.eclipse.jetty.ee10.servlet.ServletHolder; -import org.eclipse.jetty.security.openid.OpenIdConfiguration; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.statistic.CounterStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenIdProvider extends ContainerLifeCycle -{ - private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); - - private static final String CONFIG_PATH = "/.well-known/openid-configuration"; - private static final String AUTH_PATH = "/auth"; - private static final String TOKEN_PATH = "/token"; - private static final String END_SESSION_PATH = "/end_session"; - private final Map issuedAuthCodes = new HashMap<>(); - - protected final String clientId; - protected final String clientSecret; - protected final List redirectUris = new ArrayList<>(); - private final ServerConnector connector; - private final Server server; - private int port = 0; - private String provider; - private User preAuthedUser; - private final CounterStatistic loggedInUsers = new CounterStatistic(); - private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); - - public static void main(String[] args) throws Exception - { - String clientId = "CLIENT_ID123"; - String clientSecret = "PASSWORD123"; - int port = 5771; - String redirectUri = "http://localhost:8080/j_security_check"; - - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - openIdProvider.addRedirectUri(redirectUri); - openIdProvider.setPort(port); - openIdProvider.start(); - try - { - openIdProvider.join(); - } - finally - { - openIdProvider.stop(); - } - } - - public OpenIdProvider(String clientId, String clientSecret) - { - this.clientId = clientId; - this.clientSecret = clientSecret; - - server = new Server(); - connector = new ServerConnector(server); - server.addConnector(connector); - - ServletContextHandler contextHandler = new ServletContextHandler(); - contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ConfigServlet()), CONFIG_PATH); - contextHandler.addServlet(new ServletHolder(new AuthEndpoint()), AUTH_PATH); - contextHandler.addServlet(new ServletHolder(new TokenEndpoint()), TOKEN_PATH); - contextHandler.addServlet(new ServletHolder(new EndSessionEndpoint()), END_SESSION_PATH); - server.setHandler(contextHandler); - - addBean(server); - } - - public void setIdTokenDuration(long duration) - { - _idTokenDuration = duration; - } - - public long getIdTokenDuration() - { - return _idTokenDuration; - } - - public void join() throws InterruptedException - { - server.join(); - } - - public OpenIdConfiguration getOpenIdConfiguration() - { - String provider = getProvider(); - String authEndpoint = provider + AUTH_PATH; - String tokenEndpoint = provider + TOKEN_PATH; - return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null); - } - - public CounterStatistic getLoggedInUsers() - { - return loggedInUsers; - } - - @Override - protected void doStart() throws Exception - { - connector.setPort(port); - super.doStart(); - provider = "http://localhost:" + connector.getLocalPort(); - } - - public void setPort(int port) - { - if (isStarted()) - throw new IllegalStateException(); - this.port = port; - } - - public void setUser(User user) - { - this.preAuthedUser = user; - } - - public String getProvider() - { - if (!isStarted() && port == 0) - throw new IllegalStateException("Port of OpenIdProvider not configured"); - return provider; - } - - public void addRedirectUri(String uri) - { - redirectUris.add(uri); - } - - public class AuthEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - if (!clientId.equals(req.getParameter("client_id"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid client_id"); - return; - } - - String redirectUri = req.getParameter("redirect_uri"); - if (!redirectUris.contains(redirectUri)) - { - LOG.warn("invalid redirectUri {}", redirectUri); - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String scopeString = req.getParameter("scope"); - List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(scopeString.split(" ")); - if (!scopes.contains("openid")) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope"); - return; - } - - if (!"code".equals(req.getParameter("response_type"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - if (preAuthedUser == null) - { - PrintWriter writer = resp.getWriter(); - resp.setContentType("text/html"); - writer.println("

Login to OpenID Connect Provider

"); - writer.println("
"); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println("
"); - } - else - { - redirectUser(resp, preAuthedUser, redirectUri, state); - } - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String redirectUri = req.getParameter("redirectUri"); - if (!redirectUris.contains(redirectUri)) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - String username = req.getParameter("username"); - if (username == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no username"); - return; - } - - User user = new User(username); - redirectUser(resp, user, redirectUri, state); - } - - public void redirectUser(HttpServletResponse response, User user, String redirectUri, String state) throws IOException - { - String authCode = UUID.randomUUID().toString().replace("-", ""); - issuedAuthCodes.put(authCode, user); - - try - { - redirectUri += "?code=" + authCode + "&state=" + state; - response.sendRedirect(response.encodeRedirectURL(redirectUri)); - } - catch (Throwable t) - { - issuedAuthCodes.remove(authCode); - throw t; - } - } - } - - private class TokenEndpoint extends HttpServlet - { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - String code = req.getParameter("code"); - - if (!clientId.equals(req.getParameter("client_id")) || - !clientSecret.equals(req.getParameter("client_secret")) || - !redirectUris.contains(req.getParameter("redirect_uri")) || - !"authorization_code".equals(req.getParameter("grant_type")) || - code == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request"); - return; - } - - User user = issuedAuthCodes.remove(code); - if (user == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code"); - return; - } - - String accessToken = "ABCDEFG"; - long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); - String response = "{" + - "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + - "\"expires_in\": " + accessTokenDuration + "," + - "\"token_type\": \"Bearer\"" + - "}"; - - loggedInUsers.increment(); - resp.setContentType("text/plain"); - resp.getWriter().print(response); - } - } - - private class EndSessionEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - doPost(req, resp); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String idToken = req.getParameter("id_token_hint"); - if (idToken == null) - { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no id_token_hint"); - return; - } - - String logoutRedirect = req.getParameter("post_logout_redirect_uri"); - if (logoutRedirect == null) - { - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().println("logout success on end_session_endpoint"); - return; - } - - loggedInUsers.decrement(); - resp.setContentType("text/plain"); - resp.sendRedirect(logoutRedirect); - } - } - - private class ConfigServlet extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String discoveryDocument = "{" + - "\"issuer\": \"" + provider + "\"," + - "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + - "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + - "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + - "}"; - - resp.getWriter().write(discoveryDocument); - } - } - - public static class User - { - private final String subject; - private final String name; - - public User(String name) - { - this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); - } - - public User(String subject, String name) - { - this.subject = subject; - this.name = name; - } - - public String getName() - { - return name; - } - - public String getSubject() - { - return subject; - } - - public String getIdToken(String provider, String clientId, long duration) - { - long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); - return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); - } - - @Override - public boolean equals(Object obj) - { - if (!(obj instanceof User)) - return false; - return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); - } - - @Override - public int hashCode() - { - return Objects.hash(subject, name); - } - } -} diff --git a/tests/test-distribution/test-ee11-distribution/pom.xml b/tests/test-distribution/test-ee11-distribution/pom.xml index c315f8e994e..a00a3c20bf3 100644 --- a/tests/test-distribution/test-ee11-distribution/pom.xml +++ b/tests/test-distribution/test-ee11-distribution/pom.xml @@ -55,6 +55,11 @@ war test
+ + org.eclipse.jetty.tests + jetty-test-util + test + org.eclipse.jetty.tests jetty-testers diff --git a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/OpenIdTests.java index 67de446dd33..216003d492d 100644 --- a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/OpenIdTests.java +++ b/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/OpenIdTests.java @@ -17,8 +17,8 @@ import java.nio.file.Path; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.ee11.tests.distribution.openid.OpenIdProvider; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; import org.eclipse.jetty.tests.testers.JettyHomeTester; import org.eclipse.jetty.tests.testers.Tester; diff --git a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/JwtEncoder.java b/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/JwtEncoder.java deleted file mode 100644 index 6b47bfaed0f..00000000000 --- a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/JwtEncoder.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee11.tests.distribution.openid; - -import java.util.Base64; - -/** - * A basic JWT encoder for testing purposes. - */ -public class JwtEncoder -{ - private static final Base64.Encoder ENCODER = Base64.getUrlEncoder(); - private static final String DEFAULT_HEADER = "{\"INFO\": \"this is not used or checked in our implementation\"}"; - private static final String DEFAULT_SIGNATURE = "we do not validate signature as we use the authorization code flow"; - - public static String encode(String idToken) - { - return stripPadding(ENCODER.encodeToString(DEFAULT_HEADER.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(idToken.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(DEFAULT_SIGNATURE.getBytes())); - } - - private static String stripPadding(String paddedBase64) - { - return paddedBase64.split("=")[0]; - } - - /** - * Create a basic JWT for testing using argument supplied attributes. - */ - public static String createIdToken(String provider, String clientId, String subject, String name, long expiry) - { - return "{" + - "\"iss\": \"" + provider + "\"," + - "\"sub\": \"" + subject + "\"," + - "\"aud\": \"" + clientId + "\"," + - "\"exp\": " + expiry + "," + - "\"name\": \"" + name + "\"," + - "\"email\": \"" + name + "@example.com" + "\"" + - "}"; - } -} diff --git a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/OpenIdProvider.java b/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/OpenIdProvider.java deleted file mode 100644 index 7df27ce1405..00000000000 --- a/tests/test-distribution/test-ee11-distribution/src/test/java/org/eclipse/jetty/ee11/tests/distribution/openid/OpenIdProvider.java +++ /dev/null @@ -1,402 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee11.tests.distribution.openid; - -import java.io.IOException; -import java.io.PrintWriter; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.ee11.servlet.ServletContextHandler; -import org.eclipse.jetty.ee11.servlet.ServletHolder; -import org.eclipse.jetty.security.openid.OpenIdConfiguration; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.statistic.CounterStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenIdProvider extends ContainerLifeCycle -{ - private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); - - private static final String CONFIG_PATH = "/.well-known/openid-configuration"; - private static final String AUTH_PATH = "/auth"; - private static final String TOKEN_PATH = "/token"; - private static final String END_SESSION_PATH = "/end_session"; - private final Map issuedAuthCodes = new HashMap<>(); - - protected final String clientId; - protected final String clientSecret; - protected final List redirectUris = new ArrayList<>(); - private final ServerConnector connector; - private final Server server; - private int port = 0; - private String provider; - private User preAuthedUser; - private final CounterStatistic loggedInUsers = new CounterStatistic(); - private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); - - public static void main(String[] args) throws Exception - { - String clientId = "CLIENT_ID123"; - String clientSecret = "PASSWORD123"; - int port = 5771; - String redirectUri = "http://localhost:8080/j_security_check"; - - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - openIdProvider.addRedirectUri(redirectUri); - openIdProvider.setPort(port); - openIdProvider.start(); - try - { - openIdProvider.join(); - } - finally - { - openIdProvider.stop(); - } - } - - public OpenIdProvider(String clientId, String clientSecret) - { - this.clientId = clientId; - this.clientSecret = clientSecret; - - server = new Server(); - connector = new ServerConnector(server); - server.addConnector(connector); - - ServletContextHandler contextHandler = new ServletContextHandler(); - contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ConfigServlet()), CONFIG_PATH); - contextHandler.addServlet(new ServletHolder(new AuthEndpoint()), AUTH_PATH); - contextHandler.addServlet(new ServletHolder(new TokenEndpoint()), TOKEN_PATH); - contextHandler.addServlet(new ServletHolder(new EndSessionEndpoint()), END_SESSION_PATH); - server.setHandler(contextHandler); - - addBean(server); - } - - public void setIdTokenDuration(long duration) - { - _idTokenDuration = duration; - } - - public long getIdTokenDuration() - { - return _idTokenDuration; - } - - public void join() throws InterruptedException - { - server.join(); - } - - public OpenIdConfiguration getOpenIdConfiguration() - { - String provider = getProvider(); - String authEndpoint = provider + AUTH_PATH; - String tokenEndpoint = provider + TOKEN_PATH; - return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null); - } - - public CounterStatistic getLoggedInUsers() - { - return loggedInUsers; - } - - @Override - protected void doStart() throws Exception - { - connector.setPort(port); - super.doStart(); - provider = "http://localhost:" + connector.getLocalPort(); - } - - public void setPort(int port) - { - if (isStarted()) - throw new IllegalStateException(); - this.port = port; - } - - public void setUser(User user) - { - this.preAuthedUser = user; - } - - public String getProvider() - { - if (!isStarted() && port == 0) - throw new IllegalStateException("Port of OpenIdProvider not configured"); - return provider; - } - - public void addRedirectUri(String uri) - { - redirectUris.add(uri); - } - - public class AuthEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - if (!clientId.equals(req.getParameter("client_id"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid client_id"); - return; - } - - String redirectUri = req.getParameter("redirect_uri"); - if (!redirectUris.contains(redirectUri)) - { - LOG.warn("invalid redirectUri {}", redirectUri); - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String scopeString = req.getParameter("scope"); - List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(scopeString.split(" ")); - if (!scopes.contains("openid")) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope"); - return; - } - - if (!"code".equals(req.getParameter("response_type"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - if (preAuthedUser == null) - { - PrintWriter writer = resp.getWriter(); - resp.setContentType("text/html"); - writer.println("

Login to OpenID Connect Provider

"); - writer.println("
"); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println("
"); - } - else - { - redirectUser(resp, preAuthedUser, redirectUri, state); - } - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String redirectUri = req.getParameter("redirectUri"); - if (!redirectUris.contains(redirectUri)) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - String username = req.getParameter("username"); - if (username == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no username"); - return; - } - - User user = new User(username); - redirectUser(resp, user, redirectUri, state); - } - - public void redirectUser(HttpServletResponse response, User user, String redirectUri, String state) throws IOException - { - String authCode = UUID.randomUUID().toString().replace("-", ""); - issuedAuthCodes.put(authCode, user); - - try - { - redirectUri += "?code=" + authCode + "&state=" + state; - response.sendRedirect(response.encodeRedirectURL(redirectUri)); - } - catch (Throwable t) - { - issuedAuthCodes.remove(authCode); - throw t; - } - } - } - - private class TokenEndpoint extends HttpServlet - { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - String code = req.getParameter("code"); - - if (!clientId.equals(req.getParameter("client_id")) || - !clientSecret.equals(req.getParameter("client_secret")) || - !redirectUris.contains(req.getParameter("redirect_uri")) || - !"authorization_code".equals(req.getParameter("grant_type")) || - code == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request"); - return; - } - - User user = issuedAuthCodes.remove(code); - if (user == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code"); - return; - } - - String accessToken = "ABCDEFG"; - long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); - String response = "{" + - "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + - "\"expires_in\": " + accessTokenDuration + "," + - "\"token_type\": \"Bearer\"" + - "}"; - - loggedInUsers.increment(); - resp.setContentType("text/plain"); - resp.getWriter().print(response); - } - } - - private class EndSessionEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - doPost(req, resp); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String idToken = req.getParameter("id_token_hint"); - if (idToken == null) - { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no id_token_hint"); - return; - } - - String logoutRedirect = req.getParameter("post_logout_redirect_uri"); - if (logoutRedirect == null) - { - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().println("logout success on end_session_endpoint"); - return; - } - - loggedInUsers.decrement(); - resp.setContentType("text/plain"); - resp.sendRedirect(logoutRedirect); - } - } - - private class ConfigServlet extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String discoveryDocument = "{" + - "\"issuer\": \"" + provider + "\"," + - "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + - "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + - "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + - "}"; - - resp.getWriter().write(discoveryDocument); - } - } - - public static class User - { - private final String subject; - private final String name; - - public User(String name) - { - this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); - } - - public User(String subject, String name) - { - this.subject = subject; - this.name = name; - } - - public String getName() - { - return name; - } - - public String getSubject() - { - return subject; - } - - public String getIdToken(String provider, String clientId, long duration) - { - long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); - return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); - } - - @Override - public boolean equals(Object obj) - { - if (!(obj instanceof User)) - return false; - return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); - } - - @Override - public int hashCode() - { - return Objects.hash(subject, name); - } - } -} diff --git a/tests/test-distribution/test-ee9-distribution/pom.xml b/tests/test-distribution/test-ee9-distribution/pom.xml index ce6164c980e..94a6acb2b33 100644 --- a/tests/test-distribution/test-ee9-distribution/pom.xml +++ b/tests/test-distribution/test-ee9-distribution/pom.xml @@ -56,6 +56,11 @@ war test
+ + org.eclipse.jetty.tests + jetty-test-util + test + org.eclipse.jetty.tests jetty-testers diff --git a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java index 78744609f36..cb7803bf787 100644 --- a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java +++ b/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java @@ -17,8 +17,8 @@ import java.nio.file.Path; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.ee9.tests.distribution.openid.OpenIdProvider; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.tests.OpenIdProvider; import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; import org.eclipse.jetty.tests.testers.JettyHomeTester; import org.eclipse.jetty.tests.testers.Tester; diff --git a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/JwtEncoder.java b/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/JwtEncoder.java deleted file mode 100644 index 62b6f1dacfc..00000000000 --- a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/JwtEncoder.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee9.tests.distribution.openid; - -import java.util.Base64; - -/** - * A basic JWT encoder for testing purposes. - */ -public class JwtEncoder -{ - private static final Base64.Encoder ENCODER = Base64.getUrlEncoder(); - private static final String DEFAULT_HEADER = "{\"INFO\": \"this is not used or checked in our implementation\"}"; - private static final String DEFAULT_SIGNATURE = "we do not validate signature as we use the authorization code flow"; - - public static String encode(String idToken) - { - return stripPadding(ENCODER.encodeToString(DEFAULT_HEADER.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(idToken.getBytes())) + "." + - stripPadding(ENCODER.encodeToString(DEFAULT_SIGNATURE.getBytes())); - } - - private static String stripPadding(String paddedBase64) - { - return paddedBase64.split("=")[0]; - } - - /** - * Create a basic JWT for testing using argument supplied attributes. - */ - public static String createIdToken(String provider, String clientId, String subject, String name, long expiry) - { - return "{" + - "\"iss\": \"" + provider + "\"," + - "\"sub\": \"" + subject + "\"," + - "\"aud\": \"" + clientId + "\"," + - "\"exp\": " + expiry + "," + - "\"name\": \"" + name + "\"," + - "\"email\": \"" + name + "@example.com" + "\"" + - "}"; - } -} diff --git a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/OpenIdProvider.java b/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/OpenIdProvider.java deleted file mode 100644 index 33ee1dc7dc9..00000000000 --- a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/openid/OpenIdProvider.java +++ /dev/null @@ -1,402 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 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.ee9.tests.distribution.openid; - -import java.io.IOException; -import java.io.PrintWriter; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.ee9.servlet.ServletContextHandler; -import org.eclipse.jetty.ee9.servlet.ServletHolder; -import org.eclipse.jetty.security.openid.OpenIdConfiguration; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.statistic.CounterStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenIdProvider extends ContainerLifeCycle -{ - private static final Logger LOG = LoggerFactory.getLogger(OpenIdProvider.class); - - private static final String CONFIG_PATH = "/.well-known/openid-configuration"; - private static final String AUTH_PATH = "/auth"; - private static final String TOKEN_PATH = "/token"; - private static final String END_SESSION_PATH = "/end_session"; - private final Map issuedAuthCodes = new HashMap<>(); - - protected final String clientId; - protected final String clientSecret; - protected final List redirectUris = new ArrayList<>(); - private final ServerConnector connector; - private final Server server; - private int port = 0; - private String provider; - private User preAuthedUser; - private final CounterStatistic loggedInUsers = new CounterStatistic(); - private long _idTokenDuration = Duration.ofSeconds(10).toMillis(); - - public static void main(String[] args) throws Exception - { - String clientId = "CLIENT_ID123"; - String clientSecret = "PASSWORD123"; - int port = 5771; - String redirectUri = "http://localhost:8080/j_security_check"; - - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - openIdProvider.addRedirectUri(redirectUri); - openIdProvider.setPort(port); - openIdProvider.start(); - try - { - openIdProvider.join(); - } - finally - { - openIdProvider.stop(); - } - } - - public OpenIdProvider(String clientId, String clientSecret) - { - this.clientId = clientId; - this.clientSecret = clientSecret; - - server = new Server(); - connector = new ServerConnector(server); - server.addConnector(connector); - - ServletContextHandler contextHandler = new ServletContextHandler(); - contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ConfigServlet()), CONFIG_PATH); - contextHandler.addServlet(new ServletHolder(new AuthEndpoint()), AUTH_PATH); - contextHandler.addServlet(new ServletHolder(new TokenEndpoint()), TOKEN_PATH); - contextHandler.addServlet(new ServletHolder(new EndSessionEndpoint()), END_SESSION_PATH); - server.setHandler(contextHandler); - - addBean(server); - } - - public void setIdTokenDuration(long duration) - { - _idTokenDuration = duration; - } - - public long getIdTokenDuration() - { - return _idTokenDuration; - } - - public void join() throws InterruptedException - { - server.join(); - } - - public OpenIdConfiguration getOpenIdConfiguration() - { - String provider = getProvider(); - String authEndpoint = provider + AUTH_PATH; - String tokenEndpoint = provider + TOKEN_PATH; - return new OpenIdConfiguration(provider, authEndpoint, tokenEndpoint, clientId, clientSecret, null); - } - - public CounterStatistic getLoggedInUsers() - { - return loggedInUsers; - } - - @Override - protected void doStart() throws Exception - { - connector.setPort(port); - super.doStart(); - provider = "http://localhost:" + connector.getLocalPort(); - } - - public void setPort(int port) - { - if (isStarted()) - throw new IllegalStateException(); - this.port = port; - } - - public void setUser(User user) - { - this.preAuthedUser = user; - } - - public String getProvider() - { - if (!isStarted() && port == 0) - throw new IllegalStateException("Port of OpenIdProvider not configured"); - return provider; - } - - public void addRedirectUri(String uri) - { - redirectUris.add(uri); - } - - public class AuthEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - if (!clientId.equals(req.getParameter("client_id"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid client_id"); - return; - } - - String redirectUri = req.getParameter("redirect_uri"); - if (!redirectUris.contains(redirectUri)) - { - LOG.warn("invalid redirectUri {}", redirectUri); - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String scopeString = req.getParameter("scope"); - List scopes = (scopeString == null) ? Collections.emptyList() : Arrays.asList(scopeString.split(" ")); - if (!scopes.contains("openid")) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no openid scope"); - return; - } - - if (!"code".equals(req.getParameter("response_type"))) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "response_type must be code"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - if (preAuthedUser == null) - { - PrintWriter writer = resp.getWriter(); - resp.setContentType("text/html"); - writer.println("

Login to OpenID Connect Provider

"); - writer.println("
"); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println(""); - writer.println("
"); - } - else - { - redirectUser(resp, preAuthedUser, redirectUri, state); - } - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String redirectUri = req.getParameter("redirectUri"); - if (!redirectUris.contains(redirectUri)) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); - return; - } - - String state = req.getParameter("state"); - if (state == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no state param"); - return; - } - - String username = req.getParameter("username"); - if (username == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "no username"); - return; - } - - User user = new User(username); - redirectUser(resp, user, redirectUri, state); - } - - public void redirectUser(HttpServletResponse response, User user, String redirectUri, String state) throws IOException - { - String authCode = UUID.randomUUID().toString().replace("-", ""); - issuedAuthCodes.put(authCode, user); - - try - { - redirectUri += "?code=" + authCode + "&state=" + state; - response.sendRedirect(response.encodeRedirectURL(redirectUri)); - } - catch (Throwable t) - { - issuedAuthCodes.remove(authCode); - throw t; - } - } - } - - private class TokenEndpoint extends HttpServlet - { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - String code = req.getParameter("code"); - - if (!clientId.equals(req.getParameter("client_id")) || - !clientSecret.equals(req.getParameter("client_secret")) || - !redirectUris.contains(req.getParameter("redirect_uri")) || - !"authorization_code".equals(req.getParameter("grant_type")) || - code == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "bad auth request"); - return; - } - - User user = issuedAuthCodes.remove(code); - if (user == null) - { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid auth code"); - return; - } - - String accessToken = "ABCDEFG"; - long accessTokenDuration = Duration.ofMinutes(10).toSeconds(); - String response = "{" + - "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId, _idTokenDuration)) + "\"," + - "\"expires_in\": " + accessTokenDuration + "," + - "\"token_type\": \"Bearer\"" + - "}"; - - loggedInUsers.increment(); - resp.setContentType("text/plain"); - resp.getWriter().print(response); - } - } - - private class EndSessionEndpoint extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - doPost(req, resp); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String idToken = req.getParameter("id_token_hint"); - if (idToken == null) - { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no id_token_hint"); - return; - } - - String logoutRedirect = req.getParameter("post_logout_redirect_uri"); - if (logoutRedirect == null) - { - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().println("logout success on end_session_endpoint"); - return; - } - - loggedInUsers.decrement(); - resp.setContentType("text/plain"); - resp.sendRedirect(logoutRedirect); - } - } - - private class ConfigServlet extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String discoveryDocument = "{" + - "\"issuer\": \"" + provider + "\"," + - "\"authorization_endpoint\": \"" + provider + AUTH_PATH + "\"," + - "\"token_endpoint\": \"" + provider + TOKEN_PATH + "\"," + - "\"end_session_endpoint\": \"" + provider + END_SESSION_PATH + "\"," + - "}"; - - resp.getWriter().write(discoveryDocument); - } - } - - public static class User - { - private final String subject; - private final String name; - - public User(String name) - { - this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); - } - - public User(String subject, String name) - { - this.subject = subject; - this.name = name; - } - - public String getName() - { - return name; - } - - public String getSubject() - { - return subject; - } - - public String getIdToken(String provider, String clientId, long duration) - { - long expiryTime = Instant.now().plusMillis(duration).getEpochSecond(); - return JwtEncoder.createIdToken(provider, clientId, subject, name, expiryTime); - } - - @Override - public boolean equals(Object obj) - { - if (!(obj instanceof User)) - return false; - return Objects.equals(subject, ((User)obj).subject) && Objects.equals(name, ((User)obj).name); - } - - @Override - public int hashCode() - { - return Objects.hash(subject, name); - } - } -} From e31360dd7f1e50e74240488fb505ee80f3e25235 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 27 Aug 2024 14:39:47 +1000 Subject: [PATCH 3/3] rename jetty-test-util to jetty-test-common Signed-off-by: Lachlan Roberts --- jetty-ee8/jetty-ee8-openid/pom.xml | 2 +- jetty-ee9/jetty-ee9-openid/pom.xml | 2 +- jetty-integrations/jetty-openid/pom.xml | 2 +- pom.xml | 2 +- tests/{jetty-test-util => jetty-test-common}/pom.xml | 2 +- .../src/main/java/org/eclipse/jetty/tests/JwtEncoder.java | 0 .../src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java | 0 tests/pom.xml | 2 +- tests/test-distribution/test-ee10-distribution/pom.xml | 2 +- tests/test-distribution/test-ee11-distribution/pom.xml | 2 +- tests/test-distribution/test-ee9-distribution/pom.xml | 2 +- 11 files changed, 9 insertions(+), 9 deletions(-) rename tests/{jetty-test-util => jetty-test-common}/pom.xml (95%) rename tests/{jetty-test-util => jetty-test-common}/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java (100%) rename tests/{jetty-test-util => jetty-test-common}/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java (100%) diff --git a/jetty-ee8/jetty-ee8-openid/pom.xml b/jetty-ee8/jetty-ee8-openid/pom.xml index 9474870eb21..c6af6f29179 100644 --- a/jetty-ee8/jetty-ee8-openid/pom.xml +++ b/jetty-ee8/jetty-ee8-openid/pom.xml @@ -54,7 +54,7 @@
org.eclipse.jetty.tests - jetty-test-util + jetty-test-common test diff --git a/jetty-ee9/jetty-ee9-openid/pom.xml b/jetty-ee9/jetty-ee9-openid/pom.xml index 8c92210726b..3dd45710155 100644 --- a/jetty-ee9/jetty-ee9-openid/pom.xml +++ b/jetty-ee9/jetty-ee9-openid/pom.xml @@ -57,7 +57,7 @@ org.eclipse.jetty.tests - jetty-test-util + jetty-test-common test diff --git a/jetty-integrations/jetty-openid/pom.xml b/jetty-integrations/jetty-openid/pom.xml index f67543aa805..ffcf4750e3a 100644 --- a/jetty-integrations/jetty-openid/pom.xml +++ b/jetty-integrations/jetty-openid/pom.xml @@ -49,7 +49,7 @@ org.eclipse.jetty.tests - jetty-test-util + jetty-test-common test diff --git a/pom.xml b/pom.xml index 68311850742..5a386d0f036 100644 --- a/pom.xml +++ b/pom.xml @@ -1062,7 +1062,7 @@ org.eclipse.jetty.tests - jetty-test-util + jetty-test-common ${project.version} diff --git a/tests/jetty-test-util/pom.xml b/tests/jetty-test-common/pom.xml similarity index 95% rename from tests/jetty-test-util/pom.xml rename to tests/jetty-test-common/pom.xml index cab19941710..d820ada4c30 100644 --- a/tests/jetty-test-util/pom.xml +++ b/tests/jetty-test-common/pom.xml @@ -7,7 +7,7 @@ tests 12.1.0-SNAPSHOT - jetty-test-util + jetty-test-common jar Tests :: Test Utilities diff --git a/tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java b/tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java similarity index 100% rename from tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java rename to tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/JwtEncoder.java diff --git a/tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java b/tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java similarity index 100% rename from tests/jetty-test-util/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java rename to tests/jetty-test-common/src/main/java/org/eclipse/jetty/tests/OpenIdProvider.java diff --git a/tests/pom.xml b/tests/pom.xml index b916216f008..022baf529be 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -15,7 +15,7 @@ jetty-jmh jetty-test-multipart jetty-test-session-common - jetty-test-util + jetty-test-common test-cross-context-dispatch test-distribution test-integration diff --git a/tests/test-distribution/test-ee10-distribution/pom.xml b/tests/test-distribution/test-ee10-distribution/pom.xml index 37669d365f2..11f3cedaf70 100644 --- a/tests/test-distribution/test-ee10-distribution/pom.xml +++ b/tests/test-distribution/test-ee10-distribution/pom.xml @@ -62,7 +62,7 @@ org.eclipse.jetty.tests - jetty-test-util + jetty-test-common test diff --git a/tests/test-distribution/test-ee11-distribution/pom.xml b/tests/test-distribution/test-ee11-distribution/pom.xml index a00a3c20bf3..1b91c48eab8 100644 --- a/tests/test-distribution/test-ee11-distribution/pom.xml +++ b/tests/test-distribution/test-ee11-distribution/pom.xml @@ -57,7 +57,7 @@ org.eclipse.jetty.tests - jetty-test-util + jetty-test-common test diff --git a/tests/test-distribution/test-ee9-distribution/pom.xml b/tests/test-distribution/test-ee9-distribution/pom.xml index 94a6acb2b33..15ac6a3ce38 100644 --- a/tests/test-distribution/test-ee9-distribution/pom.xml +++ b/tests/test-distribution/test-ee9-distribution/pom.xml @@ -58,7 +58,7 @@ org.eclipse.jetty.tests - jetty-test-util + jetty-test-common test