]> WPIA git - gigi.git/blobdiff - lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / security / authentication / FormAuthenticator.java
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java
new file mode 100644 (file)
index 0000000..dcfea41
--- /dev/null
@@ -0,0 +1,564 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * FORM Authenticator.
+ *
+ * <p>This authenticator implements form authentication will use dispatchers to
+ * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
+ * Otherwise it will redirect.</p>
+ *
+ * <p>The form authenticator redirects unauthenticated requests to a log page
+ * which should use a form to gather username/password from the user and send them
+ * to the /j_security_check URI within the context.  FormAuthentication uses
+ * {@link SessionAuthentication} to wrap Authentication results so that they
+ * are  associated with the session.</p>
+ *
+ *
+ */
+public class FormAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
+
+    public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
+    public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
+    public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
+    public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
+    public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+    public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
+    public final static String __J_SECURITY_CHECK = "/j_security_check";
+    public final static String __J_USERNAME = "j_username";
+    public final static String __J_PASSWORD = "j_password";
+
+    private String _formErrorPage;
+    private String _formErrorPath;
+    private String _formLoginPage;
+    private String _formLoginPath;
+    private boolean _dispatch;
+    private boolean _alwaysSaveUri;
+
+    public FormAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public FormAuthenticator(String login,String error,boolean dispatch)
+    {
+        this();
+        if (login!=null)
+            setLoginPage(login);
+        if (error!=null)
+            setErrorPage(error);
+        _dispatch=dispatch;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * If true, uris that cause a redirect to a login page will always
+     * be remembered. If false, only the first uri that leads to a login
+     * page redirect is remembered.
+     * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
+     * @param alwaysSave
+     */
+    public void setAlwaysSaveUri (boolean alwaysSave)
+    {
+        _alwaysSaveUri = alwaysSave;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean getAlwaysSaveUri ()
+    {
+        return _alwaysSaveUri;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+     */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        super.setConfiguration(configuration);
+        String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
+        if (login!=null)
+            setLoginPage(login);
+        String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+        if (error!=null)
+            setErrorPage(error);
+        String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
+        _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__FORM_AUTH;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setLoginPage(String path)
+    {
+        if (!path.startsWith("/"))
+        {
+            LOG.warn("form-login-page must start with /");
+            path = "/" + path;
+        }
+        _formLoginPage = path;
+        _formLoginPath = path;
+        if (_formLoginPath.indexOf('?') > 0)
+            _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setErrorPage(String path)
+    {
+        if (path == null || path.trim().length() == 0)
+        {
+            _formErrorPath = null;
+            _formErrorPage = null;
+        }
+        else
+        {
+            if (!path.startsWith("/"))
+            {
+                LOG.warn("form-error-page must start with /");
+                path = "/" + path;
+            }
+            _formErrorPage = path;
+            _formErrorPath = path;
+
+            if (_formErrorPath.indexOf('?') > 0)
+                _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object password, ServletRequest request)
+    {
+        
+        UserIdentity user = super.login(username,password,request);
+        if (user!=null)
+        {
+            HttpSession session = ((HttpServletRequest)request).getSession(true);
+            Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
+            session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
+        }
+        return user;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prepareRequest(ServletRequest request)
+    {
+        //if this is a request resulting from a redirect after auth is complete
+        //(ie its from a redirect to the original request uri) then due to 
+        //browser handling of 302 redirects, the method may not be the same as
+        //that of the original request. Replace the method and original post
+        //params (if it was a post).
+        //
+        //See Servlet Spec 3.1 sec 13.6.3
+        HttpServletRequest httpRequest = (HttpServletRequest)request;
+        HttpSession session = httpRequest.getSession(false);
+        if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
+            return; //not authenticated yet
+        
+        String juri = (String)session.getAttribute(__J_URI);
+        if (juri == null || juri.length() == 0)
+            return; //no original uri saved
+        
+        String method = (String)session.getAttribute(__J_METHOD);
+        if (method == null || method.length() == 0)
+            return; //didn't save original request method
+       
+        StringBuffer buf = httpRequest.getRequestURL();
+        if (httpRequest.getQueryString() != null)
+            buf.append("?").append(httpRequest.getQueryString());
+        
+        if (!juri.equals(buf.toString()))
+            return; //this request is not for the same url as the original
+        
+        //restore the original request's method on this request
+        if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
+        Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+        HttpMethod m = HttpMethod.fromString(method);
+        base_request.setMethod(m,m.asString());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String uri = request.getRequestURI();
+        if (uri==null)
+            uri=URIUtil.SLASH;
+
+        mandatory|=isJSecurityCheck(uri);
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
+            return new DeferredAuthentication(this);
+
+        HttpSession session = request.getSession(true);
+
+        try
+        {
+            // Handle a request for authentication.
+            if (isJSecurityCheck(uri))
+            {
+                final String username = request.getParameter(__J_USERNAME);
+                final String password = request.getParameter(__J_PASSWORD);
+
+                UserIdentity user = login(username, password, request);
+                LOG.debug("jsecuritycheck {} {}",username,user);
+                session = request.getSession(true);
+                if (user!=null)
+                {                    
+                    // Redirect to original request
+                    String nuri;
+                    FormAuthentication form_auth;
+                    synchronized(session)
+                    {
+                        nuri = (String) session.getAttribute(__J_URI);
+
+                        if (nuri == null || nuri.length() == 0)
+                        {
+                            nuri = request.getContextPath();
+                            if (nuri.length() == 0)
+                                nuri = URIUtil.SLASH;
+                        }
+                        form_auth = new FormAuthentication(getAuthMethod(),user);
+                    }
+                    LOG.debug("authenticated {}->{}",form_auth,nuri);
+
+                    response.setContentLength(0);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
+                    return form_auth;
+                }
+
+                // not authenticated
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
+                if (_formErrorPage == null)
+                {
+                    LOG.debug("auth failed {}->403",username);
+                    if (response != null)
+                        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                }
+                else if (_dispatch)
+                {
+                    LOG.debug("auth failed {}=={}",username,_formErrorPage);
+                    RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
+                    response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                    response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+                    dispatcher.forward(new FormRequest(request), new FormResponse(response));
+                }
+                else
+                {
+                    LOG.debug("auth failed {}->{}",username,_formErrorPage);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+                }
+
+                return Authentication.SEND_FAILURE;
+            }
+
+            // Look for cached authentication
+            Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
+            if (authentication != null)
+            {
+                // Has authentication been revoked?
+                if (authentication instanceof Authentication.User &&
+                    _loginService!=null &&
+                    !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
+                {
+                    LOG.debug("auth revoked {}",authentication);
+                    session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
+                }
+                else
+                {
+                    synchronized (session)
+                    {
+                        String j_uri=(String)session.getAttribute(__J_URI);
+                        if (j_uri!=null)
+                        {
+                            //check if the request is for the same url as the original and restore
+                            //params if it was a post
+                            LOG.debug("auth retry {}->{}",authentication,j_uri);
+                            StringBuffer buf = request.getRequestURL();
+                            if (request.getQueryString() != null)
+                                buf.append("?").append(request.getQueryString());
+
+                            if (j_uri.equals(buf.toString()))
+                            {
+                                MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
+                                if (j_post!=null)
+                                {
+                                    LOG.debug("auth rePOST {}->{}",authentication,j_uri);
+                                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                                    base_request.setContentParameters(j_post);
+                                }
+                                session.removeAttribute(__J_URI);
+                                session.removeAttribute(__J_METHOD);
+                                session.removeAttribute(__J_POST);
+                            }
+                        }
+                    }
+                    LOG.debug("auth {}",authentication);
+                    return authentication;
+                }
+            }
+
+            // if we can't send challenge
+            if (DeferredAuthentication.isDeferred(response))
+            {
+                LOG.debug("auth deferred {}",session.getId());
+                return Authentication.UNAUTHENTICATED;
+            }
+
+            // remember the current URI
+            synchronized (session)
+            {
+                // But only if it is not set already, or we save every uri that leads to a login form redirect
+                if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
+                {
+                    StringBuffer buf = request.getRequestURL();
+                    if (request.getQueryString() != null)
+                        buf.append("?").append(request.getQueryString());
+                    session.setAttribute(__J_URI, buf.toString());
+                    session.setAttribute(__J_METHOD, request.getMethod());
+
+                    if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
+                    {
+                        Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
+                        MultiMap<String> formParameters = new MultiMap<>();
+                        base_request.extractFormParameters(formParameters);
+                        session.setAttribute(__J_POST, formParameters);
+                    }
+                }
+            }
+
+            // send the the challenge
+            if (_dispatch)
+            {
+                LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
+                RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+                dispatcher.forward(new FormRequest(request), new FormResponse(response));
+            }
+            else
+            {
+                LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
+                Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+            }
+            return Authentication.SEND_CONTINUE;
+        }
+        catch (IOException | ServletException e)
+        {
+            throw new ServerAuthException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isJSecurityCheck(String uri)
+    {
+        int jsc = uri.indexOf(__J_SECURITY_CHECK);
+
+        if (jsc<0)
+            return false;
+        int e=jsc+__J_SECURITY_CHECK.length();
+        if (e==uri.length())
+            return true;
+        char c = uri.charAt(e);
+        return c==';'||c=='#'||c=='/'||c=='?';
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLoginOrErrorPage(String pathInContext)
+    {
+        return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormRequest extends HttpServletRequestWrapper
+    {
+        public FormRequest(HttpServletRequest request)
+        {
+            super(request);
+        }
+
+        @Override
+        public long getDateHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return -1;
+            return super.getDateHeader(name);
+        }
+
+        @Override
+        public String getHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return null;
+            return super.getHeader(name);
+        }
+
+        @Override
+        public Enumeration<String> getHeaderNames()
+        {
+            return Collections.enumeration(Collections.list(super.getHeaderNames()));
+        }
+
+        @Override
+        public Enumeration<String> getHeaders(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return Collections.<String>enumeration(Collections.<String>emptyList());
+            return super.getHeaders(name);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormResponse extends HttpServletResponseWrapper
+    {
+        public FormResponse(HttpServletResponse response)
+        {
+            super(response);
+        }
+
+        @Override
+        public void addDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.addDateHeader(name,date);
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.addHeader(name,value);
+        }
+
+        @Override
+        public void setDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.setDateHeader(name,date);
+        }
+
+        @Override
+        public void setHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.setHeader(name,value);
+        }
+
+        private boolean notIgnored(String name)
+        {
+            if (HttpHeader.CACHE_CONTROL.is(name) ||
+                HttpHeader.PRAGMA.is(name) ||
+                HttpHeader.ETAG.is(name) ||
+                HttpHeader.EXPIRES.is(name) ||
+                HttpHeader.LAST_MODIFIED.is(name) ||
+                HttpHeader.AGE.is(name))
+                return false;
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** This Authentication represents a just completed Form authentication.
+     * Subsequent requests from the same user are authenticated by the presents
+     * of a {@link SessionAuthentication} instance in their session.
+     */
+    public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
+    {
+        public FormAuthentication(String method, UserIdentity userIdentity)
+        {
+            super(method,userIdentity);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "Form"+super.toString();
+        }
+    }
+}