Home | History | Annotate | Download | only in security
      1 //
      2 //  ========================================================================
      3 //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
      4 //  ------------------------------------------------------------------------
      5 //  All rights reserved. This program and the accompanying materials
      6 //  are made available under the terms of the Eclipse Public License v1.0
      7 //  and Apache License v2.0 which accompanies this distribution.
      8 //
      9 //      The Eclipse Public License is available at
     10 //      http://www.eclipse.org/legal/epl-v10.html
     11 //
     12 //      The Apache License v2.0 is available at
     13 //      http://www.opensource.org/licenses/apache2.0.php
     14 //
     15 //  You may elect to redistribute this code under either of these licenses.
     16 //  ========================================================================
     17 //
     18 
     19 package org.eclipse.jetty.security;
     20 
     21 import java.io.IOException;
     22 import java.security.Principal;
     23 import java.util.Enumeration;
     24 import java.util.HashMap;
     25 import java.util.List;
     26 import java.util.Map;
     27 import java.util.Set;
     28 
     29 import javax.servlet.ServletException;
     30 import javax.servlet.http.HttpServletRequest;
     31 import javax.servlet.http.HttpServletResponse;
     32 import javax.servlet.http.HttpSessionEvent;
     33 import javax.servlet.http.HttpSessionListener;
     34 
     35 import org.eclipse.jetty.security.authentication.DeferredAuthentication;
     36 import org.eclipse.jetty.server.AbstractHttpConnection;
     37 import org.eclipse.jetty.server.Authentication;
     38 import org.eclipse.jetty.server.Handler;
     39 import org.eclipse.jetty.server.Request;
     40 import org.eclipse.jetty.server.Response;
     41 import org.eclipse.jetty.server.UserIdentity;
     42 import org.eclipse.jetty.server.handler.ContextHandler;
     43 import org.eclipse.jetty.server.handler.ContextHandler.Context;
     44 import org.eclipse.jetty.server.handler.HandlerWrapper;
     45 import org.eclipse.jetty.server.session.AbstractSessionManager;
     46 import org.eclipse.jetty.util.component.LifeCycle;
     47 import org.eclipse.jetty.util.log.Log;
     48 import org.eclipse.jetty.util.log.Logger;
     49 
     50 /**
     51  * Abstract SecurityHandler.
     52  * Select and apply an {@link Authenticator} to a request.
     53  * <p>
     54  * The Authenticator may either be directly set on the handler
     55  * or will be create during {@link #start()} with a call to
     56  * either the default or set AuthenticatorFactory.
     57  * <p>
     58  * SecurityHandler has a set of initparameters that are used by the
     59  * Authentication.Configuration. At startup, any context init parameters
     60  * that start with "org.eclipse.jetty.security." that do not have
     61  * values in the SecurityHandler init parameters, are copied.
     62  *
     63  */
     64 public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
     65 {
     66     private static final Logger LOG = Log.getLogger(SecurityHandler.class);
     67 
     68     /* ------------------------------------------------------------ */
     69     private boolean _checkWelcomeFiles = false;
     70     private Authenticator _authenticator;
     71     private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
     72     private String _realmName;
     73     private String _authMethod;
     74     private final Map<String,String> _initParameters=new HashMap<String,String>();
     75     private LoginService _loginService;
     76     private boolean _loginServiceShared;
     77     private IdentityService _identityService;
     78     private boolean _renewSession=true;
     79 
     80     /* ------------------------------------------------------------ */
     81     protected SecurityHandler()
     82     {
     83     }
     84 
     85     /* ------------------------------------------------------------ */
     86     /** Get the identityService.
     87      * @return the identityService
     88      */
     89     public IdentityService getIdentityService()
     90     {
     91         return _identityService;
     92     }
     93 
     94     /* ------------------------------------------------------------ */
     95     /** Set the identityService.
     96      * @param identityService the identityService to set
     97      */
     98     public void setIdentityService(IdentityService identityService)
     99     {
    100         if (isStarted())
    101             throw new IllegalStateException("Started");
    102         _identityService = identityService;
    103     }
    104 
    105     /* ------------------------------------------------------------ */
    106     /** Get the loginService.
    107      * @return the loginService
    108      */
    109     public LoginService getLoginService()
    110     {
    111         return _loginService;
    112     }
    113 
    114     /* ------------------------------------------------------------ */
    115     /** Set the loginService.
    116      * @param loginService the loginService to set
    117      */
    118     public void setLoginService(LoginService loginService)
    119     {
    120         if (isStarted())
    121             throw new IllegalStateException("Started");
    122         _loginService = loginService;
    123         _loginServiceShared=false;
    124     }
    125 
    126 
    127     /* ------------------------------------------------------------ */
    128     public Authenticator getAuthenticator()
    129     {
    130         return _authenticator;
    131     }
    132 
    133     /* ------------------------------------------------------------ */
    134     /** Set the authenticator.
    135      * @param authenticator
    136      * @throws IllegalStateException if the SecurityHandler is running
    137      */
    138     public void setAuthenticator(Authenticator authenticator)
    139     {
    140         if (isStarted())
    141             throw new IllegalStateException("Started");
    142         _authenticator = authenticator;
    143     }
    144 
    145     /* ------------------------------------------------------------ */
    146     /**
    147      * @return the authenticatorFactory
    148      */
    149     public Authenticator.Factory getAuthenticatorFactory()
    150     {
    151         return _authenticatorFactory;
    152     }
    153 
    154     /* ------------------------------------------------------------ */
    155     /**
    156      * @param authenticatorFactory the authenticatorFactory to set
    157      * @throws IllegalStateException if the SecurityHandler is running
    158      */
    159     public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
    160     {
    161         if (isRunning())
    162             throw new IllegalStateException("running");
    163         _authenticatorFactory = authenticatorFactory;
    164     }
    165 
    166     /* ------------------------------------------------------------ */
    167     /**
    168      * @return the realmName
    169      */
    170     public String getRealmName()
    171     {
    172         return _realmName;
    173     }
    174 
    175     /* ------------------------------------------------------------ */
    176     /**
    177      * @param realmName the realmName to set
    178      * @throws IllegalStateException if the SecurityHandler is running
    179      */
    180     public void setRealmName(String realmName)
    181     {
    182         if (isRunning())
    183             throw new IllegalStateException("running");
    184         _realmName = realmName;
    185     }
    186 
    187     /* ------------------------------------------------------------ */
    188     /**
    189      * @return the authMethod
    190      */
    191     public String getAuthMethod()
    192     {
    193         return _authMethod;
    194     }
    195 
    196     /* ------------------------------------------------------------ */
    197     /**
    198      * @param authMethod the authMethod to set
    199      * @throws IllegalStateException if the SecurityHandler is running
    200      */
    201     public void setAuthMethod(String authMethod)
    202     {
    203         if (isRunning())
    204             throw new IllegalStateException("running");
    205         _authMethod = authMethod;
    206     }
    207 
    208     /* ------------------------------------------------------------ */
    209     /**
    210      * @return True if forwards to welcome files are authenticated
    211      */
    212     public boolean isCheckWelcomeFiles()
    213     {
    214         return _checkWelcomeFiles;
    215     }
    216 
    217     /* ------------------------------------------------------------ */
    218     /**
    219      * @param authenticateWelcomeFiles True if forwards to welcome files are
    220      *                authenticated
    221      * @throws IllegalStateException if the SecurityHandler is running
    222      */
    223     public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
    224     {
    225         if (isRunning())
    226             throw new IllegalStateException("running");
    227         _checkWelcomeFiles = authenticateWelcomeFiles;
    228     }
    229 
    230     /* ------------------------------------------------------------ */
    231     public String getInitParameter(String key)
    232     {
    233         return _initParameters.get(key);
    234     }
    235 
    236     /* ------------------------------------------------------------ */
    237     public Set<String> getInitParameterNames()
    238     {
    239         return _initParameters.keySet();
    240     }
    241 
    242     /* ------------------------------------------------------------ */
    243     /** Set an initialization parameter.
    244      * @param key
    245      * @param value
    246      * @return previous value
    247      * @throws IllegalStateException if the SecurityHandler is running
    248      */
    249     public String setInitParameter(String key, String value)
    250     {
    251         if (isRunning())
    252             throw new IllegalStateException("running");
    253         return _initParameters.put(key,value);
    254     }
    255 
    256     /* ------------------------------------------------------------ */
    257     protected LoginService findLoginService()
    258     {
    259         List<LoginService> list = getServer().getBeans(LoginService.class);
    260 
    261         String realm=getRealmName();
    262         if (realm!=null)
    263         {
    264             for (LoginService service : list)
    265                 if (service.getName()!=null && service.getName().equals(realm))
    266                     return service;
    267         }
    268         else if (list.size()==1)
    269             return list.get(0);
    270         return null;
    271     }
    272 
    273     /* ------------------------------------------------------------ */
    274     protected IdentityService findIdentityService()
    275     {
    276         return getServer().getBean(IdentityService.class);
    277     }
    278 
    279     /* ------------------------------------------------------------ */
    280     /**
    281      */
    282     @Override
    283     protected void doStart()
    284         throws Exception
    285     {
    286         // copy security init parameters
    287         ContextHandler.Context context =ContextHandler.getCurrentContext();
    288         if (context!=null)
    289         {
    290             Enumeration<String> names=context.getInitParameterNames();
    291             while (names!=null && names.hasMoreElements())
    292             {
    293                 String name =names.nextElement();
    294                 if (name.startsWith("org.eclipse.jetty.security.") &&
    295                         getInitParameter(name)==null)
    296                     setInitParameter(name,context.getInitParameter(name));
    297             }
    298 
    299             //register a session listener to handle securing sessions when authentication is performed
    300             context.getContextHandler().addEventListener(new HttpSessionListener()
    301             {
    302 
    303                 public void sessionDestroyed(HttpSessionEvent se)
    304                 {
    305 
    306                 }
    307 
    308                 public void sessionCreated(HttpSessionEvent se)
    309                 {
    310                     //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
    311                     AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
    312                     if (connection == null)
    313                         return;
    314                     Request request = connection.getRequest();
    315                     if (request == null)
    316                         return;
    317 
    318                     if (request.isSecure())
    319                     {
    320                         se.getSession().setAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
    321                     }
    322                 }
    323             });
    324         }
    325 
    326         // complicated resolution of login and identity service to handle
    327         // many different ways these can be constructed and injected.
    328 
    329         if (_loginService==null)
    330         {
    331             _loginService=findLoginService();
    332             if (_loginService!=null)
    333                 _loginServiceShared=true;
    334         }
    335 
    336         if (_identityService==null)
    337         {
    338 
    339             if (_loginService!=null)
    340                 _identityService=_loginService.getIdentityService();
    341 
    342             if (_identityService==null)
    343                 _identityService=findIdentityService();
    344 
    345             if (_identityService==null && _realmName!=null)
    346                 _identityService=new DefaultIdentityService();
    347         }
    348 
    349         if (_loginService!=null)
    350         {
    351             if (_loginService.getIdentityService()==null)
    352                 _loginService.setIdentityService(_identityService);
    353             else if (_loginService.getIdentityService()!=_identityService)
    354                 throw new IllegalStateException("LoginService has different IdentityService to "+this);
    355         }
    356 
    357         if (!_loginServiceShared && _loginService instanceof LifeCycle)
    358             ((LifeCycle)_loginService).start();
    359 
    360         if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
    361         {
    362             _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
    363             if (_authenticator!=null)
    364                 _authMethod=_authenticator.getAuthMethod();
    365         }
    366 
    367         if (_authenticator==null)
    368         {
    369             if (_realmName!=null)
    370             {
    371                 LOG.warn("No ServerAuthentication for "+this);
    372                 throw new IllegalStateException("No ServerAuthentication");
    373             }
    374         }
    375         else
    376         {
    377             _authenticator.setConfiguration(this);
    378             if (_authenticator instanceof LifeCycle)
    379                 ((LifeCycle)_authenticator).start();
    380         }
    381 
    382         super.doStart();
    383     }
    384 
    385     /* ------------------------------------------------------------ */
    386     /**
    387      * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
    388      */
    389     @Override
    390     protected void doStop() throws Exception
    391     {
    392         super.doStop();
    393 
    394         if (!_loginServiceShared && _loginService instanceof LifeCycle)
    395             ((LifeCycle)_loginService).stop();
    396 
    397     }
    398 
    399     /* ------------------------------------------------------------ */
    400     protected boolean checkSecurity(Request request)
    401     {
    402         switch(request.getDispatcherType())
    403         {
    404             case REQUEST:
    405             case ASYNC:
    406                 return true;
    407             case FORWARD:
    408                 if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
    409                 {
    410                     request.removeAttribute("org.eclipse.jetty.server.welcome");
    411                     return true;
    412                 }
    413                 return false;
    414             default:
    415                 return false;
    416         }
    417     }
    418 
    419     /* ------------------------------------------------------------ */
    420     /**
    421      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
    422      */
    423     public boolean isSessionRenewedOnAuthentication()
    424     {
    425         return _renewSession;
    426     }
    427 
    428     /* ------------------------------------------------------------ */
    429     /** Set renew the session on Authentication.
    430      * <p>
    431      * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
    432      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
    433      */
    434     public void setSessionRenewedOnAuthentication(boolean renew)
    435     {
    436         _renewSession=renew;
    437     }
    438 
    439     /* ------------------------------------------------------------ */
    440     /*
    441      * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
    442      *      javax.servlet.http.HttpServletRequest,
    443      *      javax.servlet.http.HttpServletResponse, int)
    444      */
    445     @Override
    446     public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
    447     {
    448         final Response base_response = baseRequest.getResponse();
    449         final Handler handler=getHandler();
    450 
    451         if (handler==null)
    452             return;
    453 
    454         final Authenticator authenticator = _authenticator;
    455 
    456         if (checkSecurity(baseRequest))
    457         {
    458             Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
    459 
    460             // Check data constraints
    461             if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
    462             {
    463                 if (!baseRequest.isHandled())
    464                 {
    465                     response.sendError(Response.SC_FORBIDDEN);
    466                     baseRequest.setHandled(true);
    467                 }
    468                 return;
    469             }
    470 
    471             // is Auth mandatory?
    472             boolean isAuthMandatory =
    473                 isAuthMandatory(baseRequest, base_response, constraintInfo);
    474 
    475             if (isAuthMandatory && authenticator==null)
    476             {
    477                 LOG.warn("No authenticator for: "+constraintInfo);
    478                 if (!baseRequest.isHandled())
    479                 {
    480                     response.sendError(Response.SC_FORBIDDEN);
    481                     baseRequest.setHandled(true);
    482                 }
    483                 return;
    484             }
    485 
    486             // check authentication
    487             Object previousIdentity = null;
    488             try
    489             {
    490                 Authentication authentication = baseRequest.getAuthentication();
    491                 if (authentication==null || authentication==Authentication.NOT_CHECKED)
    492                     authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
    493 
    494                 if (authentication instanceof Authentication.Wrapped)
    495                 {
    496                     request=((Authentication.Wrapped)authentication).getHttpServletRequest();
    497                     response=((Authentication.Wrapped)authentication).getHttpServletResponse();
    498                 }
    499 
    500                 if (authentication instanceof Authentication.ResponseSent)
    501                 {
    502                     baseRequest.setHandled(true);
    503                 }
    504                 else if (authentication instanceof Authentication.User)
    505                 {
    506                     Authentication.User userAuth = (Authentication.User)authentication;
    507                     baseRequest.setAuthentication(authentication);
    508                     if (_identityService!=null)
    509                         previousIdentity = _identityService.associate(userAuth.getUserIdentity());
    510 
    511                     if (isAuthMandatory)
    512                     {
    513                         boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
    514                         if (!authorized)
    515                         {
    516                             response.sendError(Response.SC_FORBIDDEN, "!role");
    517                             baseRequest.setHandled(true);
    518                             return;
    519                         }
    520                     }
    521 
    522                     handler.handle(pathInContext, baseRequest, request, response);
    523                     if (authenticator!=null)
    524                         authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
    525                 }
    526                 else if (authentication instanceof Authentication.Deferred)
    527                 {
    528                     DeferredAuthentication deferred= (DeferredAuthentication)authentication;
    529                     baseRequest.setAuthentication(authentication);
    530 
    531                     try
    532                     {
    533                         handler.handle(pathInContext, baseRequest, request, response);
    534                     }
    535                     finally
    536                     {
    537                         previousIdentity = deferred.getPreviousAssociation();
    538                     }
    539 
    540                     if (authenticator!=null)
    541                     {
    542                         Authentication auth=baseRequest.getAuthentication();
    543                         if (auth instanceof Authentication.User)
    544                         {
    545                             Authentication.User userAuth = (Authentication.User)auth;
    546                             authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
    547                         }
    548                         else
    549                             authenticator.secureResponse(request, response, isAuthMandatory, null);
    550                     }
    551                 }
    552                 else
    553                 {
    554                     baseRequest.setAuthentication(authentication);
    555                     if (_identityService!=null)
    556                         previousIdentity = _identityService.associate(null);
    557                     handler.handle(pathInContext, baseRequest, request, response);
    558                     if (authenticator!=null)
    559                         authenticator.secureResponse(request, response, isAuthMandatory, null);
    560                 }
    561             }
    562             catch (ServerAuthException e)
    563             {
    564                 // jaspi 3.8.3 send HTTP 500 internal server error, with message
    565                 // from AuthException
    566                 response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    567             }
    568             finally
    569             {
    570                 if (_identityService!=null)
    571                     _identityService.disassociate(previousIdentity);
    572             }
    573         }
    574         else
    575             handler.handle(pathInContext, baseRequest, request, response);
    576     }
    577 
    578 
    579     /* ------------------------------------------------------------ */
    580     public static SecurityHandler getCurrentSecurityHandler()
    581     {
    582         Context context = ContextHandler.getCurrentContext();
    583         if (context==null)
    584             return null;
    585 
    586         SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
    587         return security;
    588     }
    589 
    590     /* ------------------------------------------------------------ */
    591     public void logout(Authentication.User user)
    592     {
    593         LOG.debug("logout {}",user);
    594         LoginService login_service=getLoginService();
    595         if (login_service!=null)
    596         {
    597             login_service.logout(user.getUserIdentity());
    598         }
    599 
    600         IdentityService identity_service=getIdentityService();
    601         if (identity_service!=null)
    602         {
    603             // TODO recover previous from threadlocal (or similar)
    604             Object previous=null;
    605             identity_service.disassociate(previous);
    606         }
    607     }
    608 
    609     /* ------------------------------------------------------------ */
    610     protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
    611 
    612     /* ------------------------------------------------------------ */
    613     protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
    614 
    615     /* ------------------------------------------------------------ */
    616     protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
    617 
    618     /* ------------------------------------------------------------ */
    619     protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
    620                                                            UserIdentity userIdentity) throws IOException;
    621 
    622 
    623     /* ------------------------------------------------------------ */
    624     /* ------------------------------------------------------------ */
    625     public class NotChecked implements Principal
    626     {
    627         public String getName()
    628         {
    629             return null;
    630         }
    631 
    632         @Override
    633         public String toString()
    634         {
    635             return "NOT CHECKED";
    636         }
    637 
    638         public SecurityHandler getSecurityHandler()
    639         {
    640             return SecurityHandler.this;
    641         }
    642     }
    643 
    644 
    645     /* ------------------------------------------------------------ */
    646     /* ------------------------------------------------------------ */
    647     public static Principal __NO_USER = new Principal()
    648     {
    649         public String getName()
    650         {
    651             return null;
    652         }
    653 
    654         @Override
    655         public String toString()
    656         {
    657             return "No User";
    658         }
    659     };
    660 
    661     /* ------------------------------------------------------------ */
    662     /* ------------------------------------------------------------ */
    663     /**
    664      * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
    665      * of authentication. A request with a Nobody UserPrincipal will be allowed
    666      * past all authentication constraints - but will not be considered an
    667      * authenticated request. It can be used by Authenticators such as
    668      * FormAuthenticator to allow access to logon and error pages within an
    669      * authenticated URI tree.
    670      */
    671     public static Principal __NOBODY = new Principal()
    672     {
    673         public String getName()
    674         {
    675             return "Nobody";
    676         }
    677 
    678         @Override
    679         public String toString()
    680         {
    681             return getName();
    682         }
    683     };
    684 
    685 }
    686