Issue #7042 - if only 1 OpenIdConfig available select it regardless of realm name

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2021-11-10 18:16:22 +11:00
parent f12d6f5b6c
commit 2a3c65c164
4 changed files with 204 additions and 4 deletions

View File

@ -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

View File

@ -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;
/**
* <p>This class is used to wrap the {@link AuthConfiguration} given to the {@link OpenIdAuthenticator}.</p>
* <p>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</p>
*/
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);

View File

@ -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<OpenIdConfiguration> 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 + "\""));

View File

@ -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);
}
}