View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.security;
20  
21  import java.io.IOException;
22  import java.security.Principal;
23  import java.util.Enumeration;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.servlet.ServletException;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.servlet.http.HttpSessionEvent;
33  import javax.servlet.http.HttpSessionListener;
34  
35  import org.eclipse.jetty.security.authentication.DeferredAuthentication;
36  import org.eclipse.jetty.server.AbstractHttpConnection;
37  import org.eclipse.jetty.server.Authentication;
38  import org.eclipse.jetty.server.Handler;
39  import org.eclipse.jetty.server.Request;
40  import org.eclipse.jetty.server.Response;
41  import org.eclipse.jetty.server.UserIdentity;
42  import org.eclipse.jetty.server.handler.ContextHandler;
43  import org.eclipse.jetty.server.handler.ContextHandler.Context;
44  import org.eclipse.jetty.server.handler.HandlerWrapper;
45  import org.eclipse.jetty.server.session.AbstractSessionManager;
46  import org.eclipse.jetty.util.component.LifeCycle;
47  import org.eclipse.jetty.util.log.Log;
48  import org.eclipse.jetty.util.log.Logger;
49  
50  /**
51   * Abstract SecurityHandler.
52   * Select and apply an {@link Authenticator} to a request.
53   * <p>
54   * The Authenticator may either be directly set on the handler
55   * or will be create during {@link #start()} with a call to
56   * either the default or set AuthenticatorFactory.
57   * <p>
58   * SecurityHandler has a set of initparameters that are used by the 
59   * Authentication.Configuration. At startup, any context init parameters
60   * that start with "org.eclipse.jetty.security." that do not have 
61   * values in the SecurityHandler init parameters, are copied.  
62   * 
63   */
64  public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
65  {
66      private static final Logger LOG = Log.getLogger(SecurityHandler.class);
67  
68      /* ------------------------------------------------------------ */
69      private boolean _checkWelcomeFiles = false;
70      private Authenticator _authenticator;
71      private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
72      private String _realmName;
73      private String _authMethod;
74      private final Map<String,String> _initParameters=new HashMap<String,String>();
75      private LoginService _loginService;
76      private boolean _loginServiceShared;
77      private IdentityService _identityService;
78      private boolean _renewSession=true;
79  
80      /* ------------------------------------------------------------ */
81      protected SecurityHandler()
82      {
83      }
84      
85      /* ------------------------------------------------------------ */
86      /** Get the identityService.
87       * @return the identityService
88       */
89      public IdentityService getIdentityService()
90      {
91          return _identityService;
92      }
93  
94      /* ------------------------------------------------------------ */
95      /** Set the identityService.
96       * @param identityService the identityService to set
97       */
98      public void setIdentityService(IdentityService identityService)
99      {
100         if (isStarted())
101             throw new IllegalStateException("Started");
102         _identityService = identityService;
103     }
104 
105     /* ------------------------------------------------------------ */
106     /** Get the loginService.
107      * @return the loginService
108      */
109     public LoginService getLoginService()
110     {
111         return _loginService;
112     }
113 
114     /* ------------------------------------------------------------ */
115     /** Set the loginService.
116      * @param loginService the loginService to set
117      */
118     public void setLoginService(LoginService loginService)
119     {
120         if (isStarted())
121             throw new IllegalStateException("Started");
122         _loginService = loginService;
123         _loginServiceShared=false;
124     }
125 
126 
127     /* ------------------------------------------------------------ */
128     public Authenticator getAuthenticator()
129     {
130         return _authenticator;
131     }
132 
133     /* ------------------------------------------------------------ */
134     /** Set the authenticator.
135      * @param authenticator
136      * @throws IllegalStateException if the SecurityHandler is running
137      */
138     public void setAuthenticator(Authenticator authenticator)
139     {
140         if (isStarted())
141             throw new IllegalStateException("Started");
142         _authenticator = authenticator;
143     }
144 
145     /* ------------------------------------------------------------ */
146     /**
147      * @return the authenticatorFactory
148      */
149     public Authenticator.Factory getAuthenticatorFactory()
150     {
151         return _authenticatorFactory;
152     }
153 
154     /* ------------------------------------------------------------ */
155     /**
156      * @param authenticatorFactory the authenticatorFactory to set
157      * @throws IllegalStateException if the SecurityHandler is running
158      */
159     public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
160     {
161         if (isRunning())
162             throw new IllegalStateException("running");
163         _authenticatorFactory = authenticatorFactory;
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     protected LoginService findLoginService()
258     {
259         List<LoginService> list = getServer().getBeans(LoginService.class);
260         
261         String realm=getRealmName();
262         if (realm!=null)
263         {
264             for (LoginService service : list)
265                 if (service.getName()!=null && service.getName().equals(realm))
266                     return service;
267         }
268         else if (list.size()==1)
269             return list.get(0);
270         return null;
271     }
272     
273     /* ------------------------------------------------------------ */
274     protected IdentityService findIdentityService()
275     {
276         return getServer().getBean(IdentityService.class);
277     }
278     
279     /* ------------------------------------------------------------ */
280     /** 
281      */
282     @Override
283     protected void doStart()
284         throws Exception
285     {
286         // copy security init parameters
287         ContextHandler.Context context =ContextHandler.getCurrentContext();
288         if (context!=null)
289         {
290             Enumeration<String> names=context.getInitParameterNames();
291             while (names!=null && names.hasMoreElements())
292             {
293                 String name =names.nextElement();
294                 if (name.startsWith("org.eclipse.jetty.security.") &&
295                         getInitParameter(name)==null)
296                     setInitParameter(name,context.getInitParameter(name));
297             }
298             
299             //register a session listener to handle securing sessions when authentication is performed
300             context.getContextHandler().addEventListener(new HttpSessionListener()
301             {
302                 
303                 public void sessionDestroyed(HttpSessionEvent se)
304                 {
305                    
306                 }
307                 
308                 public void sessionCreated(HttpSessionEvent se)
309                 {
310                     //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
311                     AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
312                     if (connection == null)
313                         return;
314                     Request request = connection.getRequest();
315                     if (request == null)
316                         return;
317                     
318                     if (request.isSecure())
319                     {
320                         se.getSession().setAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
321                     }
322                 }
323             });
324         }
325         
326         // complicated resolution of login and identity service to handle
327         // many different ways these can be constructed and injected.
328         
329         if (_loginService==null)
330         {
331             _loginService=findLoginService();
332             if (_loginService!=null)
333                 _loginServiceShared=true;
334         }
335         
336         if (_identityService==null)
337         {
338             if (_loginService!=null)
339                 _identityService=_loginService.getIdentityService();
340 
341             if (_identityService==null)
342                 _identityService=findIdentityService();
343             
344             if (_identityService==null && _realmName!=null)
345                 _identityService=new DefaultIdentityService();
346         }
347         
348         if (_loginService!=null)
349         {
350             if (_loginService.getIdentityService()==null)
351                 _loginService.setIdentityService(_identityService);
352             else if (_loginService.getIdentityService()!=_identityService)
353                 throw new IllegalStateException("LoginService has different IdentityService to "+this);
354         }
355 
356         if (!_loginServiceShared && _loginService instanceof LifeCycle)
357             ((LifeCycle)_loginService).start();        
358         
359         if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
360         {
361             _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
362             if (_authenticator!=null)
363                 _authMethod=_authenticator.getAuthMethod();
364         }
365 
366         if (_authenticator==null)
367         {
368             if (_realmName!=null)
369             {
370                 LOG.warn("No ServerAuthentication for "+this);
371                 throw new IllegalStateException("No ServerAuthentication");
372             }
373         }
374         else
375         {
376             _authenticator.setConfiguration(this);
377             if (_authenticator instanceof LifeCycle)
378                 ((LifeCycle)_authenticator).start();
379         }
380 
381         super.doStart();
382     }
383 
384     /* ------------------------------------------------------------ */
385     /**
386      * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
387      */
388     @Override
389     protected void doStop() throws Exception
390     {
391         super.doStop();
392         
393         if (!_loginServiceShared && _loginService instanceof LifeCycle)
394             ((LifeCycle)_loginService).stop();
395         
396     }
397 
398     /* ------------------------------------------------------------ */
399     protected boolean checkSecurity(Request request)
400     {
401         switch(request.getDispatcherType())
402         {
403             case REQUEST:
404             case ASYNC:
405                 return true;
406             case FORWARD:
407                 if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
408                 {
409                     request.removeAttribute("org.eclipse.jetty.server.welcome");
410                     return true;
411                 }
412                 return false;
413             default:
414                 return false;
415         }
416     }
417     
418     /* ------------------------------------------------------------ */
419     /**
420      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
421      */
422     public boolean isSessionRenewedOnAuthentication()
423     {
424         return _renewSession;
425     }
426     
427     /* ------------------------------------------------------------ */
428     /** Set renew the session on Authentication.
429      * <p>
430      * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
431      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
432      */
433     public void setSessionRenewedOnAuthentication(boolean renew)
434     {
435         _renewSession=renew;
436     }
437     
438     /* ------------------------------------------------------------ */
439     /*
440      * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
441      *      javax.servlet.http.HttpServletRequest,
442      *      javax.servlet.http.HttpServletResponse, int)
443      */
444     @Override
445     public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
446     {
447         final Response base_response = baseRequest.getResponse();
448         final Handler handler=getHandler();
449         
450         if (handler==null)
451             return;
452 
453         final Authenticator authenticator = _authenticator;
454         
455         if (checkSecurity(baseRequest))
456         {
457             Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
458             
459             // Check data constraints
460             if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
461             {
462                 if (!baseRequest.isHandled())
463                 {
464                     response.sendError(Response.SC_FORBIDDEN);
465                     baseRequest.setHandled(true);
466                 }
467                 return;
468             }
469 
470             // is Auth mandatory?
471             boolean isAuthMandatory = 
472                 isAuthMandatory(baseRequest, base_response, constraintInfo);
473 
474             if (isAuthMandatory && authenticator==null)
475             {
476                 LOG.warn("No authenticator for: "+constraintInfo);
477                 if (!baseRequest.isHandled())
478                 {
479                     response.sendError(Response.SC_FORBIDDEN);
480                     baseRequest.setHandled(true);
481                 }
482                 return;
483             }
484             
485             // check authentication
486             Object previousIdentity = null;
487             try
488             {
489                 Authentication authentication = baseRequest.getAuthentication();
490                 if (authentication==null || authentication==Authentication.NOT_CHECKED)
491                     authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
492 
493                 if (authentication instanceof Authentication.Wrapped)
494                 {
495                     request=((Authentication.Wrapped)authentication).getHttpServletRequest();
496                     response=((Authentication.Wrapped)authentication).getHttpServletResponse();
497                 }
498 
499                 if (authentication instanceof Authentication.ResponseSent)
500                 {
501                     baseRequest.setHandled(true);
502                 }
503                 else if (authentication instanceof Authentication.User)
504                 {
505                     Authentication.User userAuth = (Authentication.User)authentication;
506                     baseRequest.setAuthentication(authentication);
507                     if (_identityService!=null)
508                         previousIdentity = _identityService.associate(userAuth.getUserIdentity());
509 
510                     if (isAuthMandatory)
511                     {
512                         boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
513                         if (!authorized)
514                         {
515                             response.sendError(Response.SC_FORBIDDEN, "!role");
516                             baseRequest.setHandled(true);
517                             return;
518                         }
519                     }
520                          
521                     handler.handle(pathInContext, baseRequest, request, response);
522                     if (authenticator!=null)
523                         authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
524                 }
525                 else if (authentication instanceof Authentication.Deferred)
526                 {
527                     DeferredAuthentication deferred= (DeferredAuthentication)authentication;
528                     baseRequest.setAuthentication(authentication);
529 
530                     try
531                     {
532                         handler.handle(pathInContext, baseRequest, request, response);
533                     }
534                     finally
535                     {
536                         previousIdentity = deferred.getPreviousAssociation();
537                     }
538 
539                     if (authenticator!=null)
540                     {
541                         Authentication auth=baseRequest.getAuthentication();
542                         if (auth instanceof Authentication.User)
543                         {
544                             Authentication.User userAuth = (Authentication.User)auth;
545                             authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
546                         }
547                         else
548                             authenticator.secureResponse(request, response, isAuthMandatory, null);
549                     }
550                 }
551                 else
552                 {
553                     baseRequest.setAuthentication(authentication);
554                     if (_identityService!=null)
555                         previousIdentity = _identityService.associate(null);
556                     handler.handle(pathInContext, baseRequest, request, response);
557                     if (authenticator!=null)
558                         authenticator.secureResponse(request, response, isAuthMandatory, null);
559                 }
560             }
561             catch (ServerAuthException e)
562             {
563                 // jaspi 3.8.3 send HTTP 500 internal server error, with message
564                 // from AuthException
565                 response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
566             }
567             finally
568             {
569                 if (_identityService!=null)
570                     _identityService.disassociate(previousIdentity);
571             }
572         }
573         else
574             handler.handle(pathInContext, baseRequest, request, response);
575     }
576 
577 
578     /* ------------------------------------------------------------ */
579     public static SecurityHandler getCurrentSecurityHandler()
580     {
581         Context context = ContextHandler.getCurrentContext();
582         if (context==null)
583             return null;
584         
585         SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
586         return security;
587     }
588 
589     /* ------------------------------------------------------------ */
590     public void logout(Authentication.User user)
591     {
592         LOG.debug("logout {}",user);
593         LoginService login_service=getLoginService();
594         if (login_service!=null)
595         {
596             login_service.logout(user.getUserIdentity());
597         }
598         
599         IdentityService identity_service=getIdentityService();
600         if (identity_service!=null)
601         {
602             // TODO recover previous from threadlocal (or similar)
603             Object previous=null;
604             identity_service.disassociate(previous);
605         }
606     }
607     
608     /* ------------------------------------------------------------ */
609     protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
610 
611     /* ------------------------------------------------------------ */
612     protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
613 
614     /* ------------------------------------------------------------ */
615     protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
616 
617     /* ------------------------------------------------------------ */
618     protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
619                                                            UserIdentity userIdentity) throws IOException;
620 
621     
622     /* ------------------------------------------------------------ */
623     /* ------------------------------------------------------------ */
624     public class NotChecked implements Principal
625     {
626         public String getName()
627         {
628             return null;
629         }
630 
631         @Override
632         public String toString()
633         {
634             return "NOT CHECKED";
635         }
636 
637         public SecurityHandler getSecurityHandler()
638         {
639             return SecurityHandler.this;
640         }
641     }
642 
643     
644     /* ------------------------------------------------------------ */
645     /* ------------------------------------------------------------ */
646     public static Principal __NO_USER = new Principal()
647     {
648         public String getName()
649         {
650             return null;
651         }
652 
653         @Override
654         public String toString()
655         {
656             return "No User";
657         }
658     };
659     
660     /* ------------------------------------------------------------ */
661     /* ------------------------------------------------------------ */
662     /**
663      * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
664      * of authentication. A request with a Nobody UserPrincipal will be allowed
665      * past all authentication constraints - but will not be considered an
666      * authenticated request. It can be used by Authenticators such as
667      * FormAuthenticator to allow access to logon and error pages within an
668      * authenticated URI tree.
669      */
670     public static Principal __NOBODY = new Principal()
671     {
672         public String getName()
673         {
674             return "Nobody";
675         }
676 
677         @Override
678         public String toString()
679         {
680             return getName();
681         }
682     };
683 
684 }