View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 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             System.err.println("Null identity service, trying login service: "+_identityService);
343             if (_identityService==null)
344                 _identityService=findIdentityService();
345             
346             System.err.println("Finding identity service: "+_identityService);
347             if (_identityService==null && _realmName!=null)
348                 _identityService=new DefaultIdentityService();
349         }
350         
351         if (_loginService!=null)
352         {
353             System.err.println("LoginService="+_loginService + " identityService="+_identityService);
354             if (_loginService.getIdentityService()==null)
355                 _loginService.setIdentityService(_identityService);
356             else if (_loginService.getIdentityService()!=_identityService)
357                 throw new IllegalStateException("LoginService has different IdentityService to "+this);
358         }
359 
360         if (!_loginServiceShared && _loginService instanceof LifeCycle)
361             ((LifeCycle)_loginService).start();        
362         
363         if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
364         {
365             _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
366             if (_authenticator!=null)
367                 _authMethod=_authenticator.getAuthMethod();
368         }
369 
370         if (_authenticator==null)
371         {
372             if (_realmName!=null)
373             {
374                 LOG.warn("No ServerAuthentication for "+this);
375                 throw new IllegalStateException("No ServerAuthentication");
376             }
377         }
378         else
379         {
380             _authenticator.setConfiguration(this);
381             if (_authenticator instanceof LifeCycle)
382                 ((LifeCycle)_authenticator).start();
383         }
384 
385         super.doStart();
386     }
387 
388     /* ------------------------------------------------------------ */
389     /**
390      * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
391      */
392     @Override
393     protected void doStop() throws Exception
394     {
395         super.doStop();
396         
397         if (!_loginServiceShared && _loginService instanceof LifeCycle)
398             ((LifeCycle)_loginService).stop();
399         
400     }
401 
402     /* ------------------------------------------------------------ */
403     protected boolean checkSecurity(Request request)
404     {
405         switch(request.getDispatcherType())
406         {
407             case REQUEST:
408             case ASYNC:
409                 return true;
410             case FORWARD:
411                 if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
412                 {
413                     request.removeAttribute("org.eclipse.jetty.server.welcome");
414                     return true;
415                 }
416                 return false;
417             default:
418                 return false;
419         }
420     }
421     
422     /* ------------------------------------------------------------ */
423     /**
424      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
425      */
426     public boolean isSessionRenewedOnAuthentication()
427     {
428         return _renewSession;
429     }
430     
431     /* ------------------------------------------------------------ */
432     /** Set renew the session on Authentication.
433      * <p>
434      * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
435      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
436      */
437     public void setSessionRenewedOnAuthentication(boolean renew)
438     {
439         _renewSession=renew;
440     }
441     
442     /* ------------------------------------------------------------ */
443     /*
444      * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
445      *      javax.servlet.http.HttpServletRequest,
446      *      javax.servlet.http.HttpServletResponse, int)
447      */
448     @Override
449     public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
450     {
451         final Response base_response = baseRequest.getResponse();
452         final Handler handler=getHandler();
453         
454         if (handler==null)
455             return;
456 
457         final Authenticator authenticator = _authenticator;
458         
459         if (checkSecurity(baseRequest))
460         {
461             Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
462             
463             // Check data constraints
464             if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
465             {
466                 if (!baseRequest.isHandled())
467                 {
468                     response.sendError(Response.SC_FORBIDDEN);
469                     baseRequest.setHandled(true);
470                 }
471                 return;
472             }
473 
474             // is Auth mandatory?
475             boolean isAuthMandatory = 
476                 isAuthMandatory(baseRequest, base_response, constraintInfo);
477 
478             if (isAuthMandatory && authenticator==null)
479             {
480                 LOG.warn("No authenticator for: "+constraintInfo);
481                 if (!baseRequest.isHandled())
482                 {
483                     response.sendError(Response.SC_FORBIDDEN);
484                     baseRequest.setHandled(true);
485                 }
486                 return;
487             }
488             
489             // check authentication
490             Object previousIdentity = null;
491             try
492             {
493                 Authentication authentication = baseRequest.getAuthentication();
494                 if (authentication==null || authentication==Authentication.NOT_CHECKED)
495                     authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
496 
497                 if (authentication instanceof Authentication.Wrapped)
498                 {
499                     request=((Authentication.Wrapped)authentication).getHttpServletRequest();
500                     response=((Authentication.Wrapped)authentication).getHttpServletResponse();
501                 }
502 
503                 if (authentication instanceof Authentication.ResponseSent)
504                 {
505                     baseRequest.setHandled(true);
506                 }
507                 else if (authentication instanceof Authentication.User)
508                 {
509                     Authentication.User userAuth = (Authentication.User)authentication;
510                     baseRequest.setAuthentication(authentication);
511                     if (_identityService!=null)
512                         previousIdentity = _identityService.associate(userAuth.getUserIdentity());
513 
514                     if (isAuthMandatory)
515                     {
516                         boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
517                         if (!authorized)
518                         {
519                             response.sendError(Response.SC_FORBIDDEN, "!role");
520                             baseRequest.setHandled(true);
521                             return;
522                         }
523                     }
524                          
525                     handler.handle(pathInContext, baseRequest, request, response);
526                     if (authenticator!=null)
527                         authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
528                 }
529                 else if (authentication instanceof Authentication.Deferred)
530                 {
531                     DeferredAuthentication deferred= (DeferredAuthentication)authentication;
532                     baseRequest.setAuthentication(authentication);
533 
534                     try
535                     {
536                         handler.handle(pathInContext, baseRequest, request, response);
537                     }
538                     finally
539                     {
540                         previousIdentity = deferred.getPreviousAssociation();
541                     }
542 
543                     if (authenticator!=null)
544                     {
545                         Authentication auth=baseRequest.getAuthentication();
546                         if (auth instanceof Authentication.User)
547                         {
548                             Authentication.User userAuth = (Authentication.User)auth;
549                             authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
550                         }
551                         else
552                             authenticator.secureResponse(request, response, isAuthMandatory, null);
553                     }
554                 }
555                 else
556                 {
557                     baseRequest.setAuthentication(authentication);
558                     if (_identityService!=null)
559                         previousIdentity = _identityService.associate(null);
560                     handler.handle(pathInContext, baseRequest, request, response);
561                     if (authenticator!=null)
562                         authenticator.secureResponse(request, response, isAuthMandatory, null);
563                 }
564             }
565             catch (ServerAuthException e)
566             {
567                 // jaspi 3.8.3 send HTTP 500 internal server error, with message
568                 // from AuthException
569                 response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
570             }
571             finally
572             {
573                 if (_identityService!=null)
574                     _identityService.disassociate(previousIdentity);
575             }
576         }
577         else
578             handler.handle(pathInContext, baseRequest, request, response);
579     }
580 
581 
582     /* ------------------------------------------------------------ */
583     public static SecurityHandler getCurrentSecurityHandler()
584     {
585         Context context = ContextHandler.getCurrentContext();
586         if (context==null)
587             return null;
588         
589         SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
590         return security;
591     }
592 
593     /* ------------------------------------------------------------ */
594     public void logout(Authentication.User user)
595     {
596         LOG.debug("logout {}",user);
597         LoginService login_service=getLoginService();
598         if (login_service!=null)
599         {
600             login_service.logout(user.getUserIdentity());
601         }
602         
603         IdentityService identity_service=getIdentityService();
604         if (identity_service!=null)
605         {
606             // TODO recover previous from threadlocal (or similar)
607             Object previous=null;
608             identity_service.disassociate(previous);
609         }
610     }
611     
612     /* ------------------------------------------------------------ */
613     protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
614 
615     /* ------------------------------------------------------------ */
616     protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
617 
618     /* ------------------------------------------------------------ */
619     protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
620 
621     /* ------------------------------------------------------------ */
622     protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
623                                                            UserIdentity userIdentity) throws IOException;
624 
625     
626     /* ------------------------------------------------------------ */
627     /* ------------------------------------------------------------ */
628     public class NotChecked implements Principal
629     {
630         public String getName()
631         {
632             return null;
633         }
634 
635         @Override
636         public String toString()
637         {
638             return "NOT CHECKED";
639         }
640 
641         public SecurityHandler getSecurityHandler()
642         {
643             return SecurityHandler.this;
644         }
645     }
646 
647     
648     /* ------------------------------------------------------------ */
649     /* ------------------------------------------------------------ */
650     public static Principal __NO_USER = new Principal()
651     {
652         public String getName()
653         {
654             return null;
655         }
656 
657         @Override
658         public String toString()
659         {
660             return "No User";
661         }
662     };
663     
664     /* ------------------------------------------------------------ */
665     /* ------------------------------------------------------------ */
666     /**
667      * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
668      * of authentication. A request with a Nobody UserPrincipal will be allowed
669      * past all authentication constraints - but will not be considered an
670      * authenticated request. It can be used by Authenticators such as
671      * FormAuthenticator to allow access to logon and error pages within an
672      * authenticated URI tree.
673      */
674     public static Principal __NOBODY = new Principal()
675     {
676         public String getName()
677         {
678             return "Nobody";
679         }
680 
681         @Override
682         public String toString()
683         {
684             return getName();
685         }
686     };
687 
688 }