FELIX-2639 Add new WebConsoleSecurityProvider2 interface and
increase package export version to 3.1.2; merge former
SecurityProvider proxy into OsgiManagerHttpContext; update JavaDoc
of WebConsoleSecurityProvider interface

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1022015 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index 8663c7a..dfba529 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -101,7 +101,7 @@
                             org.apache.felix.webconsole.internal.OsgiManagerActivator
                         </Bundle-Activator>
                         <Export-Package>
-                            org.apache.felix.webconsole;version=3.1
+                            org.apache.felix.webconsole;version=3.1.2
                         </Export-Package>
                         <Private-Package>
                             !org.apache.felix.webconsole,
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider.java
index 00bec5f..fa62d5e 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider.java
@@ -18,24 +18,41 @@
  */
 package org.apache.felix.webconsole;
 
+
 /**
  * The <code>WebConsoleSecurityProvider</code> is a service interface allowing
- * to use an external system to authenticate users before granting access to
- * the Web Console.
+ * to use an external system to authenticate users before granting access to the
+ * Web Console.
  *
- * @since Web Console 3.0.2
+ * @since 3.1.0; Web Console Bundle 3.1.0
  */
-public interface WebConsoleSecurityProvider {
+public interface WebConsoleSecurityProvider
+{
 
     /**
-     * Check if the user with the specified password exists and return an
-     * object identifying the user, else null
+     * Authenticates the user with the given user name and password.
+     *
+     * @param username The name of the user presented by the client
+     * @param password The password presented by the client
+     * @return Some object representing the authenticated user indicating general
+     *         access to be granted to the web console. If the user cannot be
+     *         authenticated (e.g. unknown user name or wrong password) or the
+     *         user must not be allowed access to the web console at all
+     *         <code>null</code> must be returned from this method.
      */
-    public Object authenticate(String username, String password);
+    public Object authenticate( String username, String password );
+
 
     /**
-     * Check that the authenticated user has the given role permission
+     * Checks whether bthe authenticated user has the given role permission.
+     *
+     * @param user The object referring to the authenticated user. This is the
+     *      object returned from the {@link #authenticate(String, String)}
+     *      method and will never be <code>null</code>.
+     * @param role The requested role
+     * @return <code>true</code> if the user is given permission for the given
+     *      role.
      */
-    public boolean authorize(Object user, String role);
+    public boolean authorize( Object user, String role );
 
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
new file mode 100644
index 0000000..9f91465
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleSecurityProvider2.java
@@ -0,0 +1,74 @@
+/*
+ * 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.felix.webconsole;
+
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * The <code>WebConsoleSecurityProvider2</code> extends the
+ * {@link WebConsoleSecurityProvider} interface allowing for full control of
+ * the authentication process to access the Web Console.
+ * <p>
+ * If a registered {@link WebConsoleSecurityProvider} service implements this
+ * interface the {@link #authenticate(HttpServletRequest, HttpServletResponse)}
+ * method is called instead of the
+ * {@link WebConsoleSecurityProvider#authenticate(String, String)} method.
+ *
+ * @since 3.1.2; Web Console Bundle 3.1.4
+ */
+public interface WebConsoleSecurityProvider2 extends WebConsoleSecurityProvider
+{
+
+    /**
+     * The name of the request attribute providing the object representing the
+     * authenticated user. This object is used to call the
+     * {@link WebConsoleSecurityProvider#authorize(Object, String)} to
+     * authorize access for certain roles.
+     */
+    String USER_ATTRIBUTE = "org.apache.felix.webconsole.user";
+
+
+    /**
+     * Authenticates the given request or asks the client for credentials.
+     * <p>
+     * Implementations of this method are expected to respect and implement
+     * the semantics of the <code>HttpContext.handleSecurity</code> method
+     * as specified in the OSGi HTTP Service specification.
+     * <p>
+     * If this method returns <code>true</code> it is assumed the request
+     * provided valid credentials identifying the user as accepted to access
+     * the web console. In addition, the {@link #USER_ATTRIBUTE} request
+     * attribute must be set to a non-<code>null</code> object reference
+     * identifying the authenticated user.
+     * <p>
+     * If this method returns <code>false</code> the request to the web console
+     * is terminated without any more response sent back to the client. That is
+     * the implementation is expected to have informed the client in case of
+     * non-granted access.
+     *
+     * @param request The request object
+     * @param response The response object
+     * @return <code>true</code> If the request provided valid credentials.
+     */
+    public boolean authenticate( HttpServletRequest request, HttpServletResponse response );
+
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index cfd0eca..8a5d285 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -347,6 +347,13 @@
             configurationListener = null;
         }
 
+        // stop tracking security provider
+        if ( securityProviderTracker != null )
+        {
+            securityProviderTracker.close();
+            securityProviderTracker = null;
+        }
+
         this.bundleContext = null;
     }
 
@@ -708,7 +715,8 @@
         // register the servlet and resources
         try
         {
-            HttpContext httpContext = new OsgiManagerHttpContext( httpService, realm, new SecurityProvider( securityProviderTracker, userId, password ) );
+            HttpContext httpContext = new OsgiManagerHttpContext( httpService, securityProviderTracker, userId,
+                password, realm );
 
             Dictionary servletConfig = toStringConfig( config );
 
@@ -950,7 +958,7 @@
         }
         return stringConfig;
     }
-    
+
     private Map langMap;
 
     private final Map getLangMap()
@@ -977,33 +985,4 @@
         return langMap = map;
     }
 
