You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2010/09/24 21:50:13 UTC

svn commit: r1001053 - in /sling/trunk/bundles/auth: core/src/main/java/org/apache/sling/auth/core/impl/ core/src/main/java/org/apache/sling/auth/core/spi/ form/src/main/java/org/apache/sling/auth/form/impl/

Author: fmeschbe
Date: Fri Sep 24 19:50:12 2010
New Revision: 1001053

URL: http://svn.apache.org/viewvc?rev=1001053&view=rev
Log:
SLING-1428 Implement generalized support for validating credentials supplied by a request using the j_validate request parameter.

Modified:
    sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
    sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
    sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandler.java
    sling/trunk/bundles/auth/form/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java

Modified: sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java?rev=1001053&r1=1001052&r2=1001053&view=diff
==============================================================================
--- sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java (original)
+++ sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java Fri Sep 24 19:50:12 2010
@@ -224,7 +224,7 @@ class HttpBasicAuthenticationHandler ext
      * @return <code>true</code> if the 401/UNAUTHORIZED method has successfully
      *         been sent.
      */
-    private boolean sendUnauthorized(HttpServletResponse response) {
+    boolean sendUnauthorized(HttpServletResponse response) {
 
         if (response.isCommitted()) {
 

Modified: sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java?rev=1001053&r1=1001052&r2=1001053&view=diff
==============================================================================
--- sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java (original)
+++ sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java Fri Sep 24 19:50:12 2010
@@ -405,7 +405,7 @@ public class SlingAuthenticator implemen
             if (authInfo != null) {
                 handleLoginFailure(request, response, authInfo.getUser(), e);
             } else {
-                handleLoginFailure(request, response, "<null>", e);
+            handleLoginFailure(request, response, "<null>", e);
             }
             return false;
         }
@@ -695,9 +695,17 @@ public class SlingAuthenticator implemen
 
             }
 
+            // client requested validation, which succeeds, thus send
+            // success response and close the resolver
+            if (AbstractAuthenticationHandler.isValidateRequest(request)) {
+                AbstractAuthenticationHandler.sendValid(response);
+                resolver.close();
+                return false;
+            }
+
             // no redirect desired, so continue processing by first setting
             // the request attributes and then returning true
-            setAttributes(resolver, authInfo.getAuthType(),  request);
+            setAttributes(resolver, authInfo.getAuthType(), request);
             return true;
 
         } catch (LoginException re) {
@@ -845,34 +853,95 @@ public class SlingAuthenticator implemen
     }
 
     /**
-     * Calls the {@link #login(HttpServletRequest, HttpServletResponse)} method
-     * catching declared exceptions of that method and cleanly handling and
-     * logging them. Particularly if no authentication handler is available to
-     * request credentials a 403/FORBIDDEN response is sent back to the client.
+     * Tries to request credentials from the client. The following mechanisms
+     * are implemented by this method:
+     * <ul>
+     * <li>If the request is a credentials validation request (see
+     * {@link AbstractAuthenticationHandler#isValidateRequest(HttpServletRequest)}
+     * ) a 403/FORBIDDEN response is sent back.</li>
+     * <li>If the request is not considered a
+     * {@link #isBrowserRequest(HttpServletRequest) browser request} and the
+     * HTTP Basic Authentication Handler is at least enabled for preemptive
+     * credentials processing, a 401/UNAUTHORIZED response is sent back. This
+     * helps implementing HTTP Basic authentication with WebDAV clients. If HTTP
+     * Basic Authentication is completely switched of a 403/FORBIDDEN response
+     * is sent back instead.</li>
+     * <li>If the request is considered an
+     * {@link #isAjaxRequest(HttpServletRequest) Ajax request} a 403/FORBIDDIN
+     * response is simply sent back because we assume an Ajax requestor cannot
+     * properly handle any request for credentials graciously.</li>
+     * <li>Otherwise the {@link #login(HttpServletRequest, HttpServletResponse)}
+     * method is called to try to find and call an authentication handler to
+     * request credentials from the client. If none is available or willing to
+     * request credentials, a 403/FORBIDDEN response is also sent back to the
+     * client.</li>
+     * </ul>
+     * <p>
+     * If a 403/FORBIDDEN response is sent back the {@link #X_REASON} header is
+     * set to a either the value of the
+     * {@link AuthenticationHandler#FAILURE_REASON} request attribute or to some
+     * generic description describing the reason. To actually send the response
+     * the
+     * {@link AbstractAuthenticationHandler#sendInvalid(HttpServletRequest, HttpServletResponse)}
+     * method is called.
+     * <p>
+     * This method is called in three situations:
+     * <ul>
+     * <li>If the request contains no credentials but anonymous login is not
+     * allowed</li>
+     * <li>If the request contains credentials but getting the Resource Resolver
+     * using the provided credentials fails</li>
+     * <li>If the selected authentication handler indicated any presented
+     * credentials are not valid</li>
+     * </ul>
+     *
+     * @param request The current request
+     * @param response The response to send the credentials request (or access
+     *            denial to)
+     * @see AbstractAuthenticationHandler#isValidateRequest(HttpServletRequest)
+     * @see #isBrowserRequest(HttpServletRequest)
+     * @see #isAjaxRequest(HttpServletRequest)
+     * @see AbstractAuthenticationHandler#sendInvalid(HttpServletRequest,
+     *      HttpServletResponse)
      */
     private void doLogin(HttpServletRequest request,
             HttpServletResponse response) {
 
+        if (!AbstractAuthenticationHandler.isValidateRequest(request)) {
             try {
 
                 login(request, response);
+                return;
 
             } catch (IllegalStateException ise) {
 
                 log.error("doLogin: Cannot login: Response already committed");
+                return;
 
             } catch (NoAuthenticationHandlerException nahe) {
 
+                /*
+                 * Don't set the failureReason for missing authentication
+                 * handlers to not disclose this setup information.
+                 */
+
                 log.error("doLogin: Cannot login: No AuthenticationHandler available to handle the request");
 
-        try {
-                response.sendError(HttpServletResponse.SC_FORBIDDEN,
-                    "Cannot login");
-        } catch (IOException ioe) {
-            log.error("doLogin: Failed sending 403 status", ioe);
+            }
         }
 
+        // if we are here, we cannot redirect to the login form because it is
+        // an XHR request or because there is no authentication handler willing
+        // request credentials from the client or because it is a failed
+        // credential validation
+
+        // ensure a failure reason
+        if (request.getAttribute(AuthenticationHandler.FAILURE_REASON) == null) {
+            request.setAttribute(AuthenticationHandler.FAILURE_REASON,
+                "Mandatory authentication is not possible");
         }
+
+        AbstractAuthenticationHandler.sendInvalid(request, response);
     }
 
     /**

Modified: sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandler.java?rev=1001053&r1=1001052&r2=1001053&view=diff
==============================================================================
--- sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandler.java (original)
+++ sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandler.java Fri Sep 24 19:50:12 2010
@@ -41,6 +41,31 @@ public abstract class AbstractAuthentica
         DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
 
     /**
+     * The name of the request parameter indicating that the submitted username
+     * and password should just be checked and a status code be set for success
+     * (200/OK) or failure (403/FORBIDDEN).
+     *
+     * @see #isValidateRequest(HttpServletRequest)
+     * @see #sendValid(HttpServletResponse)
+     * @see #sendInvalid(HttpServletResponse, Object)
+     * @since 1.0.2 (Bundle version 1.0.4)
+     */
+    private static final String PAR_J_VALIDATE = "j_validate";
+
+    /**
+     * The name of the request header set by the
+     * {@link #sendInvalid(HttpServletResponse, Object)} method if the provided
+     * credentials cannot be used for login.
+     * <p>
+     * This header may be inspected by clients for a reason why the request
+     * failed.
+     *
+     * @see #sendInvalid(HttpServletResponse, Object)
+     * @since 1.0.2 (Bundle version 1.0.4)
+     */
+    private static final String X_REASON = "X-Reason";
+
+    /**
      * Returns the value of the named request attribute or parameter as a string
      * as follows:
      * <ol>
@@ -231,4 +256,62 @@ public abstract class AbstractAuthentica
         // not set or not a non-empty string
         return null;
     }
+
+    /**
+     * Returns <code>true</code> if the the client just asks for validation of
+     * submitted username/password credentials.
+     * <p>
+     * This implementation returns <code>true</code> if the request parameter
+     * {@link #PAR_J_VALIDATE} is set to <code>true</code> (case-insensitve). If
+     * the request parameter is not set or to any value other than
+     * <code>true</code> this method returns <code>false</code>.
+     *
+     * @param request The request to provide the parameter to check
+     * @return <code>true</code> if the {@link #PAR_J_VALIDATE} parameter is set
+     *         to <code>true</code>.
+     * @since 1.0.2 (Bundle version 1.0.4)
+     */
+    public static boolean isValidateRequest(final HttpServletRequest request) {
+        return "true".equalsIgnoreCase(request.getParameter(PAR_J_VALIDATE));
+    }
+
+    /**
+     * Sends a 200/OK response to a credential validation request.
+     *
+     * @param response The response object
+     * @since 1.0.2 (Bundle version 1.0.4)
+     */
+    public static void sendValid(final HttpServletResponse response) {
+        try {
+            response.setStatus(HttpServletResponse.SC_OK);
+            response.flushBuffer();
+        } catch (IOException ioe) {
+            // TODO: log.error("Failed to send 200/OK response", ioe);
+        }
+    }
+
+    /**
+     * Sends a 403/FORBIDDEN response to a credential validation request
+     * providing the given reason as the value of the {@link #X_REASON} header.
+     *
+     * @param response The response object
+     * @param reason The reason to set on the header; not expected to be
+     *            <code>null</code>
+     * @since 1.0.2 (Bundle version 1.0.4)
+     */
+    public static void sendInvalid(final HttpServletRequest request,
+            final HttpServletResponse response) {
+        try {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+
+            Object reason = request.getAttribute(AuthenticationHandler.FAILURE_REASON);
+            if (reason != null) {
+                response.setHeader(X_REASON, reason.toString());
+            }
+
+            response.flushBuffer();
+        } catch (IOException ioe) {
+            // TODO: log.error("Failed to send 403/Forbidden response", ioe);
+        }
+    }
 }

