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