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.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 }