Modified: sling/trunk/bundles/auth/form/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/auth/form/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java?rev=1001053&r1=1001052&r2=1001053&view=diff
==============================================================================
--- sling/trunk/bundles/auth/form/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java (original)
+++ sling/trunk/bundles/auth/form/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java Fri Sep 24 19:50:12 2010
@@ -235,13 +235,6 @@ public class FormAuthenticationHandler e
     private static final String PAR_J_PASSWORD = "j_password";
 
     /**
-     * The name of the form submission parameter indicating that the submitted
-     * username and password should just be checked and a status code be set for
-     * success (200/OK) or failure (403/FORBIDDEN).
-     */
-    private static final String PAR_J_VALIDATE = "j_validate";
-
-    /**
      * Key in the AuthenticationInfo map which contains the domain on which the
      * auth cookie should be set.
      */
@@ -252,18 +245,6 @@ public class FormAuthenticationHandler e
      */
     private static final long MINUTES = 60L * 1000L;
 
-    /**
-     * The name of the request header set by
-     * {@link #authenticationFailed(HttpServletRequest, HttpServletResponse, AuthenticationInfo)}
-     * if instead of requesting credentials from the client a 403/FORBIDDEN response is sent.
-     * <p>
-     * This header may be inspected by clients for a reason why the request
-     * failed.
-     *
-     * @see #authenticationFailed(HttpServletRequest, HttpServletResponse, AuthenticationInfo)
-     */
-    private static final String X_REASON = "X-Reason";
-
     /** default log */
     private final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -339,15 +320,11 @@ public class FormAuthenticationHandler e
                     // so that the invalid cookie isn't present on the authN
                     // operation.
                     authStorage.clear(request, response);
