View Javadoc

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