blob: 4e5518385f48bdad13498c16597bdc507c25633b [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.security;
import java.io.IOException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
import org.eclipse.jetty.server.AbstractHttpConnection;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.AbstractSessionManager;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* Abstract SecurityHandler.
* Select and apply an {@link Authenticator} to a request.
* <p>
* The Authenticator may either be directly set on the handler
* or will be create during {@link #start()} with a call to
* either the default or set AuthenticatorFactory.
* <p>
* SecurityHandler has a set of initparameters that are used by the
* Authentication.Configuration. At startup, any context init parameters
* that start with "org.eclipse.jetty.security." that do not have
* values in the SecurityHandler init parameters, are copied.
*
*/
public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
{
private static final Logger LOG = Log.getLogger(SecurityHandler.class);
/* ------------------------------------------------------------ */
private boolean _checkWelcomeFiles = false;
private Authenticator _authenticator;
private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
private String _realmName;
private String _authMethod;
private final Map<String,String> _initParameters=new HashMap<String,String>();
private LoginService _loginService;
private boolean _loginServiceShared;
private IdentityService _identityService;
private boolean _renewSession=true;
/* ------------------------------------------------------------ */
protected SecurityHandler()
{
}
/* ------------------------------------------------------------ */
/** Get the identityService.
* @return the identityService
*/
public IdentityService getIdentityService()
{
return _identityService;
}
/* ------------------------------------------------------------ */
/** Set the identityService.
* @param identityService the identityService to set
*/
public void setIdentityService(IdentityService identityService)
{
if (isStarted())
throw new IllegalStateException("Started");
_identityService = identityService;
}
/* ------------------------------------------------------------ */
/** Get the loginService.
* @return the loginService
*/
public LoginService getLoginService()
{
return _loginService;
}
/* ------------------------------------------------------------ */
/** Set the loginService.
* @param loginService the loginService to set
*/
public void setLoginService(LoginService loginService)
{
if (isStarted())
throw new IllegalStateException("Started");
_loginService = loginService;
_loginServiceShared=false;
}
/* ------------------------------------------------------------ */
public Authenticator getAuthenticator()
{
return _authenticator;
}
/* ------------------------------------------------------------ */
/** Set the authenticator.
* @param authenticator
* @throws IllegalStateException if the SecurityHandler is running
*/
public void setAuthenticator(Authenticator authenticator)
{
if (isStarted())
throw new IllegalStateException("Started");
_authenticator = authenticator;
}
/* ------------------------------------------------------------ */
/**
* @return the authenticatorFactory
*/
public Authenticator.Factory getAuthenticatorFactory()
{
return _authenticatorFactory;
}
/* ------------------------------------------------------------ */
/**
* @param authenticatorFactory the authenticatorFactory to set
* @throws IllegalStateException if the SecurityHandler is running
*/
public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
{
if (isRunning())
throw new IllegalStateException("running");
_authenticatorFactory = authenticatorFactory;
}
/* ------------------------------------------------------------ */
/**
* @return the realmName
*/
public String getRealmName()
{
return _realmName;
}
/* ------------------------------------------------------------ */
/**
* @param realmName the realmName to set
* @throws IllegalStateException if the SecurityHandler is running
*/
public void setRealmName(String realmName)
{
if (isRunning())
throw new IllegalStateException("running");
_realmName = realmName;
}
/* ------------------------------------------------------------ */
/**
* @return the authMethod
*/
public String getAuthMethod()
{
return _authMethod;
}
/* ------------------------------------------------------------ */
/**
* @param authMethod the authMethod to set
* @throws IllegalStateException if the SecurityHandler is running
*/
public void setAuthMethod(String authMethod)
{
if (isRunning())
throw new IllegalStateException("running");
_authMethod = authMethod;
}
/* ------------------------------------------------------------ */
/**
* @return True if forwards to welcome files are authenticated
*/
public boolean isCheckWelcomeFiles()
{
return _checkWelcomeFiles;
}
/* ------------------------------------------------------------ */
/**
* @param authenticateWelcomeFiles True if forwards to welcome files are
* authenticated
* @throws IllegalStateException if the SecurityHandler is running
*/
public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
{
if (isRunning())
throw new IllegalStateException("running");
_checkWelcomeFiles = authenticateWelcomeFiles;
}
/* ------------------------------------------------------------ */
public String getInitParameter(String key)
{
return _initParameters.get(key);
}
/* ------------------------------------------------------------ */
public Set<String> getInitParameterNames()
{
return _initParameters.keySet();
}
/* ------------------------------------------------------------ */
/** Set an initialization parameter.
* @param key
* @param value
* @return previous value
* @throws IllegalStateException if the SecurityHandler is running
*/
public String setInitParameter(String key, String value)
{
if (isRunning())
throw new IllegalStateException("running");
return _initParameters.put(key,value);
}
/* ------------------------------------------------------------ */
protected LoginService findLoginService()
{
List<LoginService> list = getServer().getBeans(LoginService.class);
String realm=getRealmName();
if (realm!=null)
{
for (LoginService service : list)
if (service.getName()!=null && service.getName().equals(realm))
return service;
}
else if (list.size()==1)
return list.get(0);
return null;
}
/* ------------------------------------------------------------ */
protected IdentityService findIdentityService()
{
return getServer().getBean(IdentityService.class);
}
/* ------------------------------------------------------------ */
/**
*/
@Override
protected void doStart()
throws Exception
{
// copy security init parameters
ContextHandler.Context context =ContextHandler.getCurrentContext();
if (context!=null)
{
Enumeration<String> names=context.getInitParameterNames();
while (names!=null && names.hasMoreElements())
{
String name =names.nextElement();
if (name.startsWith("org.eclipse.jetty.security.") &&
getInitParameter(name)==null)
setInitParameter(name,context.getInitParameter(name));
}
//register a session listener to handle securing sessions when authentication is performed
context.getContextHandler().addEventListener(new HttpSessionListener()
{
public void sessionDestroyed(HttpSessionEvent se)
{
}
public void sessionCreated(HttpSessionEvent se)
{
//if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
if (connection == null)
return;
Request request = connection.getRequest();
if (request == null)
return;
if (request.isSecure())
{
se.getSession().setAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
}
}
});
}
// complicated resolution of login and identity service to handle
// many different ways these can be constructed and injected.
if (_loginService==null)
{
_loginService=findLoginService();
if (_loginService!=null)
_loginServiceShared=true;
}
if (_identityService==null)
{
if (_loginService!=null)
_identityService=_loginService.getIdentityService();
if (_identityService==null)
_identityService=findIdentityService();
if (_identityService==null && _realmName!=null)
_identityService=new DefaultIdentityService();
}
if (_loginService!=null)
{
if (_loginService.getIdentityService()==null)
_loginService.setIdentityService(_identityService);
else if (_loginService.getIdentityService()!=_identityService)
throw new IllegalStateException("LoginService has different IdentityService to "+this);
}
if (!_loginServiceShared && _loginService instanceof LifeCycle)
((LifeCycle)_loginService).start();
if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
{
_authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
if (_authenticator!=null)
_authMethod=_authenticator.getAuthMethod();
}
if (_authenticator==null)
{
if (_realmName!=null)
{
LOG.warn("No ServerAuthentication for "+this);
throw new IllegalStateException("No ServerAuthentication");
}
}
else
{
_authenticator.setConfiguration(this);
if (_authenticator instanceof LifeCycle)
((LifeCycle)_authenticator).start();
}
super.doStart();
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
*/
@Override
protected void doStop() throws Exception
{
super.doStop();
if (!_loginServiceShared && _loginService instanceof LifeCycle)
((LifeCycle)_loginService).stop();
}
/* ------------------------------------------------------------ */
protected boolean checkSecurity(Request request)
{
switch(request.getDispatcherType())
{
case REQUEST:
case ASYNC:
return true;
case FORWARD:
if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
{
request.removeAttribute("org.eclipse.jetty.server.welcome");
return true;
}
return false;
default:
return false;
}
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
*/
public boolean isSessionRenewedOnAuthentication()
{
return _renewSession;
}
/* ------------------------------------------------------------ */
/** Set renew the session on Authentication.
* <p>
* If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
* @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
*/
public void setSessionRenewedOnAuthentication(boolean renew)
{
_renewSession=renew;
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
* javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse, int)
*/
@Override
public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
final Response base_response = baseRequest.getResponse();
final Handler handler=getHandler();
if (handler==null)
return;
final Authenticator authenticator = _authenticator;
if (checkSecurity(baseRequest))
{
Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
// Check data constraints
if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
{
if (!baseRequest.isHandled())
{
response.sendError(Response.SC_FORBIDDEN);
baseRequest.setHandled(true);
}
return;
}
// is Auth mandatory?
boolean isAuthMandatory =
isAuthMandatory(baseRequest, base_response, constraintInfo);
if (isAuthMandatory && authenticator==null)
{
LOG.warn("No authenticator for: "+constraintInfo);
if (!baseRequest.isHandled())
{
response.sendError(Response.SC_FORBIDDEN);
baseRequest.setHandled(true);
}
return;
}
// check authentication
Object previousIdentity = null;
try
{
Authentication authentication = baseRequest.getAuthentication();
if (authentication==null || authentication==Authentication.NOT_CHECKED)
authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
if (authentication instanceof Authentication.Wrapped)
{
request=((Authentication.Wrapped)authentication).getHttpServletRequest();
response=((Authentication.Wrapped)authentication).getHttpServletResponse();
}
if (authentication instanceof Authentication.ResponseSent)
{
baseRequest.setHandled(true);
}
else if (authentication instanceof Authentication.User)
{
Authentication.User userAuth = (Authentication.User)authentication;
baseRequest.setAuthentication(authentication);
if (_identityService!=null)
previousIdentity = _identityService.associate(userAuth.getUserIdentity());
if (isAuthMandatory)
{
boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
if (!authorized)
{
response.sendError(Response.SC_FORBIDDEN, "!role");
baseRequest.setHandled(true);
return;
}
}
handler.handle(pathInContext, baseRequest, request, response);
if (authenticator!=null)
authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
}
else if (authentication instanceof Authentication.Deferred)
{
DeferredAuthentication deferred= (DeferredAuthentication)authentication;
baseRequest.setAuthentication(authentication);
try
{
handler.handle(pathInContext, baseRequest, request, response);
}
finally
{
previousIdentity = deferred.getPreviousAssociation();
}
if (authenticator!=null)
{
Authentication auth=baseRequest.getAuthentication();
if (auth instanceof Authentication.User)
{
Authentication.User userAuth = (Authentication.User)auth;
authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
}
else
authenticator.secureResponse(request, response, isAuthMandatory, null);
}
}
else
{
baseRequest.setAuthentication(authentication);
if (_identityService!=null)
previousIdentity = _identityService.associate(null);
handler.handle(pathInContext, baseRequest, request, response);
if (authenticator!=null)
authenticator.secureResponse(request, response, isAuthMandatory, null);
}
}
catch (ServerAuthException e)
{
// jaspi 3.8.3 send HTTP 500 internal server error, with message
// from AuthException
response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
finally
{
if (_identityService!=null)
_identityService.disassociate(previousIdentity);
}
}
else
handler.handle(pathInContext, baseRequest, request, response);
}
/* ------------------------------------------------------------ */
public static SecurityHandler getCurrentSecurityHandler()
{
Context context = ContextHandler.getCurrentContext();
if (context==null)
return null;
SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
return security;
}
/* ------------------------------------------------------------ */
public void logout(Authentication.User user)
{
LOG.debug("logout {}",user);
LoginService login_service=getLoginService();
if (login_service!=null)
{
login_service.logout(user.getUserIdentity());
}
IdentityService identity_service=getIdentityService();
if (identity_service!=null)
{
// TODO recover previous from threadlocal (or similar)
Object previous=null;
identity_service.disassociate(previous);
}
}
/* ------------------------------------------------------------ */
protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
/* ------------------------------------------------------------ */
protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
/* ------------------------------------------------------------ */
protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
/* ------------------------------------------------------------ */
protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
UserIdentity userIdentity) throws IOException;
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
public class NotChecked implements Principal
{
public String getName()
{
return null;
}
@Override
public String toString()
{
return "NOT CHECKED";
}
public SecurityHandler getSecurityHandler()
{
return SecurityHandler.this;
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
public static Principal __NO_USER = new Principal()
{
public String getName()
{
return null;
}
@Override
public String toString()
{
return "No User";
}
};
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/**
* Nobody user. The Nobody UserPrincipal is used to indicate a partial state
* of authentication. A request with a Nobody UserPrincipal will be allowed
* past all authentication constraints - but will not be considered an
* authenticated request. It can be used by Authenticators such as
* FormAuthenticator to allow access to logon and error pages within an
* authenticated URI tree.
*/
public static Principal __NOBODY = new Principal()
{
public String getName()
{
return "Nobody";
}
@Override
public String toString()
{
return getName();
}
};
}