Implement ServeAs in core SecurityHandler
This commit is contained in:
parent
798f455e6f
commit
db5209e97a
|
@ -17,6 +17,7 @@ import java.security.Principal;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpException;
|
import org.eclipse.jetty.http.HttpException;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.security.IdentityService.RunAsToken;
|
import org.eclipse.jetty.security.IdentityService.RunAsToken;
|
||||||
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
|
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
|
||||||
import org.eclipse.jetty.security.internal.DeferredAuthenticationState;
|
import org.eclipse.jetty.security.internal.DeferredAuthenticationState;
|
||||||
|
@ -270,17 +271,30 @@ public interface AuthenticationState extends Request.AuthenticationState
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
class ServeAs implements AuthenticationState
|
||||||
* Authentication should be deferred, and request allowed to bypass security constraint.
|
{
|
||||||
*/
|
private final HttpURI _uri;
|
||||||
AuthenticationState DEFER = new AuthenticationState()
|
|
||||||
|
public ServeAs(HttpURI uri)
|
||||||
|
{
|
||||||
|
_uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request wrap(Request request)
|
||||||
|
{
|
||||||
|
if (request.getHttpURI().equals(_uri))
|
||||||
|
return request;
|
||||||
|
|
||||||
|
return new Request.Wrapper(request)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public HttpURI getHttpURI()
|
||||||
{
|
{
|
||||||
return "DEFER";
|
return _uri;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Deferred defer(LoginAuthenticator loginAuthenticator)
|
static Deferred defer(LoginAuthenticator loginAuthenticator)
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,8 +24,12 @@ import java.util.Map;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.http.pathmap.MappedResource;
|
import org.eclipse.jetty.http.pathmap.MappedResource;
|
||||||
import org.eclipse.jetty.http.pathmap.PathMappings;
|
import org.eclipse.jetty.http.pathmap.PathMappings;
|
||||||
import org.eclipse.jetty.http.pathmap.PathSpec;
|
import org.eclipse.jetty.http.pathmap.PathSpec;
|
||||||
|
@ -489,14 +493,51 @@ public abstract class SecurityHandler extends Handler.Wrapper implements Configu
|
||||||
if (authenticationState instanceof AuthenticationState.ResponseSent)
|
if (authenticationState instanceof AuthenticationState.ResponseSent)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (mustValidate && isNotAuthorized(constraint, authenticationState))
|
if (authenticationState instanceof AuthenticationState.ServeAs serveAs)
|
||||||
|
{
|
||||||
|
HttpURI uri = request.getHttpURI();
|
||||||
|
request = serveAs.wrap(request);
|
||||||
|
if (!uri.equals(request.getHttpURI()))
|
||||||
|
{
|
||||||
|
// URI is replaced, so filter out all metadata for the old URI
|
||||||
|
response.getHeaders().put(HttpHeader.CACHE_CONTROL.asString(), HttpHeaderValue.NO_CACHE.asString());
|
||||||
|
response.getHeaders().putDate(HttpHeader.EXPIRES.asString(), 1);
|
||||||
|
HttpFields.Mutable headers = new HttpFields.Mutable.Wrapper(response.getHeaders())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public HttpField onAddField(HttpField field)
|
||||||
|
{
|
||||||
|
if (field.getHeader() == null)
|
||||||
|
return field;
|
||||||
|
return switch (field.getHeader())
|
||||||
|
{
|
||||||
|
case CACHE_CONTROL, PRAGMA, ETAG, EXPIRES, LAST_MODIFIED, AGE -> null;
|
||||||
|
default -> field;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
response = new Response.Wrapper(request, response)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public HttpFields.Mutable getHeaders()
|
||||||
|
{
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticationState = _deferred;
|
||||||
|
}
|
||||||
|
else if (mustValidate && !isAuthorized(constraint, authenticationState))
|
||||||
{
|
{
|
||||||
Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "!authorized");
|
Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "!authorized");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (authenticationState == null)
|
||||||
if (authenticationState == null || authenticationState == AuthenticationState.DEFER)
|
{
|
||||||
authenticationState = _deferred;
|
authenticationState = _deferred;
|
||||||
|
}
|
||||||
|
|
||||||
AuthenticationState.setAuthenticationState(request, authenticationState);
|
AuthenticationState.setAuthenticationState(request, authenticationState);
|
||||||
IdentityService.Association association =
|
IdentityService.Association association =
|
||||||
|
@ -553,23 +594,20 @@ public abstract class SecurityHandler extends Handler.Wrapper implements Configu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isNotAuthorized(Constraint constraint, AuthenticationState authenticationState)
|
protected boolean isAuthorized(Constraint constraint, AuthenticationState authenticationState)
|
||||||
{
|
{
|
||||||
if (authenticationState == AuthenticationState.DEFER)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
UserIdentity userIdentity = authenticationState instanceof AuthenticationState.Succeeded user ? user.getUserIdentity() : null;
|
UserIdentity userIdentity = authenticationState instanceof AuthenticationState.Succeeded user ? user.getUserIdentity() : null;
|
||||||
return switch (constraint.getAuthorization())
|
return switch (constraint.getAuthorization())
|
||||||
{
|
{
|
||||||
case FORBIDDEN, ALLOWED, INHERIT -> false;
|
case FORBIDDEN, ALLOWED, INHERIT -> true;
|
||||||
case ANY_USER -> userIdentity == null || userIdentity.getUserPrincipal() == null;
|
case ANY_USER -> userIdentity == null || userIdentity.getUserPrincipal() == null;
|
||||||
case KNOWN_ROLE ->
|
case KNOWN_ROLE ->
|
||||||
{
|
{
|
||||||
if (userIdentity != null && userIdentity.getUserPrincipal() != null)
|
if (userIdentity != null && userIdentity.getUserPrincipal() != null)
|
||||||
for (String role : getKnownRoles())
|
for (String role : getKnownRoles())
|
||||||
if (userIdentity.isUserInRole(role))
|
if (userIdentity.isUserInRole(role))
|
||||||
yield false;
|
|
||||||
yield true;
|
yield true;
|
||||||
|
yield false;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SPECIFIC_ROLE ->
|
case SPECIFIC_ROLE ->
|
||||||
|
@ -577,8 +615,8 @@ public abstract class SecurityHandler extends Handler.Wrapper implements Configu
|
||||||
if (userIdentity != null && userIdentity.getUserPrincipal() != null)
|
if (userIdentity != null && userIdentity.getUserPrincipal() != null)
|
||||||
for (String role : constraint.getRoles())
|
for (String role : constraint.getRoles())
|
||||||
if (userIdentity.isUserInRole(role))
|
if (userIdentity.isUserInRole(role))
|
||||||
yield false;
|
|
||||||
yield true;
|
yield true;
|
||||||
|
yield false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,11 +197,6 @@ public class ServletChannel
|
||||||
return _httpInput;
|
return _httpInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServletContextHandler.ServletContextApi getServletContextContext()
|
|
||||||
{
|
|
||||||
return _servletContextApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSendError()
|
public boolean isSendError()
|
||||||
{
|
{
|
||||||
return _state.isSendError();
|
return _state.isSendError();
|
||||||
|
@ -458,24 +453,6 @@ public class ServletChannel
|
||||||
_expects100Continue = false;
|
_expects100Continue = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>When this is called the initial dispatch will use the {@link ServletChannel#FORWARD_PATH},
|
|
||||||
* {@link ServletChannel#FORWARD_REQUEST} and {@link ServletChannel#FORWARD_RESPONSE} attributes
|
|
||||||
* to do a {@link jakarta.servlet.DispatcherType#FORWARD} dispatch instead of the initial
|
|
||||||
* {@link jakarta.servlet.DispatcherType#REQUEST} dispatch.</p>
|
|
||||||
*
|
|
||||||
* <p>This must only be called before {@link ServletChannel#handle()} is first invoked.
|
|
||||||
* This can be used to dispatch to a different target before the initial request has been dispatched.</p>
|
|
||||||
*/
|
|
||||||
public void forward(String path, HttpServletRequest request, HttpServletResponse response)
|
|
||||||
{
|
|
||||||
ServletContextRequest contextRequest = getServletContextRequest();
|
|
||||||
contextRequest.setAttribute(FORWARD_PATH, path);
|
|
||||||
contextRequest.setAttribute(FORWARD_REQUEST, request);
|
|
||||||
contextRequest.setAttribute(FORWARD_RESPONSE, response);
|
|
||||||
_state.initialDispatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the servlet request. This is called on the initial dispatch and then again on any asynchronous events.
|
* Handle the servlet request. This is called on the initial dispatch and then again on any asynchronous events.
|
||||||
* @return True if the channel is ready to continue handling (ie it is not suspended)
|
* @return True if the channel is ready to continue handling (ie it is not suspended)
|
||||||
|
@ -516,13 +493,6 @@ public class ServletChannel
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case FORWARD:
|
|
||||||
{
|
|
||||||
reopen();
|
|
||||||
initialForward();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ASYNC_DISPATCH:
|
case ASYNC_DISPATCH:
|
||||||
{
|
{
|
||||||
reopen();
|
reopen();
|
||||||
|
|
|
@ -62,7 +62,6 @@ public class ServletChannelState
|
||||||
public enum State
|
public enum State
|
||||||
{
|
{
|
||||||
IDLE, // Idle request
|
IDLE, // Idle request
|
||||||
FORWARD, // forward() has been called.
|
|
||||||
HANDLING, // Request dispatched to filter/servlet or Async IO callback
|
HANDLING, // Request dispatched to filter/servlet or Async IO callback
|
||||||
WAITING, // Suspended and waiting
|
WAITING, // Suspended and waiting
|
||||||
WOKEN, // Dispatch to handle from ASYNC_WAIT
|
WOKEN, // Dispatch to handle from ASYNC_WAIT
|
||||||
|
@ -125,7 +124,6 @@ public class ServletChannelState
|
||||||
public enum Action
|
public enum Action
|
||||||
{
|
{
|
||||||
DISPATCH, // handle a normal request dispatch
|
DISPATCH, // handle a normal request dispatch
|
||||||
FORWARD, // initial request will be forwarded
|
|
||||||
ASYNC_DISPATCH, // handle an async request dispatch
|
ASYNC_DISPATCH, // handle an async request dispatch
|
||||||
SEND_ERROR, // Generate an error page or error dispatch
|
SEND_ERROR, // Generate an error page or error dispatch
|
||||||
ASYNC_ERROR, // handle an async error
|
ASYNC_ERROR, // handle an async error
|
||||||
|
@ -324,21 +322,6 @@ public class ServletChannelState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialDispatch()
|
|
||||||
{
|
|
||||||
try (AutoLock ignored = lock())
|
|
||||||
{
|
|
||||||
switch (_state)
|
|
||||||
{
|
|
||||||
case IDLE:
|
|
||||||
_state = State.FORWARD;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException(getStatusStringLocked());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Next handling of the request should proceed
|
* @return Next handling of the request should proceed
|
||||||
*/
|
*/
|
||||||
|
@ -358,13 +341,6 @@ public class ServletChannelState
|
||||||
_state = State.HANDLING;
|
_state = State.HANDLING;
|
||||||
return Action.DISPATCH;
|
return Action.DISPATCH;
|
||||||
|
|
||||||
case FORWARD:
|
|
||||||
if (_requestState != RequestState.BLOCKING)
|
|
||||||
throw new IllegalStateException(getStatusStringLocked());
|
|
||||||
_initial = true;
|
|
||||||
_state = State.HANDLING;
|
|
||||||
return Action.FORWARD;
|
|
||||||
|
|
||||||
case WOKEN:
|
case WOKEN:
|
||||||
if (_event != null && _event.getThrowable() != null && !_sendError)
|
if (_event != null && _event.getThrowable() != null && !_sendError)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.http.HttpCookie;
|
import org.eclipse.jetty.http.HttpCookie;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.http.UriCompliance;
|
import org.eclipse.jetty.http.UriCompliance;
|
||||||
import org.eclipse.jetty.http.pathmap.MatchedResource;
|
import org.eclipse.jetty.http.pathmap.MatchedResource;
|
||||||
import org.eclipse.jetty.server.FormFields;
|
import org.eclipse.jetty.server.FormFields;
|
||||||
|
@ -116,6 +117,39 @@ public class ServletContextRequest extends ContextRequest implements ServletCont
|
||||||
addIdleTimeoutListener(_servletChannel.getServletRequestState()::onIdleTimeout);
|
addIdleTimeoutListener(_servletChannel.getServletRequestState()::onIdleTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Request serveAs(Request request, String path)
|
||||||
|
{
|
||||||
|
MatchedResource<ServletHandler.MappedServlet> matchedResource = getServletContextHandler().getServletHandler().getMatchedServlet(path);
|
||||||
|
|
||||||
|
if (matchedResource == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ServletHandler.MappedServlet mappedServlet = matchedResource.getResource();
|
||||||
|
if (mappedServlet == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ServletChannel servletChannel = getServletChannel();
|
||||||
|
|
||||||
|
HttpURI uri = HttpURI.build(request.getHttpURI()).path(path).asImmutable();
|
||||||
|
|
||||||
|
ServletContextRequest servletContextRequest = getServletContextHandler().newServletContextRequest(
|
||||||
|
servletChannel,
|
||||||
|
new Request.Wrapper(request)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public HttpURI getHttpURI()
|
||||||
|
{
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_response,
|
||||||
|
path,
|
||||||
|
matchedResource
|
||||||
|
);
|
||||||
|
servletChannel.associate(servletContextRequest);
|
||||||
|
return servletContextRequest;
|
||||||
|
}
|
||||||
|
|
||||||
protected ServletApiRequest newServletApiRequest()
|
protected ServletApiRequest newServletApiRequest()
|
||||||
{
|
{
|
||||||
if (getHttpURI().hasViolations() && !getServletChannel().getServletContextHandler().getServletHandler().isDecodeAmbiguousURIs())
|
if (getHttpURI().hasViolations() && !getServletChannel().getServletContextHandler().getServletHandler().isDecodeAmbiguousURIs())
|
||||||
|
|
|
@ -23,7 +23,6 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpServletResponseWrapper;
|
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||||
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
|
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
|
||||||
import org.eclipse.jetty.security.AuthenticationState;
|
import org.eclipse.jetty.security.AuthenticationState;
|
||||||
import org.eclipse.jetty.security.authentication.SessionAuthentication;
|
import org.eclipse.jetty.security.authentication.SessionAuthentication;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
|
@ -90,15 +89,19 @@ public class FormAuthenticator extends org.eclipse.jetty.security.authentication
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
response.getHeaders().put(HttpHeader.CACHE_CONTROL.asString(), HttpHeaderValue.NO_CACHE.asString());
|
// Currently we do not attempt to wrap the request
|
||||||
response.getHeaders().putDate(HttpHeader.EXPIRES.asString(), 1);
|
return new AuthenticationState.ServeAs(request.getHttpURI())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Request wrap(Request request)
|
||||||
|
{
|
||||||
|
ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
|
||||||
|
if (servletContextRequest == null)
|
||||||
|
return super.wrap(request);
|
||||||
|
|
||||||
ServletContextRequest contextRequest = Request.as(request, ServletContextRequest.class);
|
return servletContextRequest.serveAs(request, path);
|
||||||
FormRequest formRequest = new FormRequest(contextRequest.getServletApiRequest());
|
}
|
||||||
FormResponse formResponse = new FormResponse(contextRequest.getHttpServletResponse());
|
};
|
||||||
contextRequest.getServletChannel().forward(path, formRequest, formResponse);
|
|
||||||
|
|
||||||
return AuthenticationState.DEFER;
|
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
|
|
|
@ -87,7 +87,7 @@ public class FormAuthenticatorTest
|
||||||
{
|
{
|
||||||
String response = _connector.getResponse("GET /ctx/admin/user HTTP/1.0\r\nHost:host:8888\r\n\r\n");
|
String response = _connector.getResponse("GET /ctx/admin/user HTTP/1.0\r\nHost:host:8888\r\n\r\n");
|
||||||
assertThat(response, containsString("HTTP/1.1 200 OK"));
|
assertThat(response, containsString("HTTP/1.1 200 OK"));
|
||||||
assertThat(response, containsString("dispatcherType: FORWARD"));
|
assertThat(response, containsString("dispatcherType: REQUEST"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -95,6 +95,6 @@ public class FormAuthenticatorTest
|
||||||
{
|
{
|
||||||
String response = _connector.getResponse("GET /ctx/j_security_check?j_username=user&j_password=wrong HTTP/1.0\r\nHost:host:8888\r\n\r\n");
|
String response = _connector.getResponse("GET /ctx/j_security_check?j_username=user&j_password=wrong HTTP/1.0\r\nHost:host:8888\r\n\r\n");
|
||||||
assertThat(response, containsString("path: /ctx/error"));
|
assertThat(response, containsString("path: /ctx/error"));
|
||||||
assertThat(response, containsString("dispatcherType: FORWARD"));
|
assertThat(response, containsString("dispatcherType: REQUEST"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue