incubator-sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cziege...@apache.org
Subject svn commit: r924747 - in /sling/trunk/bundles/commons/auth: ./ src/main/java/org/apache/sling/commons/auth/impl/
Date Thu, 18 Mar 2010 12:37:32 GMT
Author: cziegeler
Date: Thu Mar 18 12:37:32 2010
New Revision: 924747

URL: http://svn.apache.org/viewvc?rev=924747&view=rev
Log:
SLING-1383 : Provide out-of-the-box HTTP Basic authentication handler in the Commons Auth
bundle

Added:
    sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java
  (with props)
Modified:
    sling/trunk/bundles/commons/auth/pom.xml
    sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/LoginServlet.java
    sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/SlingAuthenticator.java

Modified: sling/trunk/bundles/commons/auth/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/auth/pom.xml?rev=924747&r1=924746&r2=924747&view=diff
==============================================================================
--- sling/trunk/bundles/commons/auth/pom.xml (original)
+++ sling/trunk/bundles/commons/auth/pom.xml Thu Mar 18 12:37:32 2010
@@ -67,7 +67,15 @@
                             org.apache.sling.commons.auth.impl.*
                         </Private-Package>
                         <Embed-Dependency>
-                            org.apache.sling.commons.osgi;inline=org/apache/sling/commons/osgi/OsgiUtil.class
+                            org.apache.sling.commons.osgi;inline=org/apache/sling/commons/osgi/OsgiUtil.class,
+                            commons-codec;inline="org/apache/commons/codec/binary/Base64.*
+                                |org/apache/commons/codec/binary/StringUtils*
+                                |org/apache/commons/codec/BinaryEncoder*
+                                |org/apache/commons/codec/BinaryDecoder*
+                                |org/apache/commons/codec/Encoder*
+                                |org/apache/commons/codec/Decoder*
+                                |org/apache/commons/codec/EncoderException*
+                                |org/apache/commons/codec/DecoderException*"
                         </Embed-Dependency>
                     </instructions>
                 </configuration>
@@ -113,7 +121,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
-            <version>2.0.2-incubator</version>
+            <version>2.0.4-incubator</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -133,6 +141,13 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
         
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.4</version>
+            <scope>provided</scope>
+        </dependency>
+        
         <!-- Test Dependencies -->
         <dependency>
             <groupId>junit</groupId>

Added: sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java?rev=924747&view=auto
==============================================================================
--- sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java
(added)
+++ sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java
Thu Mar 18 12:37:32 2010
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.auth.impl;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.sling.commons.auth.spi.AuthenticationHandler;
+import org.apache.sling.commons.auth.spi.AuthenticationInfo;
+import org.apache.sling.commons.auth.spi.DefaultAuthenticationFeedbackHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>HttpBasicAuthenticationHandler</code> class supports plain old HTTP
+ * Basic authentication. While {@link #extractCredentials(HttpServletRequest)}
+ * always accesse the header if called and if present, the
+ * {@link #requestCredentials(HttpServletRequest, HttpServletResponse)} and
+ * {@link #dropCredentials(HttpServletRequest, HttpServletResponse)} methods
+ * must be explicitly enabled to send back a 401/UNAUTHORIZED reply to force the
+ * client into HTTP Basic authentication.
+ * <p>
+ * Being able to just extract credentials but not actively request them provides
+ * an easy way for tools (like cURL) or libraries (like Apache HttpCLient) to
+ * preemptively authenticate with HTTP Basic authentication.
+ */
+public class HttpBasicAuthenticationHandler extends
+        DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
+
+    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+    private static final String HEADER_AUTHORIZATION = "Authorization";
+
+    private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
+
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final String realm;
+
+    private final boolean forceRequestCredentials;
+
+    private final boolean forceDropCredentials;
+
+    public HttpBasicAuthenticationHandler(final String realm,
+            final boolean forceRequestCredentials,
+            final boolean forceDropCredentials) {
+        this.realm = realm;
+        this.forceRequestCredentials = forceRequestCredentials;
+        this.forceDropCredentials = forceDropCredentials;
+    }
+
+    // ----------- AuthenticationHandler interface ----------------------------
+
+    /**
+     * Extracts credential data from the request if at all contained. This check
+     * is only based on the original request object, no URI translation has
+     * taken place yet.
+     * <p>
+     * The method returns any of the following values :
+     * <table>
+     * <tr>
+     * <th>value
+     * <th>description
+     * </tr>
+     * <tr>
+     * <td><code>null</code>
+     * <td>no user details were contained in the request
+     * </tr>
+     * <tr>
+     * <td>{@link AuthenticationInfo#DOING_AUTH}
+     * <td>the handler is in an ongoing authentication exchange with the client.
+     * The request handling is terminated.
+     * <tr>
+     * <tr>
+     * <td>valid credentials
+     * <td>The user sent credentials.
+     * </tr>
+     * </table>
+     * <p>
+     * The method must not request credential information from the client, if
+     * they are not found in the request.
+     * <p>
+     * Note : The implementation should pay special attention to the fact, that
+     * the request may be for an included servlet, in which case the values for
+     * some URI specific values are contained in javax.servlet.include.* request
+     * attributes.
+     *
+     * @param request The request object containing the information for the
+     *            authentication.
+     * @param response The response object which may be used to send the
+     *            information on the request failure to the user.
+     * @return A valid Credentials instance identifying the request user,
+     *         DOING_AUTH if the handler is in an authentication trasaction with
+     *         the client or null if the request does not contain authentication
+     *         information. In case of DOING_AUTH, the method must have sent a
+     *         response indicating that fact to the client.
+     */
+    public AuthenticationInfo extractCredentials(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        // extract credentials and return
+        AuthenticationInfo info = this.extractCredentials(request);
+        if (info != null) {
+            return info;
+        }
+
+        // no credentials, check whether the client wants to login
+        if (forceAuthentication(request, response)) {
+            return AuthenticationInfo.DOING_AUTH;
+        }
+
+        // no special header, so we will not authenticate here
+        return null;
+    }
+
+    /**
+     * Sends back the form to log into the system.
+     *
+     * @param request The request object
+     * @param response The response object to which to send the request
+     * @return <code>true</code> is always returned by this handler
+     */
+    public boolean requestCredentials(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        if (forceRequestCredentials || isLoginRequested(request)) {
+            sendUnauthorized(response);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Sends a 401/UNATUHORIZED response if the request has an Authorization
+     * header and if this handler is configured to actually send this response
+     * in response to a request to drop the credentials.
+     * <p>
+     * Note, that sending a 401/UNAUTHORIZED response is generally the only save
+     * means to remove HTTP Basic credentials from a browser's cache. Yet, the
+     * nasty side-effect is that the browser's login form is displayed as a
+     * reaction to the 401/UNAUTHORIZED response.
+     */
+    public void dropCredentials(HttpServletRequest request,
+            HttpServletResponse response) {
+        if (request.getHeader(HEADER_AUTHORIZATION) != null
+            && forceDropCredentials) {
+            sendUnauthorized(response);
+        }
+    }
+
+    /**
+     * Returns true if the {@link #REQUEST_LOGIN_PARAMETER} parameter is set to
+     * the value <code>Basic</code> thus requesting plain basic authentication.
+     */
+    private boolean isLoginRequested(HttpServletRequest request) {
+        return HttpServletRequest.BASIC_AUTH.equals(request.getParameter(REQUEST_LOGIN_PARAMETER));
+    }
+
+    /**
+     * If the {@link #REQUEST_LOGIN_PARAMETER} parameter is set this method
+     * sends status <code>401</code> (Unauthorized) with a
+     * <code>WWW-Authenticate</code> requesting standard HTTP header
+     * authentication with the <code>Basic</code> scheme and the configured
+     * realm name. If the response is already committed, an error message is
+     * logged but the 401 status is not sent.
+     * <p>
+     * <code>false</code> is returned if the request parameter is not set, if
+     * the response is already committed or if an error occurred sending the
+     * status response. The latter two situations are logged as errors.
+     *
+     * @param request The request object
+     * @param response The response object to which to send the request
+     * @return <code>true</code> if the 401/UNAUTHORIZED method has successfully
+     *         been sent.
+     */
+    private boolean forceAuthentication(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        // presume 401/UNAUTHORIZED has not been sent
+        boolean authenticationForced = false;
+
+        if (isLoginRequested(request)) {
+
+            authenticationForced = sendUnauthorized(response);
+
+        } else {
+
+            log.debug(
+                "forceAuthentication: Not forcing authentication because request parameter
{} is not set",
+                REQUEST_LOGIN_PARAMETER);
+
+        }
+
+        // true if 401/UNAUTHORIZED has been sent, false otherwise
+        return authenticationForced;
+    }
+
+    /**
+     * Sends status <code>401</code> (Unauthorized) with a
+     * <code>WWW-Authenticate</code> requesting standard HTTP header
+     * authentication with the <code>Basic</code> scheme and the configured
+     * realm name.
+     *
+     * @param response The response object to which to send the request
+     * @return <code>true</code> if the 401/UNAUTHORIZED method has successfully
+     *         been sent.
+     */
+    private boolean sendUnauthorized(HttpServletResponse response) {
+        response.setHeader(HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC
+            + " realm=\"" + this.realm + "\"");
+
+        try {
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            response.flushBuffer();
+            return true;
+        } catch (IOException ioe) {
+            log.error("sendUnauthorized: Failed requesting authentication", ioe);
+        }
+
+        return false;
+    }
+
+    // ---------- internal -----------------------------------------------------
+
+    /**
+     * Extract the Base64 authentication string from the request
+     */
+    protected AuthenticationInfo extractCredentials(HttpServletRequest request) {
+
+        // Return immediately if the header is missing
+        String authHeader = request.getHeader(HEADER_AUTHORIZATION);
+        if (authHeader == null || authHeader.length() == 0) {
+            return null;
+        }
+
+        // Get the authType (Basic, Digest) and authInfo (user/password) from
+        // the header
+        authHeader = authHeader.trim();
+        int blank = authHeader.indexOf(' ');
+        if (blank <= 0) {
+            return null;
+        }
+        String authType = authHeader.substring(0, blank);
+        String authInfo = authHeader.substring(blank).trim();
+
+        // Check whether authorization type matches
+        if (!authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) {
+            return null;
+        }
+
+        // Base64 decode and split on colon
+
+        // we cannot use default base64, since we need iso encoding
+        // (nb: ISO-8859-1 is required as per API spec to be available)
+        String decoded;
+        try {
+            byte[] encoded = authInfo.getBytes("ISO-8859-1");
+            byte[] bytes = Base64.decodeBase64(encoded);
+            decoded = new String(bytes, "ISO-8859-1");
+        } catch (UnsupportedEncodingException uee) {
+            // unexpected
+            log.error(
+                "extractAuthentication: Cannot en/decode authentication info",
+                uee);
+            return null;
+        }
+
+        final int colIdx = decoded.indexOf(':');
+        final String userId;
+        final char[] password;
+        if (colIdx < 0) {
+            userId = decoded;
+            password = new char[0];
+        } else {
+            userId = decoded.substring(0, colIdx);
+            password = decoded.substring(colIdx + 1).toCharArray();
+        }
+
+        return new AuthenticationInfo(HttpServletRequest.BASIC_AUTH, userId,
+            password);
+    }
+}
\ No newline at end of file

Propchange: sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/HttpBasicAuthenticationHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/LoginServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/LoginServlet.java?rev=924747&r1=924746&r2=924747&view=diff
==============================================================================
--- sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/LoginServlet.java
(original)
+++ sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/LoginServlet.java
Thu Mar 18 12:37:32 2010
@@ -88,7 +88,6 @@ public class LoginServlet extends SlingA
                 // set the login resource to select the authenticator
                 request.setAttribute(Authenticator.LOGIN_RESOURCE,
                     (resourcePath != null) ? resourcePath : "/");
-
                 authenticator.login(request, response);
                 return;
 

Modified: sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/SlingAuthenticator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/SlingAuthenticator.java?rev=924747&r1=924746&r2=924747&view=diff
==============================================================================
--- sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/SlingAuthenticator.java
(original)
+++ sling/trunk/bundles/commons/auth/src/main/java/org/apache/sling/commons/auth/impl/SlingAuthenticator.java
Thu Mar 18 12:37:32 2010
@@ -126,6 +126,19 @@ public class SlingAuthenticator implemen
     private static final boolean DEFAULT_ANONYMOUS_ALLOWED = true;
 
     /**
+     * The name of the configuration property used to set the Realm of the
+     * built-in HTTP Basic authentication handler.
+     *
+     * @scr.property valueRef="DEFAULT_REALM"
+     */
+    public static final String PAR_REALM_NAME = "auth.http.realm";
+
+    /**
+     * The default realm for the built-in HTTP Basic authentication handler.
+     */
+    private static final String DEFAULT_REALM = "Sling (Development)";
+
+    /**
      * The name of the session attribute which is set if the session created by
      * the {@link #handleSecurity(HttpServletRequest, HttpServletResponse)}
      * method is an impersonated session. The value of this attribute is the
@@ -178,6 +191,9 @@ public class SlingAuthenticator implemen
     /** Cache control flag */
     private boolean cacheControl;
 
+    /** HTTP Basic authentication handler */
+    private HttpBasicAuthenticationHandler httpBasicHandler;
+
     /** Web Console Plugin service registration */
     private ServiceRegistration webConsolePlugin;
 
@@ -286,6 +302,14 @@ public class SlingAuthenticator implemen
         if (serviceListener != null) {
             serviceListener.registerServices();
         }
+
+        // register as a service !
+        final String realm = OsgiUtil.toString(properties.get(PAR_REALM_NAME),
+            DEFAULT_REALM);
+        final boolean forceRequestCredentials = true;
+        final boolean forceDropCredentials = false;
+        httpBasicHandler = new HttpBasicAuthenticationHandler(realm,
+            forceRequestCredentials, forceDropCredentials);
     }
 
     @SuppressWarnings("unused")
@@ -359,6 +383,8 @@ public class SlingAuthenticator implemen
         } else if (authInfo == AuthenticationInfo.FAIL_AUTH) {
 
             log.debug("handleSecurity: Credentials present but not valid, request authentication
again");
+            // FIXME: ensure resource is not set !!!
+            request.setAttribute(LOGIN_RESOURCE, request.getRequestURI());
             doLogin(request, response);
             return false;
 
@@ -427,6 +453,11 @@ public class SlingAuthenticator implemen
             }
         }
 
+        // fall back to HTTP Basic handler (if not done already)
+        if (!done) {
+            done = httpBasicHandler.requestCredentials(request, response);
+        }
+
         // no handler could send an authentication request, throw
         if (!done) {
             log.info("login: No handler for request ({} handlers available)",
@@ -465,6 +496,8 @@ public class SlingAuthenticator implemen
             }
         }
 
+        httpBasicHandler.dropCredentials(request, response);
+
         redirectAfterLogout(request, response);
     }
 
@@ -540,6 +573,14 @@ public class SlingAuthenticator implemen
             }
         }
 
+        // check whether the HTTP Basic handler can extract the header
+        final AuthenticationInfo authInfo = httpBasicHandler.extractCredentials(
+            request, response);
+        if (authInfo != null) {
+            authInfo.put(AUTH_INFO_PROP_FEEDBACK_HANDLER, httpBasicHandler);
+            return authInfo;
+        }
+
         // no handler found for the request ....
         log.debug("getAuthenticationInfo: no handler could extract credentials");
         return null;



Mime
View raw message