-                    if (this.loginAfterExpire) {
+                    if (this.loginAfterExpire || isValidateRequest(request)) {
                         // signal the requestCredentials method a previous login
                         // failure
                         request.setAttribute(FAILURE_REASON, FormReason.TIMEOUT);
                         info = AuthenticationInfo.FAIL_AUTH;
-                    } else if (isValidateRequest(request)) {
-                        // send 403 response and terminate the request
-                        sendInvalid(response, FormReason.TIMEOUT);
-                        info = AuthenticationInfo.DOING_AUTH;
                     }
                 }
             }
@@ -451,17 +428,8 @@ public class FormAuthenticationHandler e
         // clear authentication data from Cookie or Http Session
         authStorage.clear(request, response);
 
-        if (isValidateRequest(request)) {
-
-            // just validated the credentials to be invalid
-            sendInvalid(response, FormReason.INVALID_CREDENTIALS);
-
-        } else {
-
-            // signal the requestCredentials method a previous login failure
-            request.setAttribute(FAILURE_REASON, FormReason.INVALID_CREDENTIALS);
-
-        }
+        // signal the reason for login failure
+        request.setAttribute(FAILURE_REASON, FormReason.INVALID_CREDENTIALS);
     }
 
     /**
@@ -489,14 +457,7 @@ public class FormAuthenticationHandler e
         refreshAuthData(request, response, authInfo);
 
         final boolean result;
-        if (isValidateRequest(request)) {
-
-            sendValid(response);
-
-            // terminate request, all done
-            result = true;
-
-        } else if (DefaultAuthenticationFeedbackHandler.handleRedirect(
+        if (DefaultAuthenticationFeedbackHandler.handleRedirect(
             request, response)) {
 
             // terminate request, all done in the default handler
@@ -550,56 +511,6 @@ public class FormAuthenticationHandler e
     }
 
     /**
-     * Returns <code>true</code> if the the client just asks for validation of
-     * submitted username/password credentials.
-     * <p>
-     * This implementation returns <code>true</code> if the request parameter
-     * {@link #PAR_J_VALIDATE} is set to <code>true</code> (case-insensitve). If
-     * the request parameter is not set or to any value other than
-     * <code>true</code> this method returns <code>false</code>.
-     *
-     * @param request The request to provide the parameter to check
-     * @return <code>true</code> if the {@link #PAR_J_VALIDATE} parameter is set
-     *         to <code>true</code>.
-     */
-    private boolean isValidateRequest(final HttpServletRequest request) {
-        return "true".equalsIgnoreCase(request.getParameter(PAR_J_VALIDATE));
-    }
-
-    /**
-     * Sends a 200/OK response to a credential validation request.
-     *
-     * @param response The response object
-     */
-    private void sendValid(final HttpServletResponse response) {
-        try {
-            response.setStatus(200);
-            response.flushBuffer();
-        } catch (IOException ioe) {
-            log.error("Failed to send 200/OK response", ioe);
-        }
-    }
-
-    /**
-     * Sends a 403/FORBIDDEN response to a credential validation request
-     * providing the given reason as the value of the {@link #X_REASON} header.
-     *
-     * @param response The response object
-     * @param reason The reason to set on the header; not expected to be
-     *            <code>null</code>
-     */
-    private void sendInvalid(final HttpServletResponse response,
-            final FormReason reason) {
-        try {
-            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
-            response.setHeader(X_REASON, reason.toString());
-            response.flushBuffer();
-        } catch (IOException ioe) {
-            log.error("Failed to send 403/Forbidden response", ioe);
-        }
-    }
-
-    /**
      * Ensures the authentication data is set (if not set yet) and the expiry
      * time is prolonged (if auth data already existed).
      * <p>
@@ -666,8 +577,11 @@ public class FormAuthenticationHandler e
                 // make sure, that the request is redirected after successful
                 // authentication, otherwise the request may be processed
                 // as a POST request to the j_security_check page (unless
-                // the j_validate parameter is set)
-                setLoginResourceAttribute(request, request.getContextPath());
+                // the j_validate parameter is set); but only if this is not
+                // a validation request
+                if (!isValidateRequest(request)) {
+                    setLoginResourceAttribute(request, request.getContextPath());
+                }
             }
         }