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