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     protected LoginService findLoginService()
249     {
250         List<LoginService> list = getServer().getBeans(LoginService.class);
251         
252         String realm=getRealmName();
253         if (realm!=null)
254         {
255             for (LoginService service : list)
256                 if (service.getName()!=null && service.getName().equals(realm))
257                     return service;
258         }
259         else if (list.size()==1)
260             return list.get(0);
261         return null;
262     }
263     
264     /* ------------------------------------------------------------ */
265     protected IdentityService findIdentityService()
266     {
267         return getServer().getBean(IdentityService.class);
268     }
269     
270     /* ------------------------------------------------------------ */
271     /** 
272      */
273     @Override
274     protected void doStart()
275         throws Exception
276     {
277         // copy security init parameters
278         ContextHandler.Context context =ContextHandler.getCurrentContext();
279         if (context!=null)
280         {
281             Enumeration<String> names=context.getInitParameterNames();
282             while (names!=null && names.hasMoreElements())
283             {
284                 String name =names.nextElement();
285                 if (name.startsWith("org.eclipse.jetty.security.") &&
286                         getInitParameter(name)==null)
287                     setInitParameter(name,context.getInitParameter(name));
288             }
289         }
290         
291         // complicated resolution of login and identity service to handle
292         // many different ways these can be constructed and injected.
293         
294         if (_loginService==null)
295         {
296             _loginService=findLoginService();
297             if (_loginService!=null)
298                 _loginServiceShared=true;
299         }
300         
301         if (_identityService==null)
302         {
303             if (_loginService!=null)
304                 _identityService=_loginService.getIdentityService();
305 
306             if (_identityService==null)
307                 _identityService=findIdentityService();
308             
309             if (_identityService==null && _realmName!=null)
310                 _identityService=new DefaultIdentityService();
311         }
312         
313         if (_loginService!=null)
314         {
315             if (_loginService.getIdentityService()==null)
316                 _loginService.setIdentityService(_identityService);
317             else if (_loginService.getIdentityService()!=_identityService)
318                 throw new IllegalStateException("LoginService has different IdentityService to "+this);
319         }
320 
321         if (!_loginServiceShared && _loginService instanceof LifeCycle)
322             ((LifeCycle)_loginService).start();        
323         
324         if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
325         {
326             _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
327             if (_authenticator!=null)
328                 _authMethod=_authenticator.getAuthMethod();
329         }
330 
331         if (_authenticator==null)
332         {
333             if (_realmName!=null)
334             {
335                 LOG.warn("No ServerAuthentication for "+this);
336                 throw new IllegalStateException("No ServerAuthentication");
337             }
338         }
339         else
340         {
341             _authenticator.setConfiguration(this);
342             if (_authenticator instanceof LifeCycle)
343                 ((LifeCycle)_authenticator).start();
344         }
345 
346         super.doStart();
347     }
348 
349     /* ------------------------------------------------------------ */
350     /**
351      * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
352      */
353     @Override
354     protected void doStop() throws Exception
355     {
356         super.doStop();
357         
358         if (!_loginServiceShared && _loginService instanceof LifeCycle)
359             ((LifeCycle)_loginService).stop();
360         
361     }
362 
363     /* ------------------------------------------------------------ */
364     protected boolean checkSecurity(Request request)
365     {
366         switch(request.getDispatcherType())
367         {
368             case REQUEST:
369             case ASYNC:
370                 return true;
371             case FORWARD:
372                 if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
373                 {
374                     request.removeAttribute("org.eclipse.jetty.server.welcome");
375                     return true;
376                 }
377                 return false;
378             default:
379                 return false;
380         }
381     }
382     
383     /* ------------------------------------------------------------ */
384     /**
385      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
386      */
387     public boolean isSessionRenewedOnAuthentication()
388     {
389         return _renewSession;
390     }
391     
392     /* ------------------------------------------------------------ */
393     /** Set renew the session on Authentication.
394      * <p>
395      * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
396      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
397      */
398     public void setSessionRenewedOnAuthentication(boolean renew)
399     {
400         _renewSession=renew;
401     }
402     
403     /* ------------------------------------------------------------ */
404     /*
405      * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
406      *      javax.servlet.http.HttpServletRequest,
407      *      javax.servlet.http.HttpServletResponse, int)
408      */
409     @Override
410     public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
411     {
412         final Response base_response = baseRequest.getResponse();
413         final Handler handler=getHandler();
414         
415         if (handler==null)
416             return;
417 
418         final Authenticator authenticator = _authenticator;
419         
420         if (checkSecurity(baseRequest))
421         {
422             Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
423             
424             // Check data constraints
425             if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
426             {
427                 if (!baseRequest.isHandled())
428                 {
429                     response.sendError(Response.SC_FORBIDDEN);
430                     baseRequest.setHandled(true);
431                 }
432                 return;
433             }
434 
435             // is Auth mandatory?
436             boolean isAuthMandatory = 
437                 isAuthMandatory(baseRequest, base_response, constraintInfo);
438 
439             if (isAuthMandatory && authenticator==null)
440             {
441                 LOG.warn("No authenticator for: "+constraintInfo);
442                 if (!baseRequest.isHandled())
443                 {
444                     response.sendError(Response.SC_FORBIDDEN);
445                     baseRequest.setHandled(true);
446                 }
447                 return;
448             }
449             
450             // check authentication
451             Object previousIdentity = null;
452             try
453             {
454                 Authentication authentication = baseRequest.getAuthentication();
455                 if (authentication==null || authentication==Authentication.NOT_CHECKED)
456                     authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
457 
458                 if (authentication instanceof Authentication.Wrapped)
459                 {
460                     request=((Authentication.Wrapped)authentication).getHttpServletRequest();
461                     response=((Authentication.Wrapped)authentication).getHttpServletResponse();
462                 }
463 
464                 if (authentication instanceof Authentication.ResponseSent)
465                 {
466                     baseRequest.setHandled(true);
467                 }
468                 else if (authentication instanceof Authentication.User)
469                 {
470                     Authentication.User userAuth = (Authentication.User)authentication;
471                     baseRequest.setAuthentication(authentication);
472                     if (_identityService!=null)
473                         previousIdentity = _identityService.associate(userAuth.getUserIdentity());
474 
475                     if (isAuthMandatory)
476                     {
477                         boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
478                         if (!authorized)
479                         {
480                             response.sendError(Response.SC_FORBIDDEN, "!role");
481                             baseRequest.setHandled(true);
482                             return;
483                         }
484                     }
485                          
486                     handler.handle(pathInContext, baseRequest, request, response);
487                     if (authenticator!=null)
488                         authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
489                 }
490                 else if (authentication instanceof Authentication.Deferred)
491                 {
492                     DeferredAuthentication deferred= (DeferredAuthentication)authentication;
493                     deferred.setIdentityService(_identityService);
494                     deferred.setLoginService(_loginService);
495                     baseRequest.setAuthentication(authentication);
496 
497                     try
498                     {
499                         handler.handle(pathInContext, baseRequest, request, response);
500                     }
501                     finally
502                     {
503                         previousIdentity = deferred.getPreviousAssociation();
504                         deferred.setIdentityService(null);
505                     }
506 
507                     if (authenticator!=null)
508                     {
509                         Authentication auth=baseRequest.getAuthentication();
510                         if (auth instanceof Authentication.User)
511                         {
512                             Authentication.User userAuth = (Authentication.User)auth;
513                             authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
514                         }
515                         else
516                             authenticator.secureResponse(request, response, isAuthMandatory, null);
517                     }
518                 }
519                 else
520                 {
521                     baseRequest.setAuthentication(authentication);
522                     if (_identityService!=null)
523                         previousIdentity = _identityService.associate(null);
524                     handler.handle(pathInContext, baseRequest, request, response);
525                     if (authenticator!=null)
526                         authenticator.secureResponse(request, response, isAuthMandatory, null);
527                 }
528             }
529             catch (ServerAuthException e)
530             {
531                 // jaspi 3.8.3 send HTTP 500 internal server error, with message
532                 // from AuthException
533                 response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
534             }
535             finally
536             {
537                 if (_identityService!=null)
538                     _identityService.disassociate(previousIdentity);
539             }
540         }
541         else
542             handler.handle(pathInContext, baseRequest, request, response);
543     }
544 
545 
546     /* ------------------------------------------------------------ */
547     public static SecurityHandler getCurrentSecurityHandler()
548     {
549         Context context = ContextHandler.getCurrentContext();
550         if (context==null)
551             return null;
552         
553         SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
554         return security;
555     }
556 
557     /* ------------------------------------------------------------ */
558     public void logout(Authentication.User user)
559     {
560         LOG.debug("logout {}",user);
561         LoginService login_service=getLoginService();
562         if (login_service!=null)
563         {
564             login_service.logout(user.getUserIdentity());
565         }
566         
567         IdentityService identity_service=getIdentityService();
568         if (identity_service!=null)
569         {
570             // TODO recover previous from threadlocal (or similar)
571             Object previous=null;
572             identity_service.disassociate(previous);
573         }
574     }
575     
576     /* ------------------------------------------------------------ */
577     protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
578 
579     /* ------------------------------------------------------------ */
580     protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
581 
582     /* ------------------------------------------------------------ */
583     protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
584 
585     /* ------------------------------------------------------------ */
586     protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
587                                                            UserIdentity userIdentity) throws IOException;
588 
589     
590     /* ------------------------------------------------------------ */
591     /* ------------------------------------------------------------ */
592     public class NotChecked implements Principal
593     {
594         public String getName()
595         {
596             return null;
597         }
598 
599         @Override
600         public String toString()
601         {
602             return "NOT CHECKED";
603         }
604 
605         public SecurityHandler getSecurityHandler()
606         {
607             return SecurityHandler.this;
608         }
609     }
610 
611     
612     /* ------------------------------------------------------------ */
613     /* ------------------------------------------------------------ */
614     public static Principal __NO_USER = new Principal()
615     {
616         public String getName()
617         {
618             return null;
619         }
620 
621         @Override
622         public String toString()
623         {
624             return "No User";
625         }
626     };
627     
628     /* ------------------------------------------------------------ */
629     /* ------------------------------------------------------------ */
630     /**
631      * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
632      * of authentication. A request with a Nobody UserPrincipal will be allowed
633      * past all authentication constraints - but will not be considered an
634      * authenticated request. It can be used by Authenticators such as
635      * FormAuthenticator to allow access to logon and error pages within an
636      * authenticated URI tree.
637      */
638     public static Principal __NOBODY = new Principal()
639     {
640         public String getName()
641         {
642             return "Nobody";
643         }
644 
645         @Override
646         public String toString()
647         {
648             return getName();
649         }
650     };
651 
652 }