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