]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/security/SecurityHandler.java
Update notes about password security
[gigi.git] / lib / jetty / org / eclipse / jetty / security / SecurityHandler.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 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.Collection;
24 import java.util.Enumeration;
25 import java.util.HashMap;
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.Authentication;
37 import org.eclipse.jetty.server.Handler;
38 import org.eclipse.jetty.server.HttpChannel;
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.AbstractSession;
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 IdentityService _identityService;
77     private boolean _renewSession=true;
78
79     /* ------------------------------------------------------------ */
80     protected SecurityHandler()
81     {
82         addBean(_authenticatorFactory);
83     }
84
85     /* ------------------------------------------------------------ */
86     /** Get the identityService.
87      * @return the identityService
88      */
89     @Override
90     public IdentityService getIdentityService()
91     {
92         return _identityService;
93     }
94
95     /* ------------------------------------------------------------ */
96     /** Set the identityService.
97      * @param identityService the identityService to set
98      */
99     public void setIdentityService(IdentityService identityService)
100     {
101         if (isStarted())
102             throw new IllegalStateException("Started");
103         updateBean(_identityService,identityService);
104         _identityService = identityService;
105     }
106
107     /* ------------------------------------------------------------ */
108     /** Get the loginService.
109      * @return the loginService
110      */
111     @Override
112     public LoginService getLoginService()
113     {
114         return _loginService;
115     }
116
117     /* ------------------------------------------------------------ */
118     /** Set the loginService.
119      * @param loginService the loginService to set
120      */
121     public void setLoginService(LoginService loginService)
122     {
123         if (isStarted())
124             throw new IllegalStateException("Started");
125         updateBean(_loginService,loginService);
126         _loginService = loginService;
127     }
128
129
130     /* ------------------------------------------------------------ */
131     public Authenticator getAuthenticator()
132     {
133         return _authenticator;
134     }
135
136     /* ------------------------------------------------------------ */
137     /** Set the authenticator.
138      * @param authenticator
139      * @throws IllegalStateException if the SecurityHandler is running
140      */
141     public void setAuthenticator(Authenticator authenticator)
142     {
143         if (isStarted())
144             throw new IllegalStateException("Started");
145         updateBean(_authenticator,authenticator);
146         _authenticator = authenticator;
147         if (_authenticator!=null)
148             _authMethod=_authenticator.getAuthMethod();
149     }
150
151     /* ------------------------------------------------------------ */
152     /**
153      * @return the authenticatorFactory
154      */
155     public Authenticator.Factory getAuthenticatorFactory()
156     {
157         return _authenticatorFactory;
158     }
159
160     /* ------------------------------------------------------------ */
161     /**
162      * @param authenticatorFactory the authenticatorFactory to set
163      * @throws IllegalStateException if the SecurityHandler is running
164      */
165     public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
166     {
167         if (isRunning())
168             throw new IllegalStateException("running");
169         updateBean(_authenticatorFactory,authenticatorFactory);
170         _authenticatorFactory = authenticatorFactory;
171     }
172
173     /* ------------------------------------------------------------ */
174     /**
175      * @return the realmName
176      */
177     @Override
178     public String getRealmName()
179     {
180         return _realmName;
181     }
182
183     /* ------------------------------------------------------------ */
184     /**
185      * @param realmName the realmName to set
186      * @throws IllegalStateException if the SecurityHandler is running
187      */
188     public void setRealmName(String realmName)
189     {
190         if (isRunning())
191             throw new IllegalStateException("running");
192         _realmName = realmName;
193     }
194
195     /* ------------------------------------------------------------ */
196     /**
197      * @return the authMethod
198      */
199     @Override
200     public String getAuthMethod()
201     {
202         return _authMethod;
203     }
204
205     /* ------------------------------------------------------------ */
206     /**
207      * @param authMethod the authMethod to set
208      * @throws IllegalStateException if the SecurityHandler is running
209      */
210     public void setAuthMethod(String authMethod)
211     {
212         if (isRunning())
213             throw new IllegalStateException("running");
214         _authMethod = authMethod;
215     }
216
217     /* ------------------------------------------------------------ */
218     /**
219      * @return True if forwards to welcome files are authenticated
220      */
221     public boolean isCheckWelcomeFiles()
222     {
223         return _checkWelcomeFiles;
224     }
225
226     /* ------------------------------------------------------------ */
227     /**
228      * @param authenticateWelcomeFiles True if forwards to welcome files are
229      *                authenticated
230      * @throws IllegalStateException if the SecurityHandler is running
231      */
232     public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
233     {
234         if (isRunning())
235             throw new IllegalStateException("running");
236         _checkWelcomeFiles = authenticateWelcomeFiles;
237     }
238
239     /* ------------------------------------------------------------ */
240     @Override
241     public String getInitParameter(String key)
242     {
243         return _initParameters.get(key);
244     }
245
246     /* ------------------------------------------------------------ */
247     @Override
248     public Set<String> getInitParameterNames()
249     {
250         return _initParameters.keySet();
251     }
252
253     /* ------------------------------------------------------------ */
254     /** Set an initialization parameter.
255      * @param key
256      * @param value
257      * @return previous value
258      * @throws IllegalStateException if the SecurityHandler is running
259      */
260     public String setInitParameter(String key, String value)
261     {
262         if (isRunning())
263             throw new IllegalStateException("running");
264         return _initParameters.put(key,value);
265     }
266
267     /* ------------------------------------------------------------ */
268     protected LoginService findLoginService() throws Exception
269     {
270         Collection<LoginService> list = getServer().getBeans(LoginService.class);
271         LoginService service = null;
272         String realm=getRealmName();
273         if (realm!=null)
274         {
275             for (LoginService s : list)
276                 if (s.getName()!=null && s.getName().equals(realm))
277                 {
278                     service=s;
279                     break;
280                 }
281         }
282         else if (list.size()==1)
283             service =  list.iterator().next();
284         
285         return service;
286     }
287
288     /* ------------------------------------------------------------ */
289     protected IdentityService findIdentityService()
290     {
291         return getServer().getBean(IdentityService.class);
292     }
293
294     /* ------------------------------------------------------------ */
295     /**
296      */
297     @Override
298     protected void doStart()
299         throws Exception
300     {
301         // copy security init parameters
302         ContextHandler.Context context =ContextHandler.getCurrentContext();
303         if (context!=null)
304         {
305             Enumeration<String> names=context.getInitParameterNames();
306             while (names!=null && names.hasMoreElements())
307             {
308                 String name =names.nextElement();
309                 if (name.startsWith("org.eclipse.jetty.security.") &&
310                         getInitParameter(name)==null)
311                     setInitParameter(name,context.getInitParameter(name));
312             }
313             
314             //register a session listener to handle securing sessions when authentication is performed
315             context.getContextHandler().addEventListener(new HttpSessionListener()
316             {
317                 @Override
318                 public void sessionDestroyed(HttpSessionEvent se)
319                 {
320                 }
321
322                 @Override
323                 public void sessionCreated(HttpSessionEvent se)
324                 {                    
325                     //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
326                     HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();              
327                     
328                     if (channel == null)
329                         return;
330                     Request request = channel.getRequest();
331                     if (request == null)
332                         return;
333                     
334                     if (request.isSecure())
335                     {
336                         se.getSession().setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
337                     }
338                 }
339             });
340         }
341
342         // complicated resolution of login and identity service to handle
343         // many different ways these can be constructed and injected.
344
345         if (_loginService==null)
346         {
347             setLoginService(findLoginService());
348             if (_loginService!=null)
349                 unmanage(_loginService);
350         }
351         
352         if (_identityService==null)
353         {
354             if (_loginService!=null)
355                 setIdentityService(_loginService.getIdentityService());
356
357             if (_identityService==null)
358                 setIdentityService(findIdentityService());
359
360             if (_identityService==null)
361             {
362                 if (_realmName!=null)
363                 { 
364                     setIdentityService(new DefaultIdentityService());
365                     manage(_identityService);
366                 }
367             }
368             else
369                 unmanage(_identityService);
370         }
371
372         if (_loginService!=null)
373         {
374             if (_loginService.getIdentityService()==null)
375                 _loginService.setIdentityService(_identityService);
376             else if (_loginService.getIdentityService()!=_identityService)
377                 throw new IllegalStateException("LoginService has different IdentityService to "+this);
378         }
379
380         Authenticator.Factory authenticatorFactory = getAuthenticatorFactory();
381         if (_authenticator==null && authenticatorFactory!=null && _identityService!=null)
382             setAuthenticator(authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService));
383
384         if (_authenticator!=null)
385             _authenticator.setConfiguration(this);
386         else if (_realmName!=null)
387         {
388             LOG.warn("No Authenticator for "+this);
389             throw new IllegalStateException("No Authenticator");
390         }
391
392         super.doStart();
393     }
394
395     @Override
396     /* ------------------------------------------------------------ */
397     protected void doStop() throws Exception
398     {
399         //if we discovered the services (rather than had them explicitly configured), remove them.
400         if (!isManaged(_identityService))
401         {
402             removeBean(_identityService);
403             _identityService = null;   
404         }
405         
406         if (!isManaged(_loginService))
407         {
408             removeBean(_loginService);
409             _loginService=null;
410         }
411         
412         super.doStop();
413     }
414
415     /* ------------------------------------------------------------ */
416     protected boolean checkSecurity(Request request)
417     {
418         switch(request.getDispatcherType())
419         {
420             case REQUEST:
421             case ASYNC:
422                 return true;
423             case FORWARD:
424                 if (isCheckWelcomeFiles() && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
425                 {
426                     request.removeAttribute("org.eclipse.jetty.server.welcome");
427                     return true;
428                 }
429                 return false;
430             default:
431                 return false;
432         }
433     }
434
435     /* ------------------------------------------------------------ */
436     /**
437      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
438      */
439     @Override
440     public boolean isSessionRenewedOnAuthentication()
441     {
442         return _renewSession;
443     }
444
445     /* ------------------------------------------------------------ */
446     /** Set renew the session on Authentication.
447      * <p>
448      * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
449      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
450      */
451     public void setSessionRenewedOnAuthentication(boolean renew)
452     {
453         _renewSession=renew;
454     }
455
456     /* ------------------------------------------------------------ */
457     /*
458      * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
459      *      javax.servlet.http.HttpServletRequest,
460      *      javax.servlet.http.HttpServletResponse, int)
461      */
462     @Override
463     public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
464     {
465         final Response base_response = baseRequest.getResponse();
466         final Handler handler=getHandler();
467
468         if (handler==null)
469             return;
470
471         final Authenticator authenticator = _authenticator;
472
473         if (checkSecurity(baseRequest))
474         {
475             //See Servlet Spec 3.1 sec 13.6.3
476             if (authenticator != null)
477                 authenticator.prepareRequest(baseRequest);
478             
479             RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
480
481             // Check data constraints
482             if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, roleInfo))
483             {
484                 if (!baseRequest.isHandled())
485                 {
486                     response.sendError(HttpServletResponse.SC_FORBIDDEN);
487                     baseRequest.setHandled(true);
488                 }
489                 return;
490             }
491
492             // is Auth mandatory?
493             boolean isAuthMandatory =
494                 isAuthMandatory(baseRequest, base_response, roleInfo);
495
496             if (isAuthMandatory && authenticator==null)
497             {
498                 LOG.warn("No authenticator for: "+roleInfo);
499                 if (!baseRequest.isHandled())
500                 {
501                     response.sendError(HttpServletResponse.SC_FORBIDDEN);
502                     baseRequest.setHandled(true);
503                 }
504                 return;
505             }
506
507             // check authentication
508             Object previousIdentity = null;
509             try
510             {
511                 Authentication authentication = baseRequest.getAuthentication();
512                 if (authentication==null || authentication==Authentication.NOT_CHECKED)
513                     authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
514
515                 if (authentication instanceof Authentication.Wrapped)
516                 {
517                     request=((Authentication.Wrapped)authentication).getHttpServletRequest();
518                     response=((Authentication.Wrapped)authentication).getHttpServletResponse();
519                 }
520
521                 if (authentication instanceof Authentication.ResponseSent)
522                 {
523                     baseRequest.setHandled(true);
524                 }
525                 else if (authentication instanceof Authentication.User)
526                 {
527                     Authentication.User userAuth = (Authentication.User)authentication;
528                     baseRequest.setAuthentication(authentication);
529                     if (_identityService!=null)
530                         previousIdentity = _identityService.associate(userAuth.getUserIdentity());
531
532                     if (isAuthMandatory)
533                     {
534                         boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
535                         if (!authorized)
536                         {
537                             response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
538                             baseRequest.setHandled(true);
539                             return;
540                         }
541                     }
542
543                     handler.handle(pathInContext, baseRequest, request, response);
544                     if (authenticator!=null)
545                         authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
546                 }
547                 else if (authentication instanceof Authentication.Deferred)
548                 {
549                     DeferredAuthentication deferred= (DeferredAuthentication)authentication;
550                     baseRequest.setAuthentication(authentication);
551
552                     try
553                     {
554                         handler.handle(pathInContext, baseRequest, request, response);
555                     }
556                     finally
557                     {
558                         previousIdentity = deferred.getPreviousAssociation();
559                     }
560
561                     if (authenticator!=null)
562                     {
563                         Authentication auth=baseRequest.getAuthentication();
564                         if (auth instanceof Authentication.User)
565                         {
566                             Authentication.User userAuth = (Authentication.User)auth;
567                             authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
568                         }
569                         else
570                             authenticator.secureResponse(request, response, isAuthMandatory, null);
571                     }
572                 }
573                 else
574                 {
575                     baseRequest.setAuthentication(authentication);
576                     if (_identityService!=null)
577                         previousIdentity = _identityService.associate(null);
578                     handler.handle(pathInContext, baseRequest, request, response);
579                     if (authenticator!=null)
580                         authenticator.secureResponse(request, response, isAuthMandatory, null);
581                 }
582             }
583             catch (ServerAuthException e)
584             {
585                 // jaspi 3.8.3 send HTTP 500 internal server error, with message
586                 // from AuthException
587                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
588             }
589             finally
590             {
591                 if (_identityService!=null)
592                     _identityService.disassociate(previousIdentity);
593             }
594         }
595         else
596             handler.handle(pathInContext, baseRequest, request, response);
597     }
598
599
600     /* ------------------------------------------------------------ */
601     public static SecurityHandler getCurrentSecurityHandler()
602     {
603         Context context = ContextHandler.getCurrentContext();
604         if (context==null)
605             return null;
606
607         return context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
608     }
609
610     /* ------------------------------------------------------------ */
611     public void logout(Authentication.User user)
612     {
613         LOG.debug("logout {}",user);
614         LoginService login_service=getLoginService();
615         if (login_service!=null)
616         {
617             login_service.logout(user.getUserIdentity());
618         }
619
620         IdentityService identity_service=getIdentityService();
621         if (identity_service!=null)
622         {
623             // TODO recover previous from threadlocal (or similar)
624             Object previous=null;
625             identity_service.disassociate(previous);
626         }
627     }
628
629     /* ------------------------------------------------------------ */
630     protected abstract RoleInfo prepareConstraintInfo(String pathInContext, Request request);
631
632     /* ------------------------------------------------------------ */
633     protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo constraintInfo) throws IOException;
634
635     /* ------------------------------------------------------------ */
636     protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
637
638     /* ------------------------------------------------------------ */
639     protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
640                                                            UserIdentity userIdentity) throws IOException;
641
642
643     /* ------------------------------------------------------------ */
644     /* ------------------------------------------------------------ */
645     public class NotChecked implements Principal
646     {
647         @Override
648         public String getName()
649         {
650             return null;
651         }
652
653         @Override
654         public String toString()
655         {
656             return "NOT CHECKED";
657         }
658
659         public SecurityHandler getSecurityHandler()
660         {
661             return SecurityHandler.this;
662         }
663     }
664
665
666     /* ------------------------------------------------------------ */
667     /* ------------------------------------------------------------ */
668     public static final Principal __NO_USER = new Principal()
669     {
670         @Override
671         public String getName()
672         {
673             return null;
674         }
675
676         @Override
677         public String toString()
678         {
679             return "No User";
680         }
681     };
682
683     /* ------------------------------------------------------------ */
684     /* ------------------------------------------------------------ */
685     /**
686      * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
687      * of authentication. A request with a Nobody UserPrincipal will be allowed
688      * past all authentication constraints - but will not be considered an
689      * authenticated request. It can be used by Authenticators such as
690      * FormAuthenticator to allow access to logon and error pages within an
691      * authenticated URI tree.
692      */
693     public static final Principal __NOBODY = new Principal()
694     {
695         @Override
696         public String getName()
697         {
698             return "Nobody";
699         }
700
701         @Override
702         public String toString()
703         {
704             return getName();
705         }
706     };
707
708 }