-    static class SecurityProvider implements WebConsoleSecurityProvider {
-
-        final ServiceTracker tracker;
-        final String username;
-        final String password;
-
-        SecurityProvider( ServiceTracker tracker, String username, String password ) {
-            this.tracker = tracker;
-            this.username = username;
-            this.password = password;
-        }
-
-        public Object authenticate(String username, String password) {
-            WebConsoleSecurityProvider provider = (WebConsoleSecurityProvider) tracker.getService();
-            if (provider != null) {
-                return provider.authenticate(username, password);
-            }
-            if (this.username.equals(username) && this.password.equals(password)) {
-                return username;
-            }
-            return null;
-        }
-
-        public boolean authorize(Object user, String role) {
-            // no op: authorize everything
-            return true;
-        }
-    }
-
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
index d930810..f9635ff 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
@@ -25,8 +25,10 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.WebConsoleSecurityProvider;
+import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
 import org.osgi.service.http.HttpContext;
 import org.osgi.service.http.HttpService;
+import org.osgi.util.tracker.ServiceTracker;
 
 
 final class OsgiManagerHttpContext implements HttpContext
@@ -38,18 +40,25 @@
 
     private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
 
-    String realm;
-
-    WebConsoleSecurityProvider securityProvider;
-
     private final HttpContext base;
 
+    private final ServiceTracker tracker;
 
-    OsgiManagerHttpContext( HttpService httpService, String realm, WebConsoleSecurityProvider securityProvider)
+    private final String username;
+
+    private final String password;
+
+    private final String realm;
+
+
+    OsgiManagerHttpContext( HttpService httpService, final ServiceTracker tracker, final String username,
+        final String password, final String realm )
     {
-        this.base = httpService.createDefaultHttpContext();
+        this.tracker = tracker;
+        this.username = username;
+        this.password = password;
         this.realm = realm;
-        this.securityProvider = securityProvider;
+        this.base = httpService.createDefaultHttpContext();
     }
 
 
@@ -87,11 +96,12 @@
      */
     public boolean handleSecurity( HttpServletRequest request, HttpServletResponse response )
     {
+        Object provider = tracker.getService();
 
-        // don't care for authentication if no user name is configured
-        if ( this.securityProvider == null )
+        // check whether the security provider can fully handle the request
+        if ( provider instanceof WebConsoleSecurityProvider2 )
         {
-            return true;
+            return ( ( WebConsoleSecurityProvider2 ) provider ).authenticate( request, response );
         }
 
         // Return immediately if the header is missing
@@ -110,27 +120,30 @@
                 String authInfo = authHeader.substring( blank ).trim();
 
                 // Check whether authorization type matches
-                if ( authType.equalsIgnoreCase( AUTHENTICATION_SCHEME_BASIC ))
+                if ( authType.equalsIgnoreCase( AUTHENTICATION_SCHEME_BASIC ) )
                 {
                     try
                     {
                         String srcString = base64Decode( authInfo );
-                        int i = srcString.indexOf(':');
-                        String username = srcString.substring(0, i);
-                        String password = srcString.substring(i + 1);
+                        int i = srcString.indexOf( ':' );
+                        String username = srcString.substring( 0, i );
+                        String password = srcString.substring( i + 1 );
 
                         // authenticate
-                        Object id = securityProvider.authenticate( username, password );
-                        if (id != null) {
+                        if ( authenticate( provider, username, password ) )
+                        {
                             // as per the spec, set attributes
-                            request.setAttribute( HttpContext.AUTHENTICATION_TYPE, "" );
+                            request.setAttribute( HttpContext.AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH );
                             request.setAttribute( HttpContext.REMOTE_USER, username );
 
+                            // set web console user attribute
+                            request.setAttribute( WebConsoleSecurityProvider2.USER_ATTRIBUTE, username );
+
                             // succeed
                             return true;
                         }
                     }
-                    catch (Exception e)
+                    catch ( Exception e )
                     {
                         // Ignore
                     }
@@ -143,7 +156,7 @@
         {
             response.setHeader( HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC + " realm=\"" + this.realm + "\"" );
             response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
-            response.setContentLength(0);
+            response.setContentLength( 0 );
             response.flushBuffer();
         }
         catch ( IOException ioe )
@@ -155,7 +168,28 @@
         return false;
     }
 
-    public static String base64Decode( String srcString )
+
+    public boolean authorize( final HttpServletRequest request, String role )
+    {
+        Object user = request.getAttribute( WebConsoleSecurityProvider2.USER_ATTRIBUTE );
+        if ( user != null )
+        {
+            WebConsoleSecurityProvider provider = ( WebConsoleSecurityProvider ) tracker.getService();
+            if ( provider != null )
+            {
+                return provider.authorize( user, role );
+            }
+
+            // no provider, grant access (backwards compatibility)
+            return true;
+        }
+
+        // missing user in the request, deny access
+        return false;
+    }
+
+
+    private static String base64Decode( String srcString )
     {
         byte[] transformed = Base64.decodeBase64( srcString );
         try
@@ -168,4 +202,18 @@
         }
     }
 
+
+    private boolean authenticate( Object provider, String username, String password )
+    {
+        if ( provider != null )
+        {
+            return ( ( WebConsoleSecurityProvider ) provider ).authenticate( username, password ) != null;
+        }
+        if ( this.username.equals( username ) && this.password.equals( password ) )
+        {
+            return true;
+        }
+        return false;
+    }
+
 }
\ No newline at end of file