Author: markt
Date: Fri Jul  3 18:58:24 2015
New Revision: 1689060

URL: http://svn.apache.org/r1689060
Log:
Port FORM auth to JASPIC
Patch by fjodorver

Modified:
    
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java

Modified: 
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java?rev=1689060&r1=1689059&r2=1689060&view=diff
==============================================================================
--- 
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java
 (original)
+++ 
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java
 Fri Jul  3 18:58:24 2015
@@ -16,20 +16,47 @@
  */
 package org.apache.catalina.authenticator.jaspic.provider.modules;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
 import java.util.Map;
 
 import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.message.AuthException;
 import javax.security.auth.message.AuthStatus;
 import javax.security.auth.message.MessageInfo;
 import javax.security.auth.message.MessagePolicy;
+import javax.security.auth.message.callback.CallerPrincipalCallback;
+import javax.security.auth.message.callback.GroupPrincipalCallback;
+import javax.security.auth.message.callback.PasswordValidationCallback;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.catalina.Context;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Session;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.authenticator.SavedRequest;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.coyote.ActionCode;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
+import org.apache.tomcat.util.http.MimeHeaders;
 
 /**
  * This class implements JASPIC FORM-based authentication.
@@ -61,8 +88,219 @@ public class FormAuthModule extends Tomc
         if (!isMandatory(messageInfo)) {
             return AuthStatus.SUCCESS;
         }
-        return AuthStatus.FAILURE;
+        try {
 
+            Request request = (Request) messageInfo.getRequestMessage();
+            HttpServletResponse response = (HttpServletResponse) 
messageInfo.getResponseMessage();
+            // References to objects we will need later
+            Session session = null;
+            Principal principal = null;
+
+            // Have we authenticated this user before but have caching 
disabled?
+            if (!isCache()) {
+                session = request.getSessionInternal(true);
+                if (log.isDebugEnabled()) {
+                    log.debug("Checking for reauthenticate in session " + 
session);
+                }
+                String username = (String) 
session.getNote(Constants.SESS_USERNAME_NOTE);
+                String password = (String) 
session.getNote(Constants.SESS_PASSWORD_NOTE);
+                if ((username != null) && (password != null)) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Reauthenticating username '" + username + 
"'");
+                    }
+                    PasswordValidationCallback passwordCallback = new 
PasswordValidationCallback(
+                            clientSubject, username, password.toCharArray());
+                    handler.handle(new Callback[] { passwordCallback });
+
+                    if (!passwordCallback.getResult()) {
+                        forwardToErrorPage(request, response, 
context.getLoginConfig());
+                    }
+                    principal = getPrincipal(passwordCallback);
+                    if (principal != null) {
+                        session.setNote(Constants.FORM_PRINCIPAL_NOTE, 
principal);
+                        if (!matchRequest(request)) {
+                            CallerPrincipalCallback principalCallback = new 
CallerPrincipalCallback(
+                                    clientSubject, principal);
+                            GroupPrincipalCallback groupCallback = new 
GroupPrincipalCallback(
+                                    clientSubject, 
context.getRealm().getRoles(principal));
+                            handler.handle(new Callback[] { principalCallback, 
groupCallback });
+                            return AuthStatus.SUCCESS;
+                        }
+                    }
+                    if (log.isDebugEnabled()) {
+                        log.debug("Reauthentication failed, proceed normally");
+                    }
+                }
+            }
+
+            // Is this the re-submit of the original request URI after
+            // successful
+            // authentication? If so, forward the *original* request instead.
+            if (matchRequest(request)) {
+                session = request.getSessionInternal(true);
+                if (log.isDebugEnabled()) {
+                    log.debug("Restore request from session '" + 
session.getIdInternal() + "'");
+                }
+                principal = (Principal) 
session.getNote(Constants.FORM_PRINCIPAL_NOTE);
+                CallerPrincipalCallback principalCallback = new 
CallerPrincipalCallback(
+                        clientSubject, principal);
+                GroupPrincipalCallback groupCallback = new 
GroupPrincipalCallback(clientSubject,
+                        context.getRealm().getRoles(principal));
+                handler.handle(new Callback[] { principalCallback, 
groupCallback });
+
+                // If we're caching principals we no longer needgetPrincipal 
the
+                // username
+                // and password in the session, so remove them
+                if (isCache()) {
+                    session.removeNote(Constants.SESS_USERNAME_NOTE);
+                    session.removeNote(Constants.SESS_PASSWORD_NOTE);
+                }
+                if (restoreRequest(request, session)) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Proceed to restored request");
+                    }
+                    return AuthStatus.SUCCESS;
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Restore of original request failed");
+                    }
+                    response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                    return AuthStatus.FAILURE;
+                }
+            }
+
+            // Acquire references to objects we will need to evaluate
+            MessageBytes uriMB = MessageBytes.newInstance();
+            CharChunk uriCC = uriMB.getCharChunk();
+            uriCC.setLimit(-1);
+            String contextPath = request.getContextPath();
+            String requestURI = request.getDecodedRequestURI();
+
+            // Is this the action request from the login page?
+            boolean loginAction = requestURI.startsWith(contextPath)
+                    && requestURI.endsWith(Constants.FORM_ACTION);
+
+            LoginConfig config = context.getLoginConfig();
+
+            // No -- Save this request and redirect to the form login page
+            if (!loginAction) {
+                session = request.getSessionInternal(true);
+                if (log.isDebugEnabled()) {
+                    log.debug("Save request in session '" + 
session.getIdInternal() + "'");
+                }
+                try {
+                    saveRequest(request, session);
+                } catch (IOException ioe) {
+                    log.debug("Request body too big to save during 
authentication");
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN,
+                            sm.getString("authenticator.requestBodyTooBig"));
+                    return AuthStatus.FAILURE;
+                }
+                forwardToLoginPage(request, response, config);
+                return AuthStatus.SEND_CONTINUE;
+            }
+
+            // Yes -- Acknowledge the request, validate the specified
+            // credentials
+            // and redirect to the error page if they are not correct
+            request.getResponse().sendAcknowledgement();
+            Realm realm = context.getRealm();
+            // TODO fix character encoding
+            // if (characterEncoding != null) {
+            // request.setCharacterEncoding(characterEncoding);
+            // }
+            String username = request.getParameter(Constants.FORM_USERNAME);
+            String password = request.getParameter(Constants.FORM_PASSWORD);
+            if (log.isDebugEnabled()) {
+                log.debug("Authenticating username '" + username + "'");
+            }
+            principal = realm.authenticate(username, password);
+            if (principal == null) {
+                forwardToErrorPage(request, response, config);
+                return AuthStatus.FAILURE;
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("Authentication of '" + username + "' was 
successful");
+            }
+
+            if (session == null) {
+                session = request.getSessionInternal(false);
+            }
+            if (session == null) {
+                // if (containerLog.isDebugEnabled()) {
+                // containerLog.debug
+                // ("User took so long to log on the session expired");
+                // }
+                if (landingPage == null) {
+                    response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT,
+                            sm.getString("authenticator.sessionExpired"));
+                } else {
+                    // Make the authenticator think the user originally
+                    // requested
+                    // the landing page
+                    String uri = request.getContextPath() + landingPage;
+                    SavedRequest saved = new SavedRequest();
+                    saved.setMethod("GET");
+                    saved.setRequestURI(uri);
+                    saved.setDecodedRequestURI(uri);
+                    
request.getSessionInternal(true).setNote(Constants.FORM_REQUEST_NOTE, saved);
+                    response.sendRedirect(response.encodeRedirectURL(uri));
+                }
+                return AuthStatus.FAILURE;
+            }
+
+            // Save the authenticated Principal in our session
+            session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
+
+            // Save the username and password as well
+            session.setNote(Constants.SESS_USERNAME_NOTE, username);
+            session.setNote(Constants.SESS_PASSWORD_NOTE, password);
+
+            // Redirect the user to the original request URI (which will cause
+            // the original request to be restored)
+            requestURI = savedRequestURL(session);
+            if (log.isDebugEnabled()) {
+                log.debug("Redirecting to original '" + requestURI + "'");
+            }
+            if (requestURI == null) {
+                if (landingPage == null) {
+                    response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+                            sm.getString("authenticator.formlogin"));
+                } else {
+                    // Make the authenticator think the user originally
+                    // requested
+                    // the landing page
+                    String uri = request.getContextPath() + landingPage;
+                    SavedRequest saved = new SavedRequest();
+                    saved.setMethod("GET");
+                    saved.setRequestURI(uri);
+                    saved.setDecodedRequestURI(uri);
+                    session.setNote(Constants.FORM_REQUEST_NOTE, saved);
+                    response.sendRedirect(response.encodeRedirectURL(uri));
+                }
+            } else {
+                // Until the Servlet API allows specifying the type of redirect
+                // to
+                // use.
+                Response internalResponse = request.getResponse();
+                String location = response.encodeRedirectURL(requestURI);
+                if ("HTTP/1.1".equals(request.getProtocol())) {
+                    internalResponse.sendRedirect(location, 
HttpServletResponse.SC_SEE_OTHER);
+                } else {
+                    internalResponse.sendRedirect(location, 
HttpServletResponse.SC_FOUND);
+                }
+            }
+            return AuthStatus.FAILURE;
+        } catch (Exception e) {
+            throw new AuthException(e.getMessage());
+        }
+
+    }
+
+
+    private boolean isCache() {
+        return true;
     }
 
 
@@ -82,4 +320,319 @@ public class FormAuthModule extends Tomc
     public Class<?>[] getSupportedMessageTypes() {
         return supportedMessageTypes;
     }
+
+
+    private GenericPrincipal getPrincipal(PasswordValidationCallback 
passwordCallback) {
+        Iterator<Object> credentials = 
passwordCallback.getSubject().getPrivateCredentials()
+                .iterator();
+        return (GenericPrincipal) credentials.next();
+    }
+
+
+    /**
+     * Called to forward to the login page
+     *
+     * @param request Request we are processing
+     * @param response Response we are populating
+     * @param config Login configuration describing how authentication should 
be
+     *            performed
+     * @throws IOException If the forward to the login page fails and the call
+     *             to {@link HttpServletResponse#sendError(int, String)} throws
+     *             an {@link IOException}
+     */
+    protected void forwardToLoginPage(Request request, HttpServletResponse 
response,
+            LoginConfig config) throws IOException {
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("formAuthenticator.forwardLogin", 
request.getRequestURI(),
+                    request.getMethod(), config.getLoginPage(), 
context.getName()));
+        }
+
+        String loginPage = config.getLoginPage();
+        if (loginPage == null || loginPage.length() == 0) {
+            String msg = sm.getString("formAuthenticator.noLoginPage", 
context.getName());
+            log.warn(msg);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
msg);
+            return;
+        }
+
+        if (getChangeSessionIdOnAuthentication()) {
+            Session session = request.getSessionInternal(false);
+            if (session != null) {
+                Manager manager = request.getContext().getManager();
+                manager.changeSessionId(session);
+                request.changeSessionId(session.getId());
+            }
+        }
+
+        // Always use GET for the login page, regardless of the method used
+        String oldMethod = request.getMethod();
+        request.getCoyoteRequest().method().setString("GET");
+
+        RequestDispatcher disp = 
context.getServletContext().getRequestDispatcher(loginPage);
+        try {
+            if (context.fireRequestInitEvent(request)) {
+                disp.forward(request.getRequest(), response);
+                context.fireRequestDestroyEvent(request);
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            String msg = sm.getString("formAuthenticator.forwardLoginFail");
+            log.warn(msg, t);
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
msg);
+        } finally {
+            // Restore original method so that it is written into access log
+            request.getCoyoteRequest().method().setString(oldMethod);
+        }
+    }
+
+
+    private boolean getChangeSessionIdOnAuthentication() {
+        return true;        // FIXME
+    }
+
+
+    /**
+     * Called to forward to the error page
+     *
+     * @param request Request we are processing
+     * @param response Response we are populating
+     * @param config Login configuration describing how authentication should 
be
+     *            performed
+     * @throws IOException If the forward to the error page fails and the call
+     *             to {@link HttpServletResponse#sendError(int, String)} throws
+     *             an {@link IOException}
+     */
+    protected void forwardToErrorPage(Request request, HttpServletResponse 
response,
+            LoginConfig config) throws IOException {
+
+        String errorPage = config.getErrorPage();
+        if (errorPage == null || errorPage.length() == 0) {
+            String msg = sm.getString("formAuthenticator.noErrorPage", 
context.getName());
+            log.warn(msg);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
msg);
+            return;
+        }
+
+        RequestDispatcher disp = 
context.getServletContext().getRequestDispatcher(
+                config.getErrorPage());
+        try {
+            if (context.fireRequestInitEvent(request)) {
+                disp.forward(request.getRequest(), response);
+                context.fireRequestDestroyEvent(request);
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            String msg = sm.getString("formAuthenticator.forwardErrorFail");
+            log.warn(msg, t);
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
msg);
+        }
+    }
+
+
+    /**
+     * Does this request match the saved one (so that it must be the redirect 
we
+     * signaled after successful authentication?
+     *
+     * @param request The request to be verified
+     */
+    protected boolean matchRequest(Request request) {
+        // Has a session been created?
+        Session session = request.getSessionInternal(false);
+        if (session == null) {
+            return false;
+        }
+
+        // Is there a saved request?
+        SavedRequest sreq = (SavedRequest) 
session.getNote(Constants.FORM_REQUEST_NOTE);
+        if (sreq == null) {
+            return false;
+        }
+
+        // Is there a saved principal?
+        if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null) {
+            return false;
+        }
+
+        // Does the request URI match?
+        String decodedRequestURI = request.getDecodedRequestURI();
+        if (decodedRequestURI == null) {
+            return false;
+        }
+        return decodedRequestURI.equals(sreq.getDecodedRequestURI());
+    }
+
+
+    /**
+     * Restore the original request from information stored in our session. If
+     * the original request is no longer present (because the session timed
+     * out), return <code>false</code>; otherwise, return <code>true</code>.
+     *
+     * @param request The request to be restored
+     * @param session The session containing the saved information
+     */
+    protected boolean restoreRequest(Request request, Session session) throws 
IOException {
+
+        // Retrieve and remove the SavedRequest object from our session
+        SavedRequest saved = (SavedRequest) 
session.getNote(Constants.FORM_REQUEST_NOTE);
+        session.removeNote(Constants.FORM_REQUEST_NOTE);
+        session.removeNote(Constants.FORM_PRINCIPAL_NOTE);
+        if (saved == null) {
+            return false;
+        }
+
+        // Swallow any request body since we will be replacing it
+        // Need to do this before headers are restored as AJP connector uses
+        // content length header to determine how much data needs to be read 
for
+        // request body
+        byte[] buffer = new byte[4096];
+        InputStream is = request.createInputStream();
+        while (is.read(buffer) >= 0) {
+            // Ignore request body
+        }
+
+        // Modify our current request to reflect the original one
+        request.clearCookies();
+        Iterator<Cookie> cookies = saved.getCookies();
+        while (cookies.hasNext()) {
+            request.addCookie(cookies.next());
+        }
+
+        String method = saved.getMethod();
+        MimeHeaders rmh = request.getCoyoteRequest().getMimeHeaders();
+        rmh.recycle();
+        boolean cachable = "GET".equalsIgnoreCase(method) || 
"HEAD".equalsIgnoreCase(method);
+        Iterator<String> names = saved.getHeaderNames();
+        while (names.hasNext()) {
+            String name = names.next();
+            // The browser isn't expecting this conditional response now.
+            // Assuming that it can quietly recover from an unexpected 412.
+            // BZ 43687
+            if (!("If-Modified-Since".equalsIgnoreCase(name) || (cachable && 
"If-None-Match"
+                    .equalsIgnoreCase(name)))) {
+                Iterator<String> values = saved.getHeaderValues(name);
+                while (values.hasNext()) {
+                    rmh.addValue(name).setString(values.next());
+                }
+            }
+        }
+
+        request.clearLocales();
+        Iterator<Locale> locales = saved.getLocales();
+        while (locales.hasNext()) {
+            request.addLocale(locales.next());
+        }
+
+        request.getCoyoteRequest().getParameters().recycle();
+        request.getCoyoteRequest().getParameters()
+                
.setQueryStringEncoding(request.getConnector().getURIEncoding());
+
+        ByteChunk body = saved.getBody();
+
+        if (body != null) {
+            request.getCoyoteRequest().action(ActionCode.REQ_SET_BODY_REPLAY, 
body);
+
+            // Set content type
+            MessageBytes contentType = MessageBytes.newInstance();
+
+            // If no content type specified, use default for POST
+            String savedContentType = saved.getContentType();
+            if (savedContentType == null && "POST".equalsIgnoreCase(method)) {
+                savedContentType = "application/x-www-form-urlencoded";
+            }
+
+            contentType.setString(savedContentType);
+            request.getCoyoteRequest().setContentType(contentType);
+        }
+
+        request.getCoyoteRequest().method().setString(method);
+
+        return true;
+    }
+
+
+    /**
+     * Save the original request information into our session.
+     *
+     * @param request The request to be saved
+     * @param session The session to contain the saved information
+     * @throws IOException
+     */
+    protected void saveRequest(Request request, Session session) throws 
IOException {
+
+        // Create and populate a SavedRequest object for this request
+        SavedRequest saved = new SavedRequest();
+        Cookie cookies[] = request.getCookies();
+        if (cookies != null) {
+            for (int i = 0; i < cookies.length; i++) {
+                saved.addCookie(cookies[i]);
+            }
+        }
+        Enumeration<String> names = request.getHeaderNames();
+        while (names.hasMoreElements()) {
+            String name = names.nextElement();
+            Enumeration<String> values = request.getHeaders(name);
+            while (values.hasMoreElements()) {
+                String value = values.nextElement();
+                saved.addHeader(name, value);
+            }
+        }
+        Enumeration<Locale> locales = request.getLocales();
+        while (locales.hasMoreElements()) {
+            Locale locale = locales.nextElement();
+            saved.addLocale(locale);
+        }
+
+        // May need to acknowledge a 100-continue expectation
+        request.getResponse().sendAcknowledgement();
+
+        ByteChunk body = new ByteChunk();
+        body.setLimit(request.getConnector().getMaxSavePostSize());
+
+        byte[] buffer = new byte[4096];
+        int bytesRead;
+        InputStream is = request.getInputStream();
+
+        while ((bytesRead = is.read(buffer)) >= 0) {
+            body.append(buffer, 0, bytesRead);
+        }
+
+        // Only save the request body if there is something to save
+        if (body.getLength() > 0) {
+            saved.setContentType(request.getContentType());
+            saved.setBody(body);
+        }
+
+        saved.setMethod(request.getMethod());
+        saved.setQueryString(request.getQueryString());
+        saved.setRequestURI(request.getRequestURI());
+        saved.setDecodedRequestURI(request.getDecodedRequestURI());
+
+        // Stash the SavedRequest in our session for later use
+        session.setNote(Constants.FORM_REQUEST_NOTE, saved);
+    }
+
+
+    /**
+     * Return the request URI (with the corresponding query string, if any) 
from
+     * the saved request so that we can redirect to it.
+     *
+     * @param session Our current session
+     */
+    protected String savedRequestURL(Session session) {
+
+        SavedRequest saved = (SavedRequest) 
session.getNote(Constants.FORM_REQUEST_NOTE);
+        if (saved == null) {
+            return (null);
+        }
+        StringBuilder sb = new StringBuilder(saved.getRequestURI());
+        if (saved.getQueryString() != null) {
+            sb.append('?');
+            sb.append(saved.getQueryString());
+        }
+        return (sb.toString());
+
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to