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