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