SLING-1678 Add support to disable built-in HTTP Basic Authentication Handler
SLING-1679 Use Apache Felix SCR Annotations (instead of @scr JavaDoc tags)

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@988016 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java b/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
index 4b3767f..1708907 100644
--- a/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
+++ b/src/main/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandler.java
@@ -25,6 +25,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.sling.auth.core.spi.AbstractAuthenticationHandler;
 import org.apache.sling.auth.core.spi.AuthenticationHandler;
 import org.apache.sling.auth.core.spi.AuthenticationInfo;
 import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler;
@@ -44,7 +45,7 @@
  * an easy way for tools (like cURL) or libraries (like Apache HttpCLient) to
  * preemptively authenticate with HTTP Basic authentication.
  */
-public class HttpBasicAuthenticationHandler extends
+class HttpBasicAuthenticationHandler extends
         DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
 
     private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
@@ -56,47 +57,36 @@
     /** default log */
     private final Logger log = LoggerFactory.getLogger(getClass());
 
+    /** The realm to send back with the 401 response */
     private final String realm;
 
-    public HttpBasicAuthenticationHandler(final String realm) {
+    /**
+     * Whether this authentication handler is fully enabled and sends back 401
+     * responses from the
+     * {@link #requestCredentials(HttpServletRequest, HttpServletResponse)} and
+     * {@link #dropCredentials(HttpServletRequest, HttpServletResponse)}
+     * methods.
+     */
+    private final boolean fullSupport;
+
+    HttpBasicAuthenticationHandler(final String realm,
+            final boolean fullSupport) {
         this.realm = realm;
+        this.fullSupport = fullSupport;
     }
 
     // ----------- 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.
+     * Returns the credential present within in an HTTP Basic authentication
+     * header or <code>null</code> if no credentials are provided and the
+     * {@link AuthenticationHandler#REQUEST_LOGIN_PARAMETER} is neither set as a
+     * request parameter nor as a request attribute.
      * <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.
+     * If the {@link AuthenticationHandler#REQUEST_LOGIN_PARAMETER} is set as a
+     * request parameter or request attribute, a 401 response is sent to the
+     * client and the method returns {@link AuthenticationInfo#DOING_AUTH} to
+     * indicate that the handler has started its own credentials requesting.
      *
      * @param request The request object containing the information for the
      *            authentication.
@@ -105,8 +95,8 @@
      * @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.
+     *         information. In case of DOING_AUTH, the method has sent back a
+     *         401 requesting the client to provide credentials.
      */
     public AuthenticationInfo extractCredentials(HttpServletRequest request,
             HttpServletResponse response) {
@@ -129,23 +119,29 @@
     /**
      * Called by the SlingAuthenticator.login method in case no other
      * authentication handler was willing to request credentials from the
-     * client. In this case this HTTP Basic authentication handler will
-     * send back a {@link #sendUnauthorized(HttpServletResponse) 401 response}
-     * to request HTTP Basic authentication from the client.
+     * client. In this case this HTTP Basic authentication handler will send
+     * back a {@link #sendUnauthorized(HttpServletResponse) 401 response} to
+     * request HTTP Basic authentication from the client if full support has
+     * been configured in the
+     * {@link #HttpBasicAuthenticationHandler(String, boolean) constructor}
      *
      * @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
+     * @return <code>true</code> if full support is enabled and the 401 response
+     *         could be sent. If full support is not enabled <code>false</code>
+     *         is always returned.
      */
     public boolean requestCredentials(HttpServletRequest request,
             HttpServletResponse response) {
-        return sendUnauthorized(response);
+        return fullSupport ? sendUnauthorized(response) : 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.
+     * in response to a request to drop the credentials; that is if full support
+     * has been enabled in the
+     * {@link #HttpBasicAuthenticationHandler(String, boolean) constructor}.
      * <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
@@ -154,26 +150,29 @@
      */
     public void dropCredentials(HttpServletRequest request,
             HttpServletResponse response) {
-        if (request.getHeader(HEADER_AUTHORIZATION) != null) {
+        if (fullSupport && request.getHeader(HEADER_AUTHORIZATION) != null) {
             sendUnauthorized(response);
         }
     }
 
     /**
-     * Returns true if the {@link #REQUEST_LOGIN_PARAMETER} parameter is set.
+     * Returns true if the {@link #REQUEST_LOGIN_PARAMETER} parameter or request
+     * attribute is set to any non-<code>null</code> value.
      * <p>
-     * This method always returns <code>true</code> if the parameter is set
-     * regardless of its value because the client indicated it wanted to login
-     * but no authentication handler was willing to actually handle this
-     * request. So as a last fallback this handler request HTTP Basic
-     * Credentials.
+     * This method always returns <code>true</code> if the parameter or request
+     * attribute is set regardless of its value because the client indicated it
+     * wanted to login but no authentication handler was willing to actually
+     * handle this request. So as a last fallback this handler request HTTP
+     * Basic Credentials.
      *
+     * @param request The request object providing the parameter or attribute.
      * @return <code>true</code> if the
-     *         {@link AuthenticationHandler#REQUEST_LOGIN_PARAMETER} is set to
-     *         any value.
+     *         {@link AuthenticationHandler#REQUEST_LOGIN_PARAMETER} parameter
+     *         or attribute is set to any value.
      */
     private boolean isLoginRequested(HttpServletRequest request) {
-        return request.getParameter(REQUEST_LOGIN_PARAMETER) != null;
+        return AbstractAuthenticationHandler.getAttributeOrParameter(request,
+            REQUEST_LOGIN_PARAMETER, null) != null;
     }
 
     /**
diff --git a/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java b/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
index 12aa363..0a74fd0 100644
--- a/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
+++ b/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
@@ -32,6 +32,15 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyOption;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.felix.scr.annotations.Services;
 import org.apache.sling.api.auth.Authenticator;
 import org.apache.sling.api.auth.NoAuthenticationHandlerException;
 import org.apache.sling.api.resource.LoginException;
@@ -69,64 +78,41 @@
  * <p>
  * Currently this class does not support multiple handlers for any one request
  * URL.
- * <p>
- *
- * @scr.component name="org.apache.sling.engine.impl.auth.SlingAuthenticator"
- *                label="%auth.name" description="%auth.description"
- *                modified="modified" immediate="true"
- *
- * Register for three services
- * @scr.service interface="org.apache.sling.api.auth.Authenticator"
- * @scr.service interface="org.apache.sling.auth.core.AuthenticationSupport"
- * @scr.service interface="javax.servlet.ServletRequestListener"
- *
- * @scr.property name="service.vendor" value="The Apache Software Foundation"
  */
+@Component(name = "org.apache.sling.engine.impl.auth.SlingAuthenticator", label = "%auth.name", description = "%auth.description", metatype = true)
+@Services( { @Service(value = Authenticator.class),
+    @Service(value = AuthenticationSupport.class),
+    @Service(value = ServletRequestListener.class) })
+@Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation")
 public class SlingAuthenticator implements Authenticator,
         AuthenticationSupport, ServletRequestListener {
 
     /** default log */
     private final Logger log = LoggerFactory.getLogger(SlingAuthenticator.class);
 
-    /** @scr.property name="service.description" */
+    @Property(name = Constants.SERVICE_DESCRIPTION)
     static final String DESCRIPTION = "Apache Sling Request Authenticator";
 
-    /**
-     * @scr.property valueRef="DEFAULT_IMPERSONATION_COOKIE"
-     */
-    public static final String PAR_IMPERSONATION_COOKIE_NAME = "auth.sudo.cookie";
-
-    /**
-     * @scr.property valueRef="DEFAULT_IMPERSONATION_PARAMETER"
-     */
-    public static final String PAR_IMPERSONATION_PAR_NAME = "auth.sudo.parameter";
-
-    /**
-     * @scr.property valueRef="DEFAULT_ANONYMOUS_ALLOWED" type="Boolean"
-     */
-    public static final String PAR_ANONYMOUS_ALLOWED = "auth.annonymous";
-
-    /**
-     * @scr.property type="String" cardinality="+"
-     */
-    private static final String PAR_AUTH_REQ = "sling.auth.requirements";
-
     /** The default impersonation cookie name */
     private static final String DEFAULT_IMPERSONATION_COOKIE = "sling.sudo";
 
+    @Property(value = DEFAULT_IMPERSONATION_COOKIE)
+    public static final String PAR_IMPERSONATION_COOKIE_NAME = "auth.sudo.cookie";
+
     /** The default impersonation parameter name */
     private static final String DEFAULT_IMPERSONATION_PARAMETER = "sudo";
 
+    @Property(value = DEFAULT_IMPERSONATION_PARAMETER)
+    public static final String PAR_IMPERSONATION_PAR_NAME = "auth.sudo.parameter";
+
     /** The default value for allowing anonymous access */
     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";
+    @Property(boolValue = DEFAULT_ANONYMOUS_ALLOWED)
+    public static final String PAR_ANONYMOUS_ALLOWED = "auth.annonymous";
+
+    @Property(cardinality = 2147483647)
+    private static final String PAR_AUTH_REQ = "sling.auth.requirements";
 
     /**
      * The default realm for the built-in HTTP Basic authentication handler.
@@ -134,13 +120,47 @@
     private static final String DEFAULT_REALM = "Sling (Development)";
 
     /**
+     * The name of the configuration property used to set the Realm of the
+     * built-in HTTP Basic authentication handler.
+     */
+    @Property(value = DEFAULT_REALM)
+    public static final String PAR_REALM_NAME = "auth.http.realm";
+
+    /**
+     * Value of the {@link #PAR_HTTP_AUTH} property to fully enable the built-in
+     * HTTP Authentication Handler (value is "enabled").
+     */
+    private static final String HTTP_AUTH_ENABLED = "enabled";
+
+    /**
+     * Value of the {@link #PAR_HTTP_AUTH} property to completely disable the
+     * built-in HTTP Authentication Handler (value is "disabled").
+     */
+    private static final String HTTP_AUTH_DISABLED = "disabled";
+
+    /**
+     * Value of the {@link #PAR_HTTP_AUTH} property to enable extracting the
+     * credentials if the HTTP Basic authentication header is present (value is
+     * "preemptive"). In <i>preemptive</i> mode, though, the
+     * <code>requestCredentials</code> and <code>dropCredentials</code> methods
+     * will not send back a 401 response.
+     */
+    private static final String HTTP_AUTH_PREEMPTIVE = "preemptive";
+
+    @Property(value = HTTP_AUTH_PREEMPTIVE, options = {
+        @PropertyOption(name = HTTP_AUTH_ENABLED, value = "Enabled"),
+        @PropertyOption(name = HTTP_AUTH_PREEMPTIVE, value = "Enabled (Preemptive)"),
+        @PropertyOption(name = HTTP_AUTH_DISABLED, value = "Disabled") })
+    private static final String PAR_HTTP_AUTH = "auth.http";
+
+    /**
      * The name of the {@link AuthenticationInfo} property providing the option
      * {@link org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler}
      * handler to be called back on login failure or success.
      */
     private static final String AUTH_INFO_PROP_FEEDBACK_HANDLER = "$$sling.auth.AuthenticationFeedbackHandler$$";
 
-    /** @scr.reference */
+    @Reference
     private ResourceResolverFactory resourceResolverFactory;
 
     private PathBasedHolderCache<AbstractAuthenticationHandlerHolder> authHandlerCache = new PathBasedHolderCache<AbstractAuthenticationHandlerHolder>();
@@ -187,6 +207,7 @@
     // ---------- SCR integration
 
     @SuppressWarnings("unused")
+    @Activate
     private void activate(final BundleContext bundleContext,
             final Map<String, Object> properties) {
         modified(properties);
@@ -196,9 +217,10 @@
         Hashtable<String, Object> props = new Hashtable<String, Object>();
         props.put("felix.webconsole.label", plugin.getLabel());
         props.put("felix.webconsole.title", plugin.getTitle());
-        props.put("service.description",
+        props.put(Constants.SERVICE_DESCRIPTION,
             "Sling Request Authenticator WebConsole Plugin");
-        props.put("service.vendor", properties.get("service.vendor"));
+        props.put(Constants.SERVICE_VENDOR,
+            properties.get(Constants.SERVICE_VENDOR));
 
         webConsolePlugin = bundleContext.registerService(
             "javax.servlet.Servlet", plugin, props);
@@ -214,6 +236,7 @@
         authInfoPostProcessorTracker.open();
     }
 
+    @Modified
     private void modified(Map<String, Object> properties) {
         if (properties == null) {
             properties = new HashMap<String, Object>();
@@ -272,10 +295,24 @@
         // register as a service !
         final String realm = OsgiUtil.toString(properties.get(PAR_REALM_NAME),
             DEFAULT_REALM);
-        httpBasicHandler = new HttpBasicAuthenticationHandler(realm);
+        final String http = OsgiUtil.toString(properties.get(PAR_HTTP_AUTH),
+            HTTP_AUTH_PREEMPTIVE);
+        if (HTTP_AUTH_DISABLED.equals(http)) {
+            httpBasicHandler = new HttpBasicAuthenticationHandler(realm, false) {
+                @Override
+                public AuthenticationInfo extractCredentials(
+                        HttpServletRequest request, HttpServletResponse response) {
+                    return null;
+                }
+            };
+        } else {
+            httpBasicHandler = new HttpBasicAuthenticationHandler(realm,
+                HTTP_AUTH_ENABLED.equals(http));
+        }
     }
 
     @SuppressWarnings("unused")
+    @Deactivate
     private void deactivate(final BundleContext bundleContext) {
         if (engineAuthHandlerTracker != null) {
             engineAuthHandlerTracker.close();