Skip to content

Commit

Permalink
Issue #5442 - combined login page for MultiAuthenticator
Browse files Browse the repository at this point in the history
Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
  • Loading branch information
lachlan-roberts committed Oct 16, 2024
1 parent 784afa2 commit 9d0d9c4
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,28 @@
import java.util.function.Function;

import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Session;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MultiAuthenticator extends LoginAuthenticator
{
private static final Logger LOG = LoggerFactory.getLogger(MultiAuthenticator.class);
public static final String LOGIN_PATH_PARAM = "org.eclipse.jetty.security.multi.login_path";

private static final String AUTH_STATE_ATTR = MultiAuthState.class.getName();
private static final DefaultAuthenticator DEFAULT_AUTHENTICATOR = new DefaultAuthenticator();
private final DefaultAuthenticator _defaultAuthenticator = new DefaultAuthenticator();
private final PathMappings<Authenticator> _authenticatorsMappings = new PathMappings<>();
private String _loginPath;
private boolean _dispatch;

public void addAuthenticator(String pathSpec, Authenticator authenticator)
{
Expand All @@ -30,12 +35,52 @@ public void addAuthenticator(String pathSpec, Authenticator authenticator)
@Override
public void setConfiguration(Configuration configuration)
{
String loginPath = configuration.getParameter(LOGIN_PATH_PARAM);
if (loginPath != null)
setLoginPath(loginPath);

for (Authenticator authenticator : _authenticatorsMappings.values())
{
authenticator.setConfiguration(configuration);
}
}

public void setLoginPath(String loginPath)
{
if (loginPath != null)
{
if (!loginPath.startsWith("/"))
{
LOG.warn("login path must start with /");
loginPath = "/" + loginPath;
}

_loginPath = loginPath;
}
}

public boolean isLoginPage(String uri)
{
return matchURI(uri, _loginPath);
}

private boolean matchURI(String uri, String path)
{
int jsc = uri.indexOf(path);
if (jsc < 0)
return false;
int e = jsc + path.length();
if (e == uri.length())
return true;
char c = uri.charAt(e);
return c == ';' || c == '#' || c == '/' || c == '?';
}

public void setDispatch(boolean dispatch)
{
_dispatch = dispatch;
}

@Override
public String getAuthenticationType()
{
Expand All @@ -47,7 +92,11 @@ public UserIdentity login(String username, Object password, Request request, Res
{
Authenticator authenticator = getAuthenticator(request.getSession(false));
if (authenticator instanceof LoginAuthenticator loginAuthenticator)
{
doLogin(request);
return loginAuthenticator.login(username, password, request, response);
}

return super.login(username, password, request, response);
}

Expand All @@ -56,9 +105,12 @@ public void logout(Request request, Response response)
{
Authenticator authenticator = getAuthenticator(request.getSession(false));
if (authenticator instanceof LoginAuthenticator loginAuthenticator)
{
loginAuthenticator.logout(request, response);
else
super.logout(request, response);
doLogout(request);
}

super.logout(request, response);
}

@Override
Expand All @@ -80,7 +132,7 @@ public Constraint.Authorization getConstraintAuthentication(String pathInContext
if (authenticator == null)
authenticator = getAuthenticator(session);
if (authenticator == null)
authenticator = DEFAULT_AUTHENTICATOR;
authenticator = _defaultAuthenticator;
saveAuthenticator(session, authenticator);
return authenticator.getConstraintAuthentication(pathInContext, existing, getSession);
}
Expand All @@ -98,15 +150,14 @@ public AuthenticationState validateRequest(Request request, Response response, C

AuthenticationState authenticationState = authenticator.validateRequest(request, response, callback);
if (authenticationState instanceof AuthenticationState.ResponseSent)
{
if (authenticationState instanceof LoginAuthenticator.UserAuthenticationSent)
doLogin(request);
return authenticationState;
}

// Wrap the successful authentication state to intercept the logout request to clear the session attribute.
if (authenticationState instanceof AuthenticationState.Succeeded succeededState)
{
if (succeededState instanceof LoginAuthenticator.UserAuthenticationSent)
doLogin(request);
return new MultiSucceededAuthenticationState(succeededState);
}
else if (authenticationState instanceof AuthenticationState.Deferred deferredState)
return new MultiDelegateAuthenticationState(deferredState);
return authenticationState;
Expand Down Expand Up @@ -213,9 +264,8 @@ public void logout(Request request, Response response)
}
}

private static class DefaultAuthenticator implements Authenticator
private class DefaultAuthenticator implements Authenticator
{

@Override
public void setConfiguration(Configuration configuration)
{
Expand All @@ -227,9 +277,33 @@ public String getAuthenticationType()
return "DEFAULT";
}

@Override
public Constraint.Authorization getConstraintAuthentication(String pathInContext, Constraint.Authorization existing, Function<Boolean, Session> getSession)
{
if (isLoginPage(pathInContext))
return Constraint.Authorization.ALLOWED;
return existing;
}

@Override
public AuthenticationState validateRequest(Request request, Response response, Callback callback)
{
if (_loginPath != null)
{
String loginPath = URIUtil.addPaths(request.getContext().getContextPath(), _loginPath);
if (_dispatch)
{
HttpURI.Mutable newUri = HttpURI.build(request.getHttpURI()).pathQuery(loginPath);
return new AuthenticationState.ServeAs(newUri);
}
else
{
Session session = request.getSession(true);
String redirectUri = session.encodeURI(request, loginPath, true);
Response.sendRedirect(request, response, callback, redirectUri, true);
return AuthenticationState.CHALLENGE;
}
}
return null;
}
}
Expand Down Expand Up @@ -316,8 +390,8 @@ private Authenticator getAuthenticator(Session session)
return null;

String name = state.getAuthenticatorName();
if (DEFAULT_AUTHENTICATOR.getClass().getName().equals(name))
return DEFAULT_AUTHENTICATOR;
if (_defaultAuthenticator.getClass().getName().equals(name))
return _defaultAuthenticator;
for (Authenticator authenticator : _authenticatorsMappings.values())
{
if (name.equals(authenticator.getClass().getName()))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
//
// ========================================================================
// 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.test;

import java.io.PrintWriter;
Expand Down

0 comments on commit 9d0d9c4

Please sign in to comment.