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