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
19package org.eclipse.jetty.security;
20
21import java.io.IOException;
22import java.security.Principal;
23import java.util.Enumeration;
24import java.util.HashMap;
25import java.util.List;
26import java.util.Map;
27import java.util.Set;
28
29import javax.servlet.ServletException;
30import javax.servlet.http.HttpServletRequest;
31import javax.servlet.http.HttpServletResponse;
32import javax.servlet.http.HttpSessionEvent;
33import javax.servlet.http.HttpSessionListener;
34
35import org.eclipse.jetty.security.authentication.DeferredAuthentication;
36import org.eclipse.jetty.server.AbstractHttpConnection;
37import org.eclipse.jetty.server.Authentication;
38import org.eclipse.jetty.server.Handler;
39import org.eclipse.jetty.server.Request;
40import org.eclipse.jetty.server.Response;
41import org.eclipse.jetty.server.UserIdentity;
42import org.eclipse.jetty.server.handler.ContextHandler;
43import org.eclipse.jetty.server.handler.ContextHandler.Context;
44import org.eclipse.jetty.server.handler.HandlerWrapper;
45import org.eclipse.jetty.server.session.AbstractSessionManager;
46import org.eclipse.jetty.util.component.LifeCycle;
47import org.eclipse.jetty.util.log.Log;
48import 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 */
64public 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
339            if (_loginService!=null)
340                _identityService=_loginService.getIdentityService();
341
342            if (_identityService==null)
343                _identityService=findIdentityService();
344
345            if (_identityService==null && _realmName!=null)
346                _identityService=new DefaultIdentityService();
347        }
348
349        if (_loginService!=null)
350        {
351            if (_loginService.getIdentityService()==null)
352                _loginService.setIdentityService(_identityService);
353            else if (_loginService.getIdentityService()!=_identityService)
354                throw new IllegalStateException("LoginService has different IdentityService to "+this);
355        }
356
357        if (!_loginServiceShared && _loginService instanceof LifeCycle)
358            ((LifeCycle)_loginService).start();
359
360        if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
361        {
362            _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
363            if (_authenticator!=null)
364                _authMethod=_authenticator.getAuthMethod();
365        }
366
367        if (_authenticator==null)
368        {
369            if (_realmName!=null)
370            {
371                LOG.warn("No ServerAuthentication for "+this);
372                throw new IllegalStateException("No ServerAuthentication");
373            }
374        }
375        else
376        {
377            _authenticator.setConfiguration(this);
378            if (_authenticator instanceof LifeCycle)
379                ((LifeCycle)_authenticator).start();
380        }
381
382        super.doStart();
383    }
384
385    /* ------------------------------------------------------------ */
386    /**
387     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
388     */
389    @Override
390    protected void doStop() throws Exception
391    {
392        super.doStop();
393
394        if (!_loginServiceShared && _loginService instanceof LifeCycle)
395            ((LifeCycle)_loginService).stop();
396
397    }
398
399    /* ------------------------------------------------------------ */
400    protected boolean checkSecurity(Request request)
401    {
402        switch(request.getDispatcherType())
403        {
404            case REQUEST:
405            case ASYNC:
406                return true;
407            case FORWARD:
408                if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
409                {
410                    request.removeAttribute("org.eclipse.jetty.server.welcome");
411                    return true;
412                }
413                return false;
414            default:
415                return false;
416        }
417    }
418
419    /* ------------------------------------------------------------ */
420    /**
421     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
422     */
423    public boolean isSessionRenewedOnAuthentication()
424    {
425        return _renewSession;
426    }
427
428    /* ------------------------------------------------------------ */
429    /** Set renew the session on Authentication.
430     * <p>
431     * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
432     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
433     */
434    public void setSessionRenewedOnAuthentication(boolean renew)
435    {
436        _renewSession=renew;
437    }
438
439    /* ------------------------------------------------------------ */
440    /*
441     * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
442     *      javax.servlet.http.HttpServletRequest,
443     *      javax.servlet.http.HttpServletResponse, int)
444     */
445    @Override
446    public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
447    {
448        final Response base_response = baseRequest.getResponse();
449        final Handler handler=getHandler();
450
451        if (handler==null)
452            return;
453
454        final Authenticator authenticator = _authenticator;
455
456        if (checkSecurity(baseRequest))
457        {
458            Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
459
460            // Check data constraints
461            if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
462            {
463                if (!baseRequest.isHandled())
464                {
465                    response.sendError(Response.SC_FORBIDDEN);
466                    baseRequest.setHandled(true);
467                }
468                return;
469            }
470
471            // is Auth mandatory?
472            boolean isAuthMandatory =
473                isAuthMandatory(baseRequest, base_response, constraintInfo);
474
475            if (isAuthMandatory && authenticator==null)
476            {
477                LOG.warn("No authenticator for: "+constraintInfo);
478                if (!baseRequest.isHandled())
479                {
480                    response.sendError(Response.SC_FORBIDDEN);
481                    baseRequest.setHandled(true);
482                }
483                return;
484            }
485
486            // check authentication
487            Object previousIdentity = null;
488            try
489            {
490                Authentication authentication = baseRequest.getAuthentication();
491                if (authentication==null || authentication==Authentication.NOT_CHECKED)
492                    authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
493
494                if (authentication instanceof Authentication.Wrapped)
495                {
496                    request=((Authentication.Wrapped)authentication).getHttpServletRequest();
497                    response=((Authentication.Wrapped)authentication).getHttpServletResponse();
498                }
499
500                if (authentication instanceof Authentication.ResponseSent)
501                {
502                    baseRequest.setHandled(true);
503                }
504                else if (authentication instanceof Authentication.User)
505                {
506                    Authentication.User userAuth = (Authentication.User)authentication;
507                    baseRequest.setAuthentication(authentication);
508                    if (_identityService!=null)
509                        previousIdentity = _identityService.associate(userAuth.getUserIdentity());
510
511                    if (isAuthMandatory)
512                    {
513                        boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
514                        if (!authorized)
515                        {
516                            response.sendError(Response.SC_FORBIDDEN, "!role");
517                            baseRequest.setHandled(true);
518                            return;
519                        }
520                    }
521
522                    handler.handle(pathInContext, baseRequest, request, response);
523                    if (authenticator!=null)
524                        authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
525                }
526                else if (authentication instanceof Authentication.Deferred)
527                {
528                    DeferredAuthentication deferred= (DeferredAuthentication)authentication;
529                    baseRequest.setAuthentication(authentication);
530
531                    try
532                    {
533                        handler.handle(pathInContext, baseRequest, request, response);
534                    }
535                    finally
536                    {
537                        previousIdentity = deferred.getPreviousAssociation();
538                    }
539
540                    if (authenticator!=null)
541                    {
542                        Authentication auth=baseRequest.getAuthentication();
543                        if (auth instanceof Authentication.User)
544                        {
545                            Authentication.User userAuth = (Authentication.User)auth;
546                            authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
547                        }
548                        else
549                            authenticator.secureResponse(request, response, isAuthMandatory, null);
550                    }
551                }
552                else
553                {
554                    baseRequest.setAuthentication(authentication);
555                    if (_identityService!=null)
556                        previousIdentity = _identityService.associate(null);
557                    handler.handle(pathInContext, baseRequest, request, response);
558                    if (authenticator!=null)
559                        authenticator.secureResponse(request, response, isAuthMandatory, null);
560                }
561            }
562            catch (ServerAuthException e)
563            {
564                // jaspi 3.8.3 send HTTP 500 internal server error, with message
565                // from AuthException
566                response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
567            }
568            finally
569            {
570                if (_identityService!=null)
571                    _identityService.disassociate(previousIdentity);
572            }
573        }
574        else
575            handler.handle(pathInContext, baseRequest, request, response);
576    }
577
578
579    /* ------------------------------------------------------------ */
580    public static SecurityHandler getCurrentSecurityHandler()
581    {
582        Context context = ContextHandler.getCurrentContext();
583        if (context==null)
584            return null;
585
586        SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
587        return security;
588    }
589
590    /* ------------------------------------------------------------ */
591    public void logout(Authentication.User user)
592    {
593        LOG.debug("logout {}",user);
594        LoginService login_service=getLoginService();
595        if (login_service!=null)
596        {
597            login_service.logout(user.getUserIdentity());
598        }
599
600        IdentityService identity_service=getIdentityService();
601        if (identity_service!=null)
602        {
603            // TODO recover previous from threadlocal (or similar)
604            Object previous=null;
605            identity_service.disassociate(previous);
606        }
607    }
608
609    /* ------------------------------------------------------------ */
610    protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
611
612    /* ------------------------------------------------------------ */
613    protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
614
615    /* ------------------------------------------------------------ */
616    protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
617
618    /* ------------------------------------------------------------ */
619    protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
620                                                           UserIdentity userIdentity) throws IOException;
621
622
623    /* ------------------------------------------------------------ */
624    /* ------------------------------------------------------------ */
625    public class NotChecked implements Principal
626    {
627        public String getName()
628        {
629            return null;
630        }
631
632        @Override
633        public String toString()
634        {
635            return "NOT CHECKED";
636        }
637
638        public SecurityHandler getSecurityHandler()
639        {
640            return SecurityHandler.this;
641        }
642    }
643
644
645    /* ------------------------------------------------------------ */
646    /* ------------------------------------------------------------ */
647    public static Principal __NO_USER = new Principal()
648    {
649        public String getName()
650        {
651            return null;
652        }
653
654        @Override
655        public String toString()
656        {
657            return "No User";
658        }
659    };
660
661    /* ------------------------------------------------------------ */
662    /* ------------------------------------------------------------ */
663    /**
664     * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
665     * of authentication. A request with a Nobody UserPrincipal will be allowed
666     * past all authentication constraints - but will not be considered an
667     * authenticated request. It can be used by Authenticators such as
668     * FormAuthenticator to allow access to logon and error pages within an
669     * authenticated URI tree.
670     */
671    public static Principal __NOBODY = new Principal()
672    {
673        public String getName()
674        {
675            return "Nobody";
676        }
677
678        @Override
679        public String toString()
680        {
681            return getName();
682        }
683    };
684
685}
686