Issue #4235 - communicate reason of OpenID auth failure to error page
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
e913ed233a
commit
4bc32e314b
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Objects;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -74,10 +75,12 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
public static final String J_METHOD = "org.eclipse.jetty.security.openid.METHOD";
|
public static final String J_METHOD = "org.eclipse.jetty.security.openid.METHOD";
|
||||||
public static final String CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token";
|
public static final String CSRF_TOKEN = "org.eclipse.jetty.security.openid.csrf_token";
|
||||||
public static final String J_SECURITY_CHECK = "/j_security_check";
|
public static final String J_SECURITY_CHECK = "/j_security_check";
|
||||||
|
public static final String ERROR_PARAMETER = "error_description_jetty";
|
||||||
|
|
||||||
private OpenIdConfiguration _configuration;
|
private OpenIdConfiguration _configuration;
|
||||||
private String _errorPage;
|
private String _errorPage;
|
||||||
private String _errorPath;
|
private String _errorPath;
|
||||||
|
private String _errorQuery;
|
||||||
private boolean _alwaysSaveUri;
|
private boolean _alwaysSaveUri;
|
||||||
|
|
||||||
public OpenIdAuthenticator()
|
public OpenIdAuthenticator()
|
||||||
|
@ -148,9 +151,14 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
}
|
}
|
||||||
_errorPage = path;
|
_errorPage = path;
|
||||||
_errorPath = path;
|
_errorPath = path;
|
||||||
|
_errorQuery = "";
|
||||||
|
|
||||||
if (_errorPath.indexOf('?') > 0)
|
int queryIndex = _errorPath.indexOf('?');
|
||||||
_errorPath = _errorPath.substring(0, _errorPath.indexOf('?'));
|
if (queryIndex > 0)
|
||||||
|
{
|
||||||
|
_errorPath = _errorPage.substring(0, queryIndex);
|
||||||
|
_errorQuery = _errorPage.substring(queryIndex + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +238,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
{
|
{
|
||||||
final HttpServletRequest request = (HttpServletRequest)req;
|
final HttpServletRequest request = (HttpServletRequest)req;
|
||||||
final HttpServletResponse response = (HttpServletResponse)res;
|
final HttpServletResponse response = (HttpServletResponse)res;
|
||||||
final Request baseRequest = Request.getBaseRequest(request);
|
final Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
|
||||||
final Response baseResponse = baseRequest.getResponse();
|
final Response baseResponse = baseRequest.getResponse();
|
||||||
|
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
@ -246,12 +254,11 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Get the Session.
|
||||||
|
HttpSession session = request.getSession();
|
||||||
if (request.isRequestedSessionIdFromURL())
|
if (request.isRequestedSessionIdFromURL())
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
sendError(request, response, "Session ID must be a cookie to support OpenID authentication");
|
||||||
LOG.debug("Session ID should be cookie for OpenID authentication to work");
|
|
||||||
|
|
||||||
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), URIUtil.addPaths(request.getContextPath(), _errorPage));
|
|
||||||
return Authentication.SEND_FAILURE;
|
return Authentication.SEND_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,19 +270,16 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
{
|
{
|
||||||
// Verify anti-forgery state token
|
// Verify anti-forgery state token
|
||||||
String state = request.getParameter("state");
|
String state = request.getParameter("state");
|
||||||
String antiForgeryToken = (String)request.getSession().getAttribute(CSRF_TOKEN);
|
String antiForgeryToken = (String)session.getAttribute(CSRF_TOKEN);
|
||||||
if (antiForgeryToken == null || !antiForgeryToken.equals(state))
|
if (antiForgeryToken == null || !antiForgeryToken.equals(state))
|
||||||
{
|
{
|
||||||
LOG.warn("auth failed 403: invalid state parameter");
|
sendError(request, response, "auth failed: invalid state parameter");
|
||||||
if (response != null)
|
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
return Authentication.SEND_FAILURE;
|
return Authentication.SEND_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to login with the provided authCode
|
// Attempt to login with the provided authCode
|
||||||
OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration);
|
OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration);
|
||||||
UserIdentity user = login(null, credentials, request);
|
UserIdentity user = login(null, credentials, request);
|
||||||
HttpSession session = request.getSession(false);
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
// Redirect to original request
|
// Redirect to original request
|
||||||
|
@ -301,29 +305,13 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// not authenticated
|
// Not authenticated.
|
||||||
if (LOG.isDebugEnabled())
|
sendError(request, response, null);
|
||||||
LOG.debug("OpenId authentication FAILED");
|
|
||||||
if (_errorPage == null)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("auth failed 403");
|
|
||||||
if (response != null)
|
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("auth failed {}", _errorPage);
|
|
||||||
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), URIUtil.addPaths(request.getContextPath(), _errorPage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Authentication.SEND_FAILURE;
|
return Authentication.SEND_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for cached authentication
|
// Look for cached authentication in the Session.
|
||||||
HttpSession session = request.getSession(false);
|
Authentication authentication = (Authentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
|
||||||
Authentication authentication = session == null ? null : (Authentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
|
|
||||||
if (authentication != null)
|
if (authentication != null)
|
||||||
{
|
{
|
||||||
// Has authentication been revoked?
|
// Has authentication been revoked?
|
||||||
|
@ -371,16 +359,15 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we can't send challenge
|
// If we can't send challenge.
|
||||||
if (DeferredAuthentication.isDeferred(response))
|
if (DeferredAuthentication.isDeferred(response))
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("auth deferred {}", session == null ? null : session.getId());
|
LOG.debug("auth deferred {}", session.getId());
|
||||||
return Authentication.UNAUTHENTICATED;
|
return Authentication.UNAUTHENTICATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember the current URI
|
// Remember the current URI.
|
||||||
session = (session != null ? session : request.getSession(true));
|
|
||||||
synchronized (session)
|
synchronized (session)
|
||||||
{
|
{
|
||||||
// But only if it is not set already, or we save every uri that leads to a login redirect
|
// But only if it is not set already, or we save every uri that leads to a login redirect
|
||||||
|
@ -401,7 +388,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the the challenge
|
// Send the the challenge.
|
||||||
String challengeUri = getChallengeUri(request);
|
String challengeUri = getChallengeUri(request);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("challenge {}->{}", session.getId(), challengeUri);
|
LOG.debug("challenge {}->{}", session.getId(), challengeUri);
|
||||||
|
@ -415,6 +402,47 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error case either by redirecting to the error page if it is defined, otherwise sending a 403 response.
|
||||||
|
* If the message parameter is not null, a query parameter with a key of {@link #ERROR_PARAMETER} and value of the error
|
||||||
|
* message will be logged and added to the error redirect URI if the error page is defined.
|
||||||
|
* @param request the request.
|
||||||
|
* @param response the response.
|
||||||
|
* @param message the reason for the error or null.
|
||||||
|
* @throws IOException if sending the error fails for any reason.
|
||||||
|
*/
|
||||||
|
private void sendError(HttpServletRequest request, HttpServletResponse response, String message) throws IOException
|
||||||
|
{
|
||||||
|
final Request baseRequest = Request.getBaseRequest(request);
|
||||||
|
final Response baseResponse = Objects.requireNonNull(baseRequest).getResponse();
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("OpenId authentication FAILED: {}", message);
|
||||||
|
|
||||||
|
if (_errorPage == null)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("auth failed 403");
|
||||||
|
if (response != null)
|
||||||
|
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("auth failed {}", _errorPage);
|
||||||
|
|
||||||
|
String redirectUri = URIUtil.addPaths(request.getContextPath(), _errorPage);
|
||||||
|
if (message != null)
|
||||||
|
{
|
||||||
|
String query = URIUtil.combineQueryParams(ERROR_PARAMETER + "=" + UrlEncoded.encodeString(message), _errorQuery);
|
||||||
|
redirectUri = URIUtil.addPaths(request.getContextPath(), _errorPath) + "?" + query;
|
||||||
|
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), redirectUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isJSecurityCheck(String uri)
|
public boolean isJSecurityCheck(String uri)
|
||||||
{
|
{
|
||||||
int jsc = uri.indexOf(J_SECURITY_CHECK);
|
int jsc = uri.indexOf(J_SECURITY_CHECK);
|
||||||
|
|
|
@ -1305,6 +1305,28 @@ public class URIUtil
|
||||||
return URI.create(buf.toString());
|
return URI.create(buf.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine two query strings into one. Each query string should not contain the beginning '?' character, but
|
||||||
|
* may contain multiple parameters separated by the '{@literal &}' character.
|
||||||
|
* @param query1 the first query string.
|
||||||
|
* @param query2 the second query string.
|
||||||
|
* @return the combination of the two query strings.
|
||||||
|
*/
|
||||||
|
public static String combineQueryParams(String query1, String query2)
|
||||||
|
{
|
||||||
|
StringBuilder queryBuilder = new StringBuilder();
|
||||||
|
if (!StringUtil.isEmpty(query1))
|
||||||
|
{
|
||||||
|
queryBuilder.append(query1);
|
||||||
|
if (!StringUtil.isEmpty(query2))
|
||||||
|
queryBuilder.append("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtil.isEmpty(query2))
|
||||||
|
queryBuilder.append(query2);
|
||||||
|
return queryBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static URI getJarSource(URI uri)
|
public static URI getJarSource(URI uri)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.condition.OS;
|
import org.junit.jupiter.api.condition.OS;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -709,4 +710,26 @@ public class URIUtilTest
|
||||||
{
|
{
|
||||||
assertThat(URIUtil.getUriLastPathSegment(uri), is(expectedName));
|
assertThat(URIUtil.getUriLastPathSegment(uri), is(expectedName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Stream<Arguments> addQueryParameterSource()
|
||||||
|
{
|
||||||
|
final String newQueryParam = "newParam=11";
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(null, newQueryParam, is(newQueryParam)),
|
||||||
|
Arguments.of(newQueryParam, null, is(newQueryParam)),
|
||||||
|
Arguments.of("", newQueryParam, is(newQueryParam)),
|
||||||
|
Arguments.of(newQueryParam, "", is(newQueryParam)),
|
||||||
|
Arguments.of("existingParam=3", newQueryParam, is("existingParam=3&" + newQueryParam)),
|
||||||
|
Arguments.of(newQueryParam, "existingParam=3", is(newQueryParam + "&existingParam=3")),
|
||||||
|
Arguments.of("existingParam1=value1&existingParam2=value2", newQueryParam, is("existingParam1=value1&existingParam2=value2&" + newQueryParam)),
|
||||||
|
Arguments.of(newQueryParam, "existingParam1=value1&existingParam2=value2", is(newQueryParam + "&existingParam1=value1&existingParam2=value2"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("addQueryParameterSource")
|
||||||
|
public void testAddQueryParam(String param1, String param2, Matcher<String> matcher)
|
||||||
|
{
|
||||||
|
assertThat(URIUtil.combineQueryParams(param1, param2), matcher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue