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 22:03:24 UTC

svn commit: r1001056 - in /sling/trunk/bundles/auth/core: SLING-1745.patch src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java

Author: fmeschbe
Date: Fri Sep 24 20:03:24 2010
New Revision: 1001056

URL: http://svn.apache.org/viewvc?rev=1001056&view=rev
Log:
SLING-1745 No redirect to login form for AJAX requests (403 instead)
SLING-1400 Use 401 if possible for failed authentication of non-browser requests (fallback to 403)

Added:
    sling/trunk/bundles/auth/core/SLING-1745.patch
Modified:
    sling/trunk/bundles/auth/core/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java

Added: sling/trunk/bundles/auth/core/SLING-1745.patch
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/auth/core/SLING-1745.patch?rev=1001056&view=auto
==============================================================================
--- sling/trunk/bundles/auth/core/SLING-1745.patch (added)
+++ sling/trunk/bundles/auth/core/SLING-1745.patch Fri Sep 24 20:03:24 2010
@@ -0,0 +1,171 @@
+Index: src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
+===================================================================
+--- src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java	(Revision 998797)
++++ src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java	(Arbeitskopie)
+@@ -224,7 +224,7 @@
+      * @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()) {
+ 
+Index: src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
+===================================================================
+--- src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java	(Revision 998936)
++++ src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java	(Arbeitskopie)
+@@ -47,6 +47,7 @@
+ import org.apache.sling.api.resource.LoginException;
+ import org.apache.sling.api.resource.ResourceResolver;
+ import org.apache.sling.api.resource.ResourceResolverFactory;
++import org.apache.sling.api.servlets.HttpConstants;
+ import org.apache.sling.auth.core.AuthenticationSupport;
+ import org.apache.sling.auth.core.impl.engine.EngineAuthenticationHandlerHolder;
+ import org.apache.sling.auth.core.spi.AbstractAuthenticationHandler;
+@@ -161,6 +162,32 @@
+      */
+     private static final String AUTH_INFO_PROP_FEEDBACK_HANDLER = "$$sling.auth.AuthenticationFeedbackHandler$$";
+ 
++    /**
++     * Request header commonly set by Ajax Frameworks to indicate the request is
++     * posted as an Ajax request. The value set is expected to be
++     * {@link #XML_HTTP_REQUEST}.
++     * <p>
++     * This header is known to be set by JQuery, ExtJS and Prototype. Other
++     * client-side JavaScript framework most probably also set it.
++     */
++    private static final String X_REQUESTED_WITH = "X-Requested-With";
++
++    /**
++     * The expected value of the {@link #X_REQUESTED_WITH} request header to
++     * identify a request as an Ajax request.
++     */
++    private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
++
++    /**
++     * The name of the request header set by the
++     * {@link #doLogin(HttpServletRequest, HttpServletResponse)} 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.
++     */
++    private static final String X_REASON = "X-Reason";
++
+     @Reference
+     private ResourceResolverFactory resourceResolverFactory;
+ 
+@@ -842,17 +869,72 @@
+     }
+ 
+     /**
+-     * 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 an <code>OPTIONS</code> 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 which send an
++     * OPTIONS request as a very first request to find out about the WebDAV
++     * capabilities of a supposed WebDAV server. If HTTP Basic Authentication is
++     * completely switched of a 403/FORBIDDEN response is sent back instead.</li>
++     * <li>If the request is an Ajax request a 403/FORBIDDIN response is simply
++     * sent back because we assume an Ajax requestor cannot properly handle any
++     * request for credentials graciously. To help any Ajax caller to identify
++     * the problem 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. Ajax requests are
++     * identified by the {@link #X_REQUESTED_WITH} request header set to
++     * {@link #XML_HTTP_REQUEST} which is done by most (if not all) modern Ajax
++     * frameworks like JQuery, ExtJS, Prototype, etc.
++     * <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>
++     * 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>
+      */
+     private void doLogin(HttpServletRequest request,
+             HttpServletResponse response) {
+ 
++        String failureReason = null;
++        if (HttpConstants.METHOD_OPTIONS.equals(request.getMethod())) {
++
++            // Presumably this is WebDAV. If HTTP Basic is fully enabled or
++            // enabled for preemptive credential support, we just request
++            // HTTP Basic credentials. Otherwise (HTTP Basic is fully
++            // switched off, 403 is sent back)
++            if (httpBasicHandler != null) {
++                httpBasicHandler.sendUnauthorized(response);
++                return;
++            }
++
++        } else if (XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH))) {
++
++            // this is an Ajax request, don't try to request credentials
++            // but fail the request with an optional reason response header
++            Object jReason = request.getAttribute(AuthenticationHandler.FAILURE_REASON);
++            if (jReason != null) {
++                failureReason = jReason.toString();
++            }
++
++        } else {
++
+             try {
+ 
+                 login(request, response);
++                return;
+ 
+             } catch (IllegalStateException ise) {
+ 
+@@ -860,17 +942,32 @@
+ 
+             } 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");
+ 
++            }
++        }
++
++        // 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.
++
++        if (failureReason == null) {
++            failureReason = "Mandatory authentication is not possible";
++        }
++
+         try {
+-                response.sendError(HttpServletResponse.SC_FORBIDDEN,
+-                    "Cannot login");
++            response.setHeader(X_REASON, failureReason);
++            response.sendError(HttpServletResponse.SC_FORBIDDEN, failureReason);
+         } catch (IOException ioe) {
+             log.error("doLogin: Failed sending 403 status", ioe);
+         }
+ 
+         }
+-    }
+ 
+     /**
+      * Sets the request attributes required by the OSGi HttpContext interface

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=1001056&r1=1001055&r2=1001056&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 20:03:24 2010
@@ -161,6 +161,34 @@ public class SlingAuthenticator implemen
      */
     private static final String AUTH_INFO_PROP_FEEDBACK_HANDLER = "$$sling.auth.AuthenticationFeedbackHandler$$";
 
