From b8af57153a5469e36f8a4c3786c1e3fd923ec4d3 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 26 Oct 2021 09:45:05 +1100 Subject: [PATCH 1/5] Issue #7042 - Allow OpenIdConfiguration to be selected based on realm name. Signed-off-by: Lachlan Roberts --- .../src/main/config/etc/jetty-openid.xml | 43 +++-- jetty-openid/src/main/java/module-info.java | 4 + .../openid/OpenIdAuthConfiguration.java | 52 ++++++ .../security/openid/OpenIdAuthenticator.java | 64 +++++-- .../openid/OpenIdAuthenticatorFactory.java | 15 +- .../security/openid/OpenIdConfiguration.java | 31 +++- .../security/openid/OpenIdLoginService.java | 8 +- .../openid/OpenIdAuthenticationTest.java | 69 ++++---- .../jetty/security/openid/OpenIdProvider.java | 162 +++++++++++++++--- 9 files changed, 345 insertions(+), 103 deletions(-) create mode 100644 jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java diff --git a/jetty-openid/src/main/config/etc/jetty-openid.xml b/jetty-openid/src/main/config/etc/jetty-openid.xml index 8e252c22f80..5f973448877 100644 --- a/jetty-openid/src/main/config/etc/jetty-openid.xml +++ b/jetty-openid/src/main/config/etc/jetty-openid.xml @@ -20,30 +20,29 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-openid/src/main/java/module-info.java b/jetty-openid/src/main/java/module-info.java index f518a176f3b..f83b559b71c 100644 --- a/jetty-openid/src/main/java/module-info.java +++ b/jetty-openid/src/main/java/module-info.java @@ -11,6 +11,8 @@ // ======================================================================== // +import org.eclipse.jetty.security.openid.OpenIdAuthenticatorFactory; + module org.eclipse.jetty.security.openid { requires org.eclipse.jetty.util.ajax; @@ -19,4 +21,6 @@ module org.eclipse.jetty.security.openid requires transitive org.eclipse.jetty.security; exports org.eclipse.jetty.security.openid; + + provides org.eclipse.jetty.security.Authenticator.Factory with OpenIdAuthenticatorFactory; } diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java new file mode 100644 index 00000000000..9d6de0e0821 --- /dev/null +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java @@ -0,0 +1,52 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.openid; + +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.WrappedAuthConfiguration; + +public class OpenIdAuthConfiguration extends WrappedAuthConfiguration +{ + public static final String AUTHENTICATE_NEW_USERS_INIT_PARAM = "jetty.openid.authenticateNewUsers"; + + private final OpenIdLoginService _openIdLoginService; + + public OpenIdAuthConfiguration(OpenIdConfiguration openIdConfiguration, Authenticator.AuthConfiguration authConfiguration) + { + super(authConfiguration); + + LoginService loginService = authConfiguration.getLoginService(); + if (loginService instanceof OpenIdLoginService) + { + _openIdLoginService = (OpenIdLoginService)loginService; + } + else + { + _openIdLoginService = new OpenIdLoginService(openIdConfiguration, loginService); + if (loginService == null) + _openIdLoginService.setIdentityService(authConfiguration.getIdentityService()); + + String authNewUsers = authConfiguration.getInitParameter(AUTHENTICATE_NEW_USERS_INIT_PARAM); + if (authNewUsers != null) + _openIdLoginService.setAuthenticateNewUsers(Boolean.parseBoolean(authNewUsers)); + } + } + + @Override + public LoginService getLoginService() + { + return _openIdLoginService; + } +} diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java index 727b5a2c352..de88f1d23ec 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java @@ -51,7 +51,7 @@ import org.slf4j.LoggerFactory; *

Implements authentication using OpenId Connect on top of OAuth 2.0. * *

