From e52bd7a9c76d2fc82dca9e9acc10587f1327825a Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Tue, 17 Sep 2024 15:08:49 +1000 Subject: [PATCH] Jetty 12.0.x Simplify tests with OpenId by using an exising openid provider and avoid extra not necessary maven modules (#12274) * use keycloak as openid provider for testing * merge all test-distribution into a single one --------- Signed-off-by: Olivier Lamy --- tests/test-distribution/pom.xml | 2 - .../test-distribution-common/pom.xml | 44 +- .../distribution/DisableUrlCacheTest.java | 3 +- .../jetty/tests/distribution/OpenIdTests.java | 203 +++++++++ .../test/resources/jetty-logging.properties | 3 + .../test-ee10-distribution/pom.xml | 71 ---- .../ee10/tests/distribution/OpenIdTests.java | 119 ------ .../tests/distribution/openid/JwtEncoder.java | 53 --- .../distribution/openid/OpenIdProvider.java | 402 ------------------ .../test-ee9-distribution/pom.xml | 72 ---- .../ee9/tests/distribution/OpenIdTests.java | 120 ------ .../tests/distribution/openid/JwtEncoder.java | 53 --- .../distribution/openid/OpenIdProvider.java | 402 ------------------ 13 files changed, 239 insertions(+), 1308 deletions(-) rename tests/test-distribution/{test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10 => test-distribution-common/src/test/java/org/eclipse/jetty}/tests/distribution/DisableUrlCacheTest.java (98%) create mode 100644 tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/OpenIdTests.java delete mode 100644 tests/test-distribution/test-ee10-distribution/pom.xml delete mode 100644 tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.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-ee9-distribution/pom.xml delete mode 100644 tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.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/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index 245bcb68740..18e45fd929b 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -13,8 +13,6 @@ test-distribution-common - test-ee10-distribution - test-ee9-distribution diff --git a/tests/test-distribution/test-distribution-common/pom.xml b/tests/test-distribution/test-distribution-common/pom.xml index b83a3ba2775..eb1f703aed0 100644 --- a/tests/test-distribution/test-distribution-common/pom.xml +++ b/tests/test-distribution/test-distribution-common/pom.xml @@ -15,9 +15,21 @@ ${project.groupId}.tests.distribution.common 2 + 3.4.0 + + com.github.dasniko + testcontainers-keycloak + ${testcontainers-keycloak.version} + + + io.quarkus + quarkus-junit4-mock + + + org.apache.maven maven-artifact @@ -138,6 +150,12 @@ org.eclipse.jetty jetty-infinispan-common test + + + io.smallrye + jandex + + org.eclipse.jetty @@ -155,6 +173,20 @@ jetty-util-ajax test + + org.eclipse.jetty.ee10 + jetty-ee10-test-log4j2-webapp + ${project.version} + war + test + + + org.eclipse.jetty.ee9 + jetty-ee9-test-openid-webapp + ${project.version} + war + test + org.eclipse.jetty.http2 jetty-http2-client @@ -244,18 +276,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - ${maven.jar.plugin.version} - - - - test-jar - - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/DisableUrlCacheTest.java b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DisableUrlCacheTest.java similarity index 98% rename from tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/DisableUrlCacheTest.java rename to tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DisableUrlCacheTest.java index 81299ef8038..9b8a2919088 100644 --- a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/DisableUrlCacheTest.java +++ b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/DisableUrlCacheTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.ee10.tests.distribution; +package org.eclipse.jetty.tests.distribution; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -23,7 +23,6 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest; import org.eclipse.jetty.tests.testers.JettyHomeTester; import org.eclipse.jetty.tests.testers.Tester; import org.eclipse.jetty.toolchain.test.FS; diff --git a/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/OpenIdTests.java b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/OpenIdTests.java new file mode 100644 index 00000000000..c96fffe5cff --- /dev/null +++ b/tests/test-distribution/test-distribution-common/src/test/java/org/eclipse/jetty/tests/distribution/OpenIdTests.java @@ -0,0 +1,203 @@ +// +// ======================================================================== +// 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.distribution; + +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import dasniko.testcontainers.keycloak.KeycloakContainer; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.tests.testers.JettyHomeTester; +import org.eclipse.jetty.tests.testers.Tester; +import org.eclipse.jetty.util.Fields; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.keycloak.admin.client.CreatedResponseUtil; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OpenIdTests extends AbstractJettyHomeTest +{ + private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdTests.class); + + private static final KeycloakContainer KEYCLOAK_CONTAINER = new KeycloakContainer(); + + private static final String clientId = "jetty-api"; + private static final String clientSecret = "JettyRocks!"; + + private static final String userName = "jetty"; + private static final String password = "JettyRocks!Really"; + + private static final String firstName = "John"; + private static final String lastName = "Doe"; + private static final String email = "jetty@jetty.org"; + + private static String userId; + + @BeforeAll + public static void startKeycloak() + { + KEYCLOAK_CONTAINER.start(); + // init keycloak + try (Keycloak keycloak = KEYCLOAK_CONTAINER.getKeycloakAdminClient()) + { + RealmRepresentation jettyRealm = new RealmRepresentation(); + jettyRealm.setId("jetty"); + jettyRealm.setRealm("jetty"); + jettyRealm.setEnabled(true); + keycloak.realms().create(jettyRealm); + + ClientRepresentation clientRepresentation = new ClientRepresentation(); + clientRepresentation.setClientId(clientId); + clientRepresentation.setSecret(clientSecret); + clientRepresentation.setRedirectUris(List.of("http://localhost:*")); + clientRepresentation.setEnabled(true); + clientRepresentation.setPublicClient(Boolean.TRUE); + keycloak.realm("jetty").clients().create(clientRepresentation); + + UserRepresentation user = new UserRepresentation(); + user.setEnabled(true); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setUsername(userName); + user.setEmail(email); + + userId = CreatedResponseUtil.getCreatedId(keycloak.realm("jetty").users().create(user)); + + CredentialRepresentation passwordCred = new CredentialRepresentation(); + passwordCred.setTemporary(false); + passwordCred.setType(CredentialRepresentation.PASSWORD); + passwordCred.setValue(password); + + // Set password credential + keycloak.realm("jetty").users().get(userId).resetPassword(passwordCred); + } + } + + @AfterAll + public static void stopKeycloak() + { + if (KEYCLOAK_CONTAINER.isRunning()) + { + KEYCLOAK_CONTAINER.stop(); + } + } + + public static Stream tests() + { + return Stream.of( + Arguments.of("ee9", "ee9-openid"), + Arguments.of("ee10", "openid") + ); + } + + @ParameterizedTest + @MethodSource("tests") + public void testOpenID(String env, String openIdModule) throws Exception + { + Path jettyBase = newTestJettyBaseDirectory(); + String jettyVersion = System.getProperty("jettyVersion"); + JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(jettyBase) + .build(); + + String[] args1 = { + "--create-startd", + "--approve-all-licenses", + "--add-to-start=http," + toEnvironment("webapp", env) + "," + toEnvironment("deploy", env) + "," + openIdModule + }; + + try (JettyHomeTester.Run run1 = distribution.start(args1)) + { + assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); + assertEquals(0, run1.getExitValue()); + + Path webApp = distribution.resolveArtifact("org.eclipse.jetty." + env + ":jetty-" + env + "-test-openid-webapp:war:" + jettyVersion); + distribution.installWar(webApp, "test"); + String openIdProvider = KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/jetty"; + LOGGER.info("openIdProvider: {}", openIdProvider); + + int port = Tester.freePort(); + String[] args2 = { + "jetty.http.port=" + port, + "jetty.ssl.port=" + port, + "jetty.openid.provider=" + openIdProvider, + "jetty.openid.clientId=" + clientId, + "jetty.openid.clientSecret=" + clientSecret, + //"jetty.server.dumpAfterStart=true", + }; + + try (JettyHomeTester.Run run2 = distribution.start(args2)) + { + assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS)); + String uri = "http://localhost:" + port + "/test"; + // Initially not authenticated + startHttpClient(); + ContentResponse contentResponse = client.GET(uri + "/"); + assertThat(contentResponse.getStatus(), is(HttpStatus.OK_200)); + assertThat(contentResponse.getContentAsString(), containsString("not authenticated")); + + // Request to login is success + contentResponse = client.GET(uri + "/login"); + assertThat(contentResponse.getStatus(), is(HttpStatus.OK_200)); + // need to extract form + String html = contentResponse.getContentAsString(); + // need this attribute
- - - 4.0.0 - - org.eclipse.jetty.tests - test-distribution - 12.0.14-SNAPSHOT - - test-ee10-distribution - jar - Tests :: Distribution :: EE10 - - - ${project.groupId}.ee10.distribution - - - - - org.eclipse.jetty - jetty-home - zip - - - * - * - - - - - org.eclipse.jetty - jetty-client - test - - - org.eclipse.jetty - jetty-openid - test - - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.ee10 - jetty-ee10-servlet - ${project.version} - test - - - org.eclipse.jetty.ee10 - jetty-ee10-test-openid-webapp - ${project.version} - war - test - - - org.eclipse.jetty.tests - jetty-testers - test - - - org.eclipse.jetty.tests - test-distribution-common - test-jar - test - - - - 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 deleted file mode 100644 index 0e06385efd3..00000000000 --- a/tests/test-distribution/test-ee10-distribution/src/test/java/org/eclipse/jetty/ee10/tests/distribution/OpenIdTests.java +++ /dev/null @@ -1,119 +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; - -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.distribution.AbstractJettyHomeTest; -import org.eclipse.jetty.tests.testers.JettyHomeTester; -import org.eclipse.jetty.tests.testers.Tester; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Isolated -public class OpenIdTests extends AbstractJettyHomeTest -{ - @Test - public void testOpenID() throws Exception - { - Path jettyBase = newTestJettyBaseDirectory(); - String jettyVersion = System.getProperty("jettyVersion"); - JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() - .jettyVersion(jettyVersion) - .jettyBase(jettyBase) - .build(); - - String[] args1 = { - "--create-startd", - "--approve-all-licenses", - "--add-to-start=http,ee10-webapp,ee10-deploy,openid" - }; - - String clientId = "clientId123"; - String clientSecret = "clientSecret456"; - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - try (JettyHomeTester.Run run1 = distribution.start(args1)) - { - assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); - assertEquals(0, run1.getExitValue()); - - Path webApp = distribution.resolveArtifact("org.eclipse.jetty.ee10:jetty-ee10-test-openid-webapp:war:" + jettyVersion); - distribution.installWar(webApp, "test"); - - int port = Tester.freePort(); - openIdProvider.addRedirectUri("http://localhost:" + port + "/test/j_security_check"); - openIdProvider.start(); - String[] args2 = { - "jetty.http.port=" + port, - "jetty.ssl.port=" + port, - "jetty.openid.provider=" + openIdProvider.getProvider(), - "jetty.openid.clientId=" + clientId, - "jetty.openid.clientSecret=" + clientSecret, - //"jetty.server.dumpAfterStart=true", - }; - - try (JettyHomeTester.Run run2 = distribution.start(args2)) - { - assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS)); - startHttpClient(false); - String uri = "http://localhost:" + port + "/test"; - openIdProvider.setUser(new OpenIdProvider.User("123456789", "Alice")); - - // Initially not authenticated - ContentResponse response = client.GET(uri + "/"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - String content = response.getContentAsString(); - assertThat(content, containsString("not authenticated")); - - // Request to login is success - response = client.GET(uri + "/login"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString(); - assertThat(content, containsString("success")); - - // Now authenticated we can get info - response = client.GET(uri + "/"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString(); - assertThat(content, containsString("userId: 123456789")); - assertThat(content, containsString("name: Alice")); - assertThat(content, containsString("email: Alice@example.com")); - - // Request to admin page gives 403 as we do not have admin role - response = client.GET(uri + "/admin"); - assertThat(response.getStatus(), is(HttpStatus.FORBIDDEN_403)); - - // We are no longer authenticated after logging out - response = client.GET(uri + "/logout"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString(); - assertThat(content, containsString("not authenticated")); - } - } - finally - { - openIdProvider.stop(); - } - } -} 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-ee9-distribution/pom.xml b/tests/test-distribution/test-ee9-distribution/pom.xml deleted file mode 100644 index c49ace26941..00000000000 --- a/tests/test-distribution/test-ee9-distribution/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - 4.0.0 - - org.eclipse.jetty.tests - test-distribution - 12.0.14-SNAPSHOT - - test-ee9-distribution - jar - Tests :: Distribution :: EE9 - - - ${project.groupId}.ee9.distribution - - - - - org.eclipse.jetty - jetty-home - zip - - - * - * - - - - - org.eclipse.jetty - jetty-client - test - - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.ee9 - jetty-ee9-openid - ${project.version} - test - - - org.eclipse.jetty.ee9 - jetty-ee9-servlet - ${project.version} - test - - - org.eclipse.jetty.ee9 - jetty-ee9-test-openid-webapp - ${project.version} - war - test - - - org.eclipse.jetty.tests - jetty-testers - test - - - org.eclipse.jetty.tests - test-distribution-common - test-jar - test - - - - 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 deleted file mode 100644 index 78744609f36..00000000000 --- a/tests/test-distribution/test-ee9-distribution/src/test/java/org/eclipse/jetty/ee9/tests/distribution/OpenIdTests.java +++ /dev/null @@ -1,120 +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; - -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.distribution.AbstractJettyHomeTest; -import org.eclipse.jetty.tests.testers.JettyHomeTester; -import org.eclipse.jetty.tests.testers.Tester; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Isolated -public class OpenIdTests extends AbstractJettyHomeTest -{ - @Test - public void testOpenID() throws Exception - { - Path jettyBase = newTestJettyBaseDirectory(); - String jettyVersion = System.getProperty("jettyVersion"); - JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() - .jettyVersion(jettyVersion) - .jettyBase(jettyBase) - .build(); - - String[] args1 = { - "--create-startd", - "--approve-all-licenses", - "--add-to-start=http,ee9-webapp,ee9-deploy,ee9-openid" - }; - - String clientId = "clientId123"; - String clientSecret = "clientSecret456"; - OpenIdProvider openIdProvider = new OpenIdProvider(clientId, clientSecret); - try (JettyHomeTester.Run run1 = distribution.start(args1)) - { - assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS)); - assertEquals(0, run1.getExitValue()); - - Path webApp = distribution.resolveArtifact("org.eclipse.jetty.ee9:jetty-ee9-test-openid-webapp:war:" + jettyVersion); - distribution.installWar(webApp, "test"); - - int port = Tester.freePort(); - openIdProvider.addRedirectUri("http://localhost:" + port + "/test/j_security_check"); - openIdProvider.start(); - String[] args2 = { - "jetty.http.port=" + port, - "jetty.ssl.port=" + port, - "jetty.openid.provider=" + openIdProvider.getProvider(), - "jetty.openid.clientId=" + clientId, - "jetty.openid.clientSecret=" + clientSecret, - //"jetty.server.dumpAfterStart=true", - }; - - try (JettyHomeTester.Run run2 = distribution.start(args2)) - { - assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS)); - startHttpClient(false); - String uri = "http://localhost:" + port + "/test"; - openIdProvider.setUser(new OpenIdProvider.User("123456789", "Alice")); - - // Initially not authenticated - ContentResponse response = client.GET(uri + "/"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - String content = response.getContentAsString(); - assertThat(content, containsString("not authenticated")); - - // Request to login is success - response = client.GET(uri + "/login"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString(); - assertThat(content, containsString("success")); - - // Now authenticated we can get info - response = client.GET(uri + "/"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString(); - assertThat(content, containsString("userId: 123456789")); - assertThat(content, containsString("name: Alice")); - assertThat(content, containsString("email: Alice@example.com")); - - // Request to admin page gives 403 as we do not have admin role - response = client.GET(uri + "/admin"); - assertThat(response.getStatus(), is(HttpStatus.FORBIDDEN_403)); - - // We are no longer authenticated after logging out - response = client.GET(uri + "/logout"); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString(); - assertThat(content, containsString("not authenticated")); - - } - } - finally - { - openIdProvider.stop(); - } - } -} 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); - } - } -}