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