The OpenIdAuthenticator redirects unauthenticated requests to the OpenID Connect Provider. The End-User is - * eventually redirected back with an Authorization Code to the /j_security_check URI within the context. + * eventually redirected back with an Authorization Code to the path set by {@link #setRedirectPath(String)} within the context. * The Authorization Code is then used to authenticate the user through the {@link OpenIdCredentials} and {@link OpenIdLoginService}. *

*

@@ -66,6 +66,8 @@ public class OpenIdAuthenticator extends LoginAuthenticator public static final String CLAIMS = "org.eclipse.jetty.security.openid.claims"; public static final String RESPONSE = "org.eclipse.jetty.security.openid.response"; + public static final String ISSUER = "org.eclipse.jetty.security.openid.issuer"; + public static final String REDIRECT_PATH = "org.eclipse.jetty.security.openid.redirect_path"; public static final String ERROR_PAGE = "org.eclipse.jetty.security.openid.error_page"; public static final String J_URI = "org.eclipse.jetty.security.openid.URI"; public static final String J_POST = "org.eclipse.jetty.security.openid.POST"; @@ -79,6 +81,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator private final SecureRandom _secureRandom = new SecureRandom(); private OpenIdConfiguration _configuration; + private String _redirectPath; private String _errorPage; private String _errorPath; private String _errorQuery; @@ -86,11 +89,23 @@ public class OpenIdAuthenticator extends LoginAuthenticator public OpenIdAuthenticator() { + this(null, J_SECURITY_CHECK, null); + } + + public OpenIdAuthenticator(OpenIdConfiguration configuration) + { + this(configuration, J_SECURITY_CHECK, null); } public OpenIdAuthenticator(OpenIdConfiguration configuration, String errorPage) { - this._configuration = configuration; + this(configuration, J_SECURITY_CHECK, errorPage); + } + + public OpenIdAuthenticator(OpenIdConfiguration configuration, String redirectPath, String errorPage) + { + _configuration = configuration; + setRedirectPath(redirectPath); if (errorPage != null) setErrorPage(errorPage); } @@ -98,19 +113,23 @@ public class OpenIdAuthenticator extends LoginAuthenticator @Override public void setConfiguration(AuthConfiguration configuration) { - super.setConfiguration(configuration); + if (_configuration == null) + { + LoginService loginService = configuration.getLoginService(); + if (!(loginService instanceof OpenIdLoginService)) + throw new IllegalArgumentException("invalid LoginService " + loginService); + this._configuration = ((OpenIdLoginService)loginService).getConfiguration(); + } + + String redirectPath = configuration.getInitParameter(REDIRECT_PATH); + if (redirectPath != null) + _redirectPath = redirectPath; String error = configuration.getInitParameter(ERROR_PAGE); if (error != null) setErrorPage(error); - if (_configuration != null) - return; - - LoginService loginService = configuration.getLoginService(); - if (!(loginService instanceof OpenIdLoginService)) - throw new IllegalArgumentException("invalid LoginService"); - this._configuration = ((OpenIdLoginService)loginService).getConfiguration(); + super.setConfiguration(new OpenIdAuthConfiguration(_configuration, configuration)); } @Override @@ -131,7 +150,23 @@ public class OpenIdAuthenticator extends LoginAuthenticator return _alwaysSaveUri; } - private void setErrorPage(String path) + public void setRedirectPath(String redirectPath) + { + if (redirectPath == null) + { + LOG.warn("redirect path must not be null, defaulting to " + J_SECURITY_CHECK); + redirectPath = J_SECURITY_CHECK; + } + else if (!redirectPath.startsWith("/")) + { + LOG.warn("redirect path must start with /"); + redirectPath = "/" + redirectPath; + } + + _redirectPath = redirectPath; + } + + public void setErrorPage(String path) { if (path == null || path.trim().length() == 0) { @@ -174,6 +209,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached); session.setAttribute(CLAIMS, ((OpenIdCredentials)credentials).getClaims()); session.setAttribute(RESPONSE, ((OpenIdCredentials)credentials).getResponse()); + session.setAttribute(ISSUER, _configuration.getIssuer()); } } return user; @@ -445,11 +481,11 @@ public class OpenIdAuthenticator extends LoginAuthenticator public boolean isJSecurityCheck(String uri) { - int jsc = uri.indexOf(J_SECURITY_CHECK); + int jsc = uri.indexOf(_redirectPath); if (jsc < 0) return false; - int e = jsc + J_SECURITY_CHECK.length(); + int e = jsc + _redirectPath.length(); if (e == uri.length()) return true; char c = uri.charAt(e); @@ -467,7 +503,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator URIUtil.appendSchemeHostPort(redirectUri, request.getScheme(), request.getServerName(), request.getServerPort()); redirectUri.append(request.getContextPath()); - redirectUri.append(J_SECURITY_CHECK); + redirectUri.append(_redirectPath); return redirectUri.toString(); } diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java index a63dcd1681e..7a20e021bdb 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java @@ -28,7 +28,20 @@ public class OpenIdAuthenticatorFactory implements Authenticator.Factory { String auth = configuration.getAuthMethod(); if (Constraint.__OPENID_AUTH.equalsIgnoreCase(auth)) - return new OpenIdAuthenticator(); + { + // If LoginService is an OpenIdLoginService it already contains the configuration and will be obtained in setConfiguration(); + if (loginService instanceof OpenIdLoginService) + return new OpenIdAuthenticator(); + + // Otherwise we should find an OpenIdConfiguration for this realm on the Server. + String realmName = configuration.getRealmName(); + OpenIdConfiguration openIdConfiguration = server.getBeans(OpenIdConfiguration.class).stream() + .filter(c -> c.getIssuer().equals(realmName)) + .findAny() + .orElseThrow(() -> new IllegalStateException("No OpenIdConfiguration found for realm \"" + realmName + "\"")); + return new OpenIdAuthenticator(openIdConfiguration); + } + return null; } } diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java index 8a60ec3418c..40efea46143 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java @@ -48,6 +48,7 @@ public class OpenIdConfiguration extends ContainerLifeCycle private final String authMethod; private String authEndpoint; private String tokenEndpoint; + private boolean authenticateNewUsers = false; /** * Create an OpenID configuration for a specific OIDC provider. @@ -139,29 +140,28 @@ public class OpenIdConfiguration extends ContainerLifeCycle provider = provider.substring(0, provider.length() - 1); Map result; - String responseBody = httpClient.GET(provider + CONFIG_PATH) - .getContentAsString(); + String responseBody = httpClient.GET(provider + CONFIG_PATH).getContentAsString(); Object parsedResult = new JSON().fromJSON(responseBody); if (parsedResult instanceof Map) { Map rawResult = (Map)parsedResult; result = rawResult.entrySet().stream() + .filter(entry -> entry.getValue() != null) .collect(Collectors.toMap(it -> it.getKey().toString(), Map.Entry::getValue)); + if (LOG.isDebugEnabled()) + LOG.debug("discovery document {}", result); + return result; } else { LOG.warn("OpenID provider did not return a proper JSON object response. Result was '{}'", responseBody); throw new IllegalStateException("Could not parse OpenID provider's malformed response"); } - - LOG.debug("discovery document {}", result); - - return result; } catch (Exception e) { - throw new IllegalArgumentException("invalid identity provider", e); + throw new IllegalArgumentException("invalid identity provider " + provider, e); } } @@ -210,4 +210,21 @@ public class OpenIdConfiguration extends ContainerLifeCycle { return scopes; } + + public boolean isAuthenticateNewUsers() + { + return authenticateNewUsers; + } + + public void setAuthenticateNewUsers(boolean authenticateNewUsers) + { + this.authenticateNewUsers = authenticateNewUsers; + } + + @Override + public String toString() + { + return String.format("%s@%x{iss=%s, clientId=%s, authEndpoint=%s, authMethod=%s, tokenEndpoint=%s, scopes=%s, authNewUsers=%s}", + getClass().getSimpleName(), hashCode(), issuer, clientId, authEndpoint, authMethod, tokenEndpoint, scopes, authenticateNewUsers); + } } diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java index 0979eed52d5..f3598c27479 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.security.openid; +import java.util.Objects; import javax.security.auth.Subject; import javax.servlet.ServletRequest; @@ -53,10 +54,12 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi */ public OpenIdLoginService(OpenIdConfiguration configuration, LoginService loginService) { - this.configuration = configuration; + this.configuration = Objects.requireNonNull(configuration); this.loginService = loginService; addBean(this.configuration); addBean(this.loginService); + + setAuthenticateNewUsers(configuration.isAuthenticateNewUsers()); } @Override @@ -93,13 +96,14 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi subject.getPrivateCredentials().add(credentials); subject.setReadOnly(); + IdentityService identityService = getIdentityService(); if (loginService != null) { UserIdentity userIdentity = loginService.login(openIdCredentials.getUserId(), "", req); if (userIdentity == null) { if (isAuthenticateNewUsers()) - return getIdentityService().newUserIdentity(subject, userPrincipal, new String[0]); + return identityService.newUserIdentity(subject, userPrincipal, new String[0]); return null; } return new OpenIdUserIdentity(subject, userPrincipal, userIdentity); diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java index 210bd8629a3..d13952f167e 100644 --- a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdAuthenticationTest.java @@ -23,7 +23,6 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.server.Server; @@ -35,8 +34,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; +@SuppressWarnings("unchecked") public class OpenIdAuthenticationTest { public static final String CLIENT_ID = "testClient101"; @@ -88,24 +90,22 @@ public class OpenIdAuthenticationTest // security handler ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); - securityHandler.setRealmName("OpenID Connect Authentication"); + assertThat(securityHandler.getKnownAuthenticatorFactories().size(), greaterThanOrEqualTo(2)); + + securityHandler.setAuthMethod(Constraint.__OPENID_AUTH); + securityHandler.setRealmName(openIdProvider.getProvider()); securityHandler.addConstraintMapping(profileMapping); securityHandler.addConstraintMapping(loginMapping); securityHandler.addConstraintMapping(adminMapping); // Authentication using local OIDC Provider - OpenIdConfiguration configuration = new OpenIdConfiguration(openIdProvider.getProvider(), CLIENT_ID, CLIENT_SECRET); - - // Configure OpenIdLoginService optionally providing a base LoginService to provide user roles - OpenIdLoginService loginService = new OpenIdLoginService(configuration); - securityHandler.setLoginService(loginService); - - Authenticator authenticator = new OpenIdAuthenticator(configuration, "/error"); - securityHandler.setAuthenticator(authenticator); + server.addBean(new OpenIdConfiguration(openIdProvider.getProvider(), CLIENT_ID, CLIENT_SECRET)); + securityHandler.setInitParameter(OpenIdAuthenticator.REDIRECT_PATH, "/redirect_path"); + securityHandler.setInitParameter(OpenIdAuthenticator.ERROR_PAGE, "/error"); context.setSecurityHandler(securityHandler); server.start(); - String redirectUri = "http://localhost:" + connector.getLocalPort() + "/j_security_check"; + String redirectUri = "http://localhost:" + connector.getLocalPort() + "/redirect_path"; openIdProvider.addRedirectUri(redirectUri); client = new HttpClient(); @@ -122,30 +122,29 @@ public class OpenIdAuthenticationTest @Test public void testLoginLogout() throws Exception { + openIdProvider.setUser(new OpenIdProvider.User("123456789", "Alice")); + String appUriString = "http://localhost:" + connector.getLocalPort(); // Initially not authenticated ContentResponse response = client.GET(appUriString + "/"); assertThat(response.getStatus(), is(HttpStatus.OK_200)); - String[] content = response.getContentAsString().split("[\r\n]+"); - assertThat(content.length, is(1)); - assertThat(content[0], is("not authenticated")); + String content = response.getContentAsString(); + assertThat(content, containsString("not authenticated")); // Request to login is success response = client.GET(appUriString + "/login"); assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString().split("[\r\n]+"); - assertThat(content.length, is(1)); - assertThat(content[0], is("success")); + content = response.getContentAsString(); + assertThat(content, containsString("success")); // Now authenticated we can get info response = client.GET(appUriString + "/"); assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString().split("[\r\n]+"); - assertThat(content.length, is(3)); - assertThat(content[0], is("userId: 123456789")); - assertThat(content[1], is("name: Alice")); - assertThat(content[2], is("email: Alice@example.com")); + 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(appUriString + "/admin"); @@ -154,9 +153,8 @@ public class OpenIdAuthenticationTest // We are no longer authenticated after logging out response = client.GET(appUriString + "/logout"); assertThat(response.getStatus(), is(HttpStatus.OK_200)); - content = response.getContentAsString().split("[\r\n]+"); - assertThat(content.length, is(1)); - assertThat(content[0], is("not authenticated")); + content = response.getContentAsString(); + assertThat(content, containsString("not authenticated")); } public static class LoginPage extends HttpServlet @@ -164,7 +162,9 @@ public class OpenIdAuthenticationTest @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html"); response.getWriter().println("success"); + response.getWriter().println("
Home"); } } @@ -183,7 +183,7 @@ public class OpenIdAuthenticationTest @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - Map userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.CLAIMS); + Map userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.CLAIMS); response.getWriter().println(userInfo.get("sub") + ": success"); } } @@ -193,18 +193,20 @@ public class OpenIdAuthenticationTest @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - response.setContentType("text/plain"); + response.setContentType("text/html"); Principal userPrincipal = request.getUserPrincipal(); if (userPrincipal != null) { - Map userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.CLAIMS); - response.getWriter().println("userId: " + userInfo.get("sub")); - response.getWriter().println("name: " + userInfo.get("name")); - response.getWriter().println("email: " + userInfo.get("email")); + Map userInfo = (Map)request.getSession().getAttribute(OpenIdAuthenticator.CLAIMS); + response.getWriter().println("userId: " + userInfo.get("sub") + "
"); + response.getWriter().println("name: " + userInfo.get("name") + "
"); + response.getWriter().println("email: " + userInfo.get("email") + "
"); + response.getWriter().println("
Logout"); } else { response.getWriter().println("not authenticated"); + response.getWriter().println("
Login"); } } } @@ -214,8 +216,9 @@ public class OpenIdAuthenticationTest @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - response.setContentType("text/plain"); + response.setContentType("text/html"); response.getWriter().println("not authorized"); + response.getWriter().println("
Home"); } } -} +} \ No newline at end of file diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java index 62eb9ca78e1..90139d49f8a 100644 --- a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.security.openid; import java.io.IOException; +import java.io.PrintWriter; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -21,7 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; +import java.util.Objects; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -37,9 +38,13 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.ContainerLifeCycle; +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"; @@ -48,10 +53,32 @@ public class OpenIdProvider extends ContainerLifeCycle 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 Server server; - private ServerConnector connector; + private User preAuthedUser; + + public static void main(String[] args) throws Exception + { + String clientId = "CLIENT_ID123"; + String clientSecret = "PASSWORD123"; + int port = 5771; + String redirectUri = "http://localhost:8080/openid/auth"; + + 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) { @@ -72,17 +99,43 @@ public class OpenIdProvider extends ContainerLifeCycle addBean(server); } + 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); + } + @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()) - throw new IllegalStateException(); + if (!isStarted() && port == 0) + throw new IllegalStateException("Port of OpenIdProvider not configured"); return provider; } @@ -94,7 +147,7 @@ public class OpenIdProvider extends ContainerLifeCycle public class OpenIdAuthEndpoint extends HttpServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (!clientId.equals(req.getParameter("client_id"))) { @@ -105,6 +158,7 @@ public class OpenIdProvider extends ContainerLifeCycle String redirectUri = req.getParameter("redirect_uri"); if (!redirectUris.contains(redirectUri)) { + LOG.warn("invalid redirectUri {}", redirectUri); resp.sendError(HttpServletResponse.SC_FORBIDDEN, "invalid redirect_uri"); return; } @@ -130,16 +184,71 @@ public class OpenIdProvider extends ContainerLifeCycle 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(req, 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(req, user, redirectUri, state); + } + + public void redirectUser(HttpServletRequest request, User user, String redirectUri, String state) throws IOException + { String authCode = UUID.randomUUID().toString().replace("-", ""); - User user = new User(123456789, "Alice"); issuedAuthCodes.put(authCode, user); - final Request baseRequest = Request.getBaseRequest(req); - final Response baseResponse = baseRequest.getResponse(); - redirectUri += "?code=" + authCode + "&state=" + state; - int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() - ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER); - baseResponse.sendRedirect(redirectCode, resp.encodeRedirectURL(redirectUri)); + try + { + final Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request)); + final Response baseResponse = baseRequest.getResponse(); + redirectUri += "?code=" + authCode + "&state=" + state; + int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() + ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER); + baseResponse.sendRedirect(redirectCode, baseResponse.encodeRedirectURL(redirectUri)); + } + catch (Throwable t) + { + issuedAuthCodes.remove(authCode); + throw t; + } } } @@ -171,7 +280,7 @@ public class OpenIdProvider extends ContainerLifeCycle long expiry = System.currentTimeMillis() + Duration.ofMinutes(10).toMillis(); String response = "{" + "\"access_token\": \"" + accessToken + "\"," + - "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken()) + "\"," + + "\"id_token\": \"" + JwtEncoder.encode(user.getIdToken(provider, clientId)) + "\"," + "\"expires_in\": " + expiry + "," + "\"token_type\": \"Bearer\"" + "}"; @@ -184,7 +293,7 @@ public class OpenIdProvider extends ContainerLifeCycle public class OpenIdConfigServlet extends HttpServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String discoveryDocument = "{" + "\"issuer\": \"" + provider + "\"," + @@ -196,17 +305,17 @@ public class OpenIdProvider extends ContainerLifeCycle } } - public class User + public static class User { - private long subject; - private String name; + private final String subject; + private final String name; public User(String name) { - this(new Random().nextLong(), name); + this(UUID.nameUUIDFromBytes(name.getBytes()).toString(), name); } - public User(long subject, String name) + public User(String subject, String name) { this.subject = subject; this.name = name; @@ -217,10 +326,15 @@ public class OpenIdProvider extends ContainerLifeCycle return name; } - public String getIdToken() + public String getSubject() + { + return subject; + } + + public String getIdToken(String provider, String clientId) { long expiry = System.currentTimeMillis() + Duration.ofMinutes(1).toMillis(); - return JwtEncoder.createIdToken(provider, clientId, Long.toString(subject), name, expiry); + return JwtEncoder.createIdToken(provider, clientId, subject, name, expiry); } } -} +} \ No newline at end of file From deb4e203a40c3886f1de35cabfc79cdb8c56eff3 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 5 Nov 2021 10:45:15 +1100 Subject: [PATCH 2/5] Issue #7042 - add name arguments to jetty-openid.xml Signed-off-by: Lachlan Roberts --- .../src/main/config/etc/jetty-openid.xml | 16 ++++++++-------- .../jetty/security/openid/OpenIdProvider.java | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/jetty-openid/src/main/config/etc/jetty-openid.xml b/jetty-openid/src/main/config/etc/jetty-openid.xml index 5f973448877..0e6c2a06a23 100644 --- a/jetty-openid/src/main/config/etc/jetty-openid.xml +++ b/jetty-openid/src/main/config/etc/jetty-openid.xml @@ -28,13 +28,13 @@ - - - - - - - + + + + + + + @@ -48,4 +48,4 @@ - \ No newline at end of file + diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java index 90139d49f8a..3b68aad33aa 100644 --- a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdProvider.java @@ -337,4 +337,4 @@ public class OpenIdProvider extends ContainerLifeCycle return JwtEncoder.createIdToken(provider, clientId, subject, name, expiry); } } -} \ No newline at end of file +} From f12d6f5b6cf42db554b4ace44c58eff3f4d3c9eb Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 10 Nov 2021 10:22:50 +1100 Subject: [PATCH 3/5] Issue #7042 - Changes from review Signed-off-by: Lachlan Roberts --- .../security/openid/OpenIdAuthenticator.java | 26 +++++++++---------- .../openid/OpenIdAuthenticatorFactory.java | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java index de88f1d23ec..cbc30516820 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java @@ -80,7 +80,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator public static final String CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token"; private final SecureRandom _secureRandom = new SecureRandom(); - private OpenIdConfiguration _configuration; + private OpenIdConfiguration _openIdConfiguration; private String _redirectPath; private String _errorPage; private String _errorPath; @@ -104,32 +104,32 @@ public class OpenIdAuthenticator extends LoginAuthenticator public OpenIdAuthenticator(OpenIdConfiguration configuration, String redirectPath, String errorPage) { - _configuration = configuration; + _openIdConfiguration = configuration; setRedirectPath(redirectPath); if (errorPage != null) setErrorPage(errorPage); } @Override - public void setConfiguration(AuthConfiguration configuration) + public void setConfiguration(AuthConfiguration authConfig) { - if (_configuration == null) + if (_openIdConfiguration == null) { - LoginService loginService = configuration.getLoginService(); + LoginService loginService = authConfig.getLoginService(); if (!(loginService instanceof OpenIdLoginService)) throw new IllegalArgumentException("invalid LoginService " + loginService); - this._configuration = ((OpenIdLoginService)loginService).getConfiguration(); + this._openIdConfiguration = ((OpenIdLoginService)loginService).getConfiguration(); } - String redirectPath = configuration.getInitParameter(REDIRECT_PATH); + String redirectPath = authConfig.getInitParameter(REDIRECT_PATH); if (redirectPath != null) _redirectPath = redirectPath; - String error = configuration.getInitParameter(ERROR_PAGE); + String error = authConfig.getInitParameter(ERROR_PAGE); if (error != null) setErrorPage(error); - super.setConfiguration(new OpenIdAuthConfiguration(_configuration, configuration)); + super.setConfiguration(new OpenIdAuthConfiguration(_openIdConfiguration, authConfig)); } @Override @@ -209,7 +209,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached); session.setAttribute(CLAIMS, ((OpenIdCredentials)credentials).getClaims()); session.setAttribute(RESPONSE, ((OpenIdCredentials)credentials).getResponse()); - session.setAttribute(ISSUER, _configuration.getIssuer()); + session.setAttribute(ISSUER, _openIdConfiguration.getIssuer()); } } return user; @@ -520,13 +520,13 @@ public class OpenIdAuthenticator extends LoginAuthenticator // any custom scopes requested from configuration StringBuilder scopes = new StringBuilder(); - for (String s : _configuration.getScopes()) + for (String s : _openIdConfiguration.getScopes()) { scopes.append(" ").append(s); } - return _configuration.getAuthEndpoint() + - "?client_id=" + UrlEncoded.encodeString(_configuration.getClientId(), StandardCharsets.UTF_8) + + return _openIdConfiguration.getAuthEndpoint() + + "?client_id=" + UrlEncoded.encodeString(_openIdConfiguration.getClientId(), StandardCharsets.UTF_8) + "&redirect_uri=" + UrlEncoded.encodeString(getRedirectUri(request), StandardCharsets.UTF_8) + "&scope=openid" + UrlEncoded.encodeString(scopes.toString(), StandardCharsets.UTF_8) + "&state=" + antiForgeryToken + diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java index 7a20e021bdb..ff3860aa489 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java @@ -29,9 +29,9 @@ public class OpenIdAuthenticatorFactory implements Authenticator.Factory String auth = configuration.getAuthMethod(); if (Constraint.__OPENID_AUTH.equalsIgnoreCase(auth)) { - // If LoginService is an OpenIdLoginService it already contains the configuration and will be obtained in setConfiguration(); + // If we have an OpenIdLoginService we can extract the configuration. if (loginService instanceof OpenIdLoginService) - return new OpenIdAuthenticator(); + return new OpenIdAuthenticator(((OpenIdLoginService)loginService).getConfiguration()); // Otherwise we should find an OpenIdConfiguration for this realm on the Server. String realmName = configuration.getRealmName(); From 2a3c65c1641beef5fbd77f57c8edf8413c68ec1c Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 10 Nov 2021 18:16:22 +1100 Subject: [PATCH 4/5] Issue #7042 - if only 1 OpenIdConfig available select it regardless of realm name Signed-off-by: Lachlan Roberts --- .../src/main/config/modules/openid.mod | 2 +- .../openid/OpenIdAuthConfiguration.java | 10 +- .../openid/OpenIdAuthenticatorFactory.java | 12 +- .../security/openid/OpenIdReamNameTest.java | 184 ++++++++++++++++++ 4 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdReamNameTest.java diff --git a/jetty-openid/src/main/config/modules/openid.mod b/jetty-openid/src/main/config/modules/openid.mod index 7f04767a2d9..c1070b29910 100644 --- a/jetty-openid/src/main/config/modules/openid.mod +++ b/jetty-openid/src/main/config/modules/openid.mod @@ -20,7 +20,7 @@ etc/jetty-openid.xml [ini-template] ## The OpenID Identity Provider's issuer ID (the entire URL *before* ".well-known/openid-configuration") -# jetty.openid.provider=https://id.example.com/~ +# jetty.openid.provider=https://id.example.com/ ## The OpenID Identity Provider's authorization endpoint (optional if the metadata of the OP is accessible) # jetty.openid.provider.authorizationEndpoint=https://id.example.com/authorization diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java index 9d6de0e0821..ad1bf8ab7c0 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java @@ -13,17 +13,23 @@ package org.eclipse.jetty.security.openid; -import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.Authenticator.AuthConfiguration; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.WrappedAuthConfiguration; +/** + *

