View Javadoc

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