View Javadoc

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