This class is used to wrap the {@link AuthConfiguration} given to the {@link OpenIdAuthenticator}.

+ *

When {@link #getLoginService()} method is called, this implementation will always return an instance of + * {@link OpenIdLoginService}. This allows you to configure an {@link OpenIdAuthenticator} using a {@code null} + * LoginService or any alternative LoginService implementation which will be wrapped by the OpenIdLoginService

+ */ public class OpenIdAuthConfiguration extends WrappedAuthConfiguration { public static final String AUTHENTICATE_NEW_USERS_INIT_PARAM = "jetty.openid.authenticateNewUsers"; private final OpenIdLoginService _openIdLoginService; - public OpenIdAuthConfiguration(OpenIdConfiguration openIdConfiguration, Authenticator.AuthConfiguration authConfiguration) + public OpenIdAuthConfiguration(OpenIdConfiguration openIdConfiguration, AuthConfiguration authConfiguration) { super(authConfiguration); diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java index ff3860aa489..bc75ae0f963 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticatorFactory.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.security.openid; +import java.util.Collection; import javax.servlet.ServletContext; import org.eclipse.jetty.security.Authenticator; @@ -34,8 +35,17 @@ public class OpenIdAuthenticatorFactory implements Authenticator.Factory return new OpenIdAuthenticator(((OpenIdLoginService)loginService).getConfiguration()); // Otherwise we should find an OpenIdConfiguration for this realm on the Server. + Collection configurations = server.getBeans(OpenIdConfiguration.class); + if (configurations == null || configurations.isEmpty()) + throw new IllegalStateException("No OpenIdConfiguration found"); + + // If only 1 configuration use that regardless of its realm name. + if (configurations.size() == 1) + return new OpenIdAuthenticator(configurations.iterator().next()); + + // If there are multiple configurations then select one matching the realm name. String realmName = configuration.getRealmName(); - OpenIdConfiguration openIdConfiguration = server.getBeans(OpenIdConfiguration.class).stream() + OpenIdConfiguration openIdConfiguration = configurations.stream() .filter(c -> c.getIssuer().equals(realmName)) .findAny() .orElseThrow(() -> new IllegalStateException("No OpenIdConfiguration found for realm \"" + realmName + "\"")); diff --git a/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdReamNameTest.java b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdReamNameTest.java new file mode 100644 index 00000000000..0b71697bed9 --- /dev/null +++ b/jetty-openid/src/test/java/org/eclipse/jetty/security/openid/OpenIdReamNameTest.java @@ -0,0 +1,184 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.openid; + +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.security.Constraint; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class OpenIdReamNameTest +{ + private final Server server = new Server(); + + public static ServletContextHandler configureOpenIdContext(String realmName) + { + ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); + assertThat(securityHandler.getKnownAuthenticatorFactories().size(), greaterThanOrEqualTo(2)); + securityHandler.setAuthMethod(Constraint.__OPENID_AUTH); + securityHandler.setRealmName(realmName); + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/" + realmName); + context.setSecurityHandler(securityHandler); + return context; + } + + @Test + public void testSingleConfiguration() throws Exception + { + // Add some OpenID configurations. + OpenIdConfiguration config1 = new OpenIdConfiguration("provider1", + "", "", "", "", null); + server.addBean(config1); + + // Configure two webapps to select configs based on realm name. + ServletContextHandler context1 = configureOpenIdContext("This doesn't matter if only 1 OpenIdConfiguration"); + ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + contextHandlerCollection.addHandler(context1); + server.setHandler(contextHandlerCollection); + + try + { + server.start(); + + // The OpenIdConfiguration from context1 matches to config1. + Authenticator authenticator = context1.getSecurityHandler().getAuthenticator(); + assertThat(authenticator, instanceOf(OpenIdAuthenticator.class)); + LoginService loginService = ((OpenIdAuthenticator)authenticator).getLoginService(); + assertThat(loginService, instanceOf(OpenIdLoginService.class)); + assertThat(((OpenIdLoginService)loginService).getConfiguration(), is(config1)); + } + finally + { + server.stop(); + } + } + + @Test + public void testSingleConfigurationNoRealmName() throws Exception + { + // Add some OpenID configurations. + OpenIdConfiguration config1 = new OpenIdConfiguration("provider1", + "", "", "", "", null); + server.addBean(config1); + + // Configure two webapps to select configs based on realm name. + ServletContextHandler context1 = configureOpenIdContext(null); + ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + contextHandlerCollection.addHandler(context1); + server.setHandler(contextHandlerCollection); + + try + { + server.start(); + + // The OpenIdConfiguration from context1 matches to config1. + Authenticator authenticator = context1.getSecurityHandler().getAuthenticator(); + assertThat(authenticator, instanceOf(OpenIdAuthenticator.class)); + LoginService loginService = ((OpenIdAuthenticator)authenticator).getLoginService(); + assertThat(loginService, instanceOf(OpenIdLoginService.class)); + assertThat(((OpenIdLoginService)loginService).getConfiguration(), is(config1)); + } + finally + { + server.stop(); + } + } + + @Test + public void testMultipleConfiguration() throws Exception + { + // Add some OpenID configurations. + OpenIdConfiguration config1 = new OpenIdConfiguration("provider1", + "", "", "", "", null); + OpenIdConfiguration config2 = new OpenIdConfiguration("provider2", + "", "", "", "", null); + server.addBean(config1); + server.addBean(config2); + + // Configure two webapps to select configs based on realm name. + ServletContextHandler context1 = configureOpenIdContext(config1.getIssuer()); + ServletContextHandler context2 = configureOpenIdContext(config2.getIssuer()); + ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + contextHandlerCollection.addHandler(context1); + contextHandlerCollection.addHandler(context2); + server.setHandler(contextHandlerCollection); + + try + { + server.start(); + + // The OpenIdConfiguration from context1 matches to config1. + Authenticator authenticator = context1.getSecurityHandler().getAuthenticator(); + assertThat(authenticator, instanceOf(OpenIdAuthenticator.class)); + LoginService loginService = ((OpenIdAuthenticator)authenticator).getLoginService(); + assertThat(loginService, instanceOf(OpenIdLoginService.class)); + assertThat(((OpenIdLoginService)loginService).getConfiguration(), is(config1)); + + // The OpenIdConfiguration from context2 matches to config2. + authenticator = context2.getSecurityHandler().getAuthenticator(); + assertThat(authenticator, instanceOf(OpenIdAuthenticator.class)); + loginService = ((OpenIdAuthenticator)authenticator).getLoginService(); + assertThat(loginService, instanceOf(OpenIdLoginService.class)); + assertThat(((OpenIdLoginService)loginService).getConfiguration(), is(config2)); + } + finally + { + server.stop(); + } + } + + @Test + public void testMultipleConfigurationNoMatch() throws Exception + { + // Add some OpenID configurations. + OpenIdConfiguration config1 = new OpenIdConfiguration("provider1", + "", "", "", "", null); + OpenIdConfiguration config2 = new OpenIdConfiguration("provider2", + "", "", "", "", null); + server.addBean(config1); + server.addBean(config2); + + // Configure two webapps to select configs based on realm name. + ServletContextHandler context1 = configureOpenIdContext("provider3"); + ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + contextHandlerCollection.addHandler(context1); + server.setHandler(contextHandlerCollection); + + // Multiple OpenIdConfigurations were available and didn't match one based on realm name. + assertThrows(IllegalStateException.class, server::start); + } + + @Test + public void testNoConfiguration() throws Exception + { + ServletContextHandler context1 = configureOpenIdContext(null); + ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + contextHandlerCollection.addHandler(context1); + server.setHandler(contextHandlerCollection); + + // If no OpenIdConfigurations are present it is bad configuration. + assertThrows(IllegalStateException.class, server::start); + } +} From 2a5197216d7c35a9b40914a2c611022a51a8acb5 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 10 Nov 2021 18:19:51 +1100 Subject: [PATCH 5/5] Issue #7042 - remove init param to set authenticateNewUsers per webapp Signed-off-by: Lachlan Roberts --- .../jetty/security/openid/OpenIdAuthConfiguration.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java index ad1bf8ab7c0..9c1ba33e696 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthConfiguration.java @@ -25,8 +25,6 @@ import org.eclipse.jetty.security.WrappedAuthConfiguration; */ public class OpenIdAuthConfiguration extends WrappedAuthConfiguration { - public static final String AUTHENTICATE_NEW_USERS_INIT_PARAM = "jetty.openid.authenticateNewUsers"; - private final OpenIdLoginService _openIdLoginService; public OpenIdAuthConfiguration(OpenIdConfiguration openIdConfiguration, AuthConfiguration authConfiguration) @@ -43,10 +41,6 @@ public class OpenIdAuthConfiguration extends WrappedAuthConfiguration _openIdLoginService = new OpenIdLoginService(openIdConfiguration, loginService); if (loginService == null) _openIdLoginService.setIdentityService(authConfiguration.getIdentityService()); - - String authNewUsers = authConfiguration.getInitParameter(AUTHENTICATE_NEW_USERS_INIT_PARAM); - if (authNewUsers != null) - _openIdLoginService.setAuthenticateNewUsers(Boolean.parseBoolean(authNewUsers)); } }