+    /**
+     * Request header commonly set by Ajax Frameworks to indicate the request is
+     * posted as an Ajax request. The value set is expected to be
+     * {@link #XML_HTTP_REQUEST}.
+     * <p>
+     * This header is known to be set by JQuery, ExtJS and Prototype. Other
+     * client-side JavaScript framework most probably also set it.
+     *
+     * @see #isAjaxRequest(HttpServletRequest)
+     */
+    private static final String X_REQUESTED_WITH = "X-Requested-With";
+
+    /**
+     * The expected value of the {@link #X_REQUESTED_WITH} request header to
+     * identify a request as an Ajax request.
+     *
+     * @see #isAjaxRequest(HttpServletRequest)
+     */
+    private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
+
+    /**
+     * The name of the <code>Accept</code> header which must not exists to
+     * consider a request an initial WebDAV request.
+     *
+     * @see #isBrowserRequest(HttpServletRequest)
+     */
+    private static final String HEADER_ACCEPT = "Accept";
+
     @Reference
     private ResourceResolverFactory resourceResolverFactory;
 
@@ -908,24 +936,42 @@ public class SlingAuthenticator implemen
             HttpServletResponse response) {
 
         if (!AbstractAuthenticationHandler.isValidateRequest(request)) {
-            try {
+            if (isBrowserRequest(request)) {
 
-                login(request, response);
-                return;
+                if (!isAjaxRequest(request)) {
+                    try {
 
-            } catch (IllegalStateException ise) {
+                        login(request, response);
+                        return;
 
-                log.error("doLogin: Cannot login: Response already committed");
-                return;
+                    } catch (IllegalStateException ise) {
 
-            } catch (NoAuthenticationHandlerException nahe) {
+                        log.error("doLogin: Cannot login: Response already committed");
+                        return;
 
-                /*
-                 * Don't set the failureReason for missing authentication
-                 * handlers to not disclose this setup information.
-                 */
+                    } catch (NoAuthenticationHandlerException nahe) {
 
-                log.error("doLogin: Cannot login: No AuthenticationHandler available to handle the request");
+                        /*
+                         * 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");
+
+                    }
+                }
+
+            } else {
+
+                // Presumably this is WebDAV. If HTTP Basic is fully enabled or
+                // enabled for preemptive credential support, we just request
+                // HTTP Basic credentials. Otherwise (HTTP Basic is fully
+                // switched off, 403 is sent back)
+                if (httpBasicHandler != null) {
+                    httpBasicHandler.sendUnauthorized(response);
+                    return;
+                }
 
             }
         }
@@ -945,10 +991,37 @@ public class SlingAuthenticator implemen
     }
 
     /**
+     * Determine if this request comes from a web browser which accepts
+     * anything.
+     *
+     * @param request The current request
+     * @return <code>true</code> if the request can be considered a browser
+     *         request.
+     */
+    private boolean isBrowserRequest(final HttpServletRequest request) {
+        return request.getHeader(HEADER_ACCEPT) != null;
+    }
+
+    /**
+     * Returns <code>true</code> if the request is to be considered an AJAX
+     * request placed using the <code>XMLHttpRequest</code> browser host object.
+     * Currently a request is considered an AJAX request if the client sends the
+     * <i>X-Requested-With</i> request header set to <code>XMLHttpRequest</code>
+     * .
+     *
+     * @param request The current request
+     * @return <code>true</code> if the request can be considered an AJAX
+     *         request.
+     */
+    private boolean isAjaxRequest(final HttpServletRequest request) {
+        return XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH));
+    }
+
+    /**
      * Sets the request attributes required by the OSGi HttpContext interface
      * specification for the <code>handleSecurity</code> method. In addition the
-     * {@link SlingAuthenticator#REQUEST_ATTRIBUTE_RESOLVER} request attribute is
-     * set to the ResourceResolver.
+     * {@link SlingAuthenticator#REQUEST_ATTRIBUTE_RESOLVER} request attribute
+     * is set to the ResourceResolver.
      */
     private void setAttributes(final ResourceResolver resolver, final String authType,
             final HttpServletRequest request) {