You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2021/03/28 08:23:35 UTC

[felix-dev] branch master updated: FELIX-6390 Refactor the default authentication mechanism of the (#71)

This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 2278487  FELIX-6390 Refactor the default authentication mechanism of the (#71)
2278487 is described below

commit 2278487bb929dc3b761366c262c08915e5d82fd0
Author: Eric Norman <en...@apache.org>
AuthorDate: Sun Mar 28 01:23:25 2021 -0700

    FELIX-6390 Refactor the default authentication mechanism of the (#71)
    
    webconsole to be a WebConsoleSecurityProvider2
---
 ...t.java => BasicWebConsoleSecurityProvider.java} | 141 +++++----------------
 .../webconsole/internal/servlet/OsgiManager.java   |  28 +++-
 .../internal/servlet/OsgiManagerHttpContext.java   |  76 ++---------
 .../servlet/OsgiManagerHttpContextTest.java        |   9 +-
 4 files changed, 71 insertions(+), 183 deletions(-)

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/BasicWebConsoleSecurityProvider.java
similarity index 53%
copy from webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
copy to webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/BasicWebConsoleSecurityProvider.java
index c7d472f..c17e530 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/BasicWebConsoleSecurityProvider.java
@@ -16,36 +16,27 @@
  */
 package org.apache.felix.webconsole.internal.servlet;
 
-
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
-import java.net.URL;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.felix.webconsole.User;
-import org.apache.felix.webconsole.WebConsoleSecurityProvider;
 import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.http.HttpContext;
-import org.osgi.service.http.HttpService;
-import org.osgi.util.tracker.ServiceTracker;
-
-
-final class OsgiManagerHttpContext implements HttpContext
-{
-
-    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
-    private static final String HEADER_AUTHORIZATION = "Authorization";
 
-    private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
+/**
+ * Basic implementation of WebConsoleSecurityProvider to replace logic that
+ * was previously in OsgiManagerHttpContext
+ */
+public class BasicWebConsoleSecurityProvider implements WebConsoleSecurityProvider2 {
 
-    private final BundleContext bundleContext;
+    static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
 
-    private final HttpContext base;
+    static final String HEADER_AUTHORIZATION = "Authorization";
 
-    private final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker;
+    static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
 
     private final String username;
 
@@ -53,95 +44,38 @@ final class OsgiManagerHttpContext implements HttpContext
 
     private final String realm;
 
+    private BundleContext bundleContext;
 
-    OsgiManagerHttpContext(final BundleContext bundleContext,
-        final HttpService httpService, final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker, final String username,
-        final String password, final String realm )
-    {
+    public BasicWebConsoleSecurityProvider(BundleContext bundleContext, String username, String password,
+            String realm) {
+        super();
         this.bundleContext = bundleContext;
-        this.tracker = tracker;
         this.username = username;
         this.password = new Password(password);
         this.realm = realm;
-        this.base = httpService.createDefaultHttpContext();
     }
 
-
-    public String getMimeType( String name )
-    {
-        return this.base.getMimeType( name );
-    }
-
-
-    public URL getResource( String name )
-    {
-        URL url = this.base.getResource( name );
-        if ( url == null && name.endsWith( "/" ) )
+    public Object authenticate(String username, String password) {
+        if ( this.username.equals( username ) && this.password.matches( password.getBytes() ) )
         {
-            return this.base.getResource( name.substring( 0, name.length() - 1 ) );
+            if (bundleContext.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS) == null) {
+                // Only allow username and password authentication if no mandatory security providers are registered
+                return true;
+            }
         }
-        return url;
+        return null;
     }
 
-
     /**
-     * Checks the <code>Authorization</code> header of the request for Basic
-     * authentication user name and password. If contained, the credentials are
-     * compared to the user name and password set for the OSGi Console.
-     * <p>
-     * If no user name is set, the <code>Authorization</code> header is
-     * ignored and the client is assumed to be authenticated.
-     *
-     * @param request The HTTP request used to get the
-     *            <code>Authorization</code> header.
-     * @param response The HTTP response used to send the authentication request
-     *            if authentication is required but not satisfied.
-     * @return {@code} true if authentication is required and not satisfied by the request.
+     * All users authenticated with the repository are granted access for all roles in the Web Console.
      */
-    public boolean handleSecurity( final HttpServletRequest request, final HttpServletResponse response ) {
-        final WebConsoleSecurityProvider provider = tracker.getService();
-
-        // check whether the security provider can fully handle the request
-        final boolean result;
-        if ( provider instanceof WebConsoleSecurityProvider2 ) {
-            result = ( ( WebConsoleSecurityProvider2 ) provider ).authenticate( request, response );
-        } else {
-            result = handleSecurity(provider, request, response);
-        }
-
-        if ( result ) {
-            request.setAttribute(User.USER_ATTRIBUTE, new User(){
-
-				@Override
-				public boolean authorize(String role) {
-                    final Object user = this.getUserObject();
-                    if ( user == null ) {
-                        // no user object in request, deny
-                        return false;
-                    }
-					if ( provider == null ) {
-                        // no provider, allow (compatibility)
-                        return true;
-                    }
-					return provider.authorize(this.getUserObject(), role);
-				}
-
-				@Override
-				public Object getUserObject() {
-					return request.getAttribute(WebConsoleSecurityProvider2.USER_ATTRIBUTE);
-				}
-                
-            });
-        }
-        return result;
+    @Override
+    public boolean authorize(Object user, String role) {
+        return true;
     }
 
-    /**
-     * Handle security with an optional web console security provider
-     */
-    private boolean handleSecurity( final WebConsoleSecurityProvider provider, 
-        final HttpServletRequest request,
-        final HttpServletResponse response) {
+    @Override
+    public boolean authenticate(HttpServletRequest request, HttpServletResponse response) {
         // Return immediately if the header is missing
         String authHeader = request.getHeader( HEADER_AUTHORIZATION );
         if ( authHeader != null && authHeader.length() > 0 )
@@ -166,7 +100,7 @@ final class OsgiManagerHttpContext implements HttpContext
                         final String username = toString( userPass[0] );
 
                         // authenticate
-                        if ( authenticate( provider, username, userPass[1] ) )
+                        if ( authenticate( username, toString(userPass[1]) ) != null )
                         {
                             // as per the spec, set attributes
                             request.setAttribute( HttpContext.AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH );
@@ -204,7 +138,7 @@ final class OsgiManagerHttpContext implements HttpContext
         return false;
     }
 
-    private static byte[][] base64Decode( String srcString )
+    static byte[][] base64Decode( String srcString )
     {
         byte[] transformed = Base64.decodeBase64( srcString );
         for ( int i = 0; i < transformed.length; i++ )
@@ -224,8 +158,7 @@ final class OsgiManagerHttpContext implements HttpContext
             { transformed, new byte[0] };
     }
 
-
-    private static String toString( final byte[] src )
+    static String toString( final byte[] src )
     {
         try
         {
@@ -237,20 +170,4 @@ final class OsgiManagerHttpContext implements HttpContext
         }
     }
 
-
-    private boolean authenticate( WebConsoleSecurityProvider provider, String username, byte[] password )
-    {
-        if ( provider != null )
-        {
-            return provider.authenticate( username, toString( password ) ) != null;
-        }
-        if ( this.username.equals( username ) && this.password.matches( password ) )
-        {
-            if (bundleContext.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS) == null) {
-                // Only allow username and password authentication if no mandatory security providers are registered
-                return true;
-            }
-        }
-        return false;
-    }
 }
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 9a3569e..767da5b 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
@@ -230,6 +230,9 @@ public class OsgiManager extends GenericServlet
 
     private String webManagerRoot;
 
+    // not-null when the BasicWebConsoleSecurityProvider service is registered
+    private ServiceRegistration<WebConsoleSecurityProvider> basicSecurityServiceRegistration;
+
     // true if the OsgiManager is registered as a Servlet with the HttpService
     private boolean httpServletRegistered;
 
@@ -958,11 +961,22 @@ public class OsgiManager extends GenericServlet
         // register the servlet and resources
         try
         {
-            HttpContext httpContext = new OsgiManagerHttpContext(bundleContext, httpService,
-                securityProviderTracker, userId, password, realm);
+            HttpContext httpContext = new OsgiManagerHttpContext(httpService,
+                securityProviderTracker, realm);
 
             Dictionary<String, String> servletConfig = toStringConfig(config);
 
+            if (basicSecurityServiceRegistration == null) {
+                //register this component
+                BasicWebConsoleSecurityProvider service = new BasicWebConsoleSecurityProvider(bundleContext,
+                        userId, password, realm);
+                Dictionary<String, Object> serviceProperties = new Hashtable<>(); // NOSONAR
+                // this is a last resort service, so use a low service ranking to prefer all other services over this one
+                serviceProperties.put(Constants.SERVICE_RANKING, Integer.MIN_VALUE);
+                basicSecurityServiceRegistration = bundleContext.registerService(WebConsoleSecurityProvider.class,
+                        service, serviceProperties);
+            }
+
             if (!httpServletRegistered) {
                 // register this servlet and take note of this
                 httpService.registerServlet(this.webManagerRoot, this, servletConfig,
@@ -1002,6 +1016,16 @@ public class OsgiManager extends GenericServlet
         if (httpService == null)
             return;
 
+        if (basicSecurityServiceRegistration != null) {
+            try {
+                basicSecurityServiceRegistration.unregister();
+            } catch (Throwable t) {
+                log(LogService.LOG_WARNING,
+                        "unbindHttpService: Failed unregistering basic WebConsoleSecurityProvider", t);
+            }
+            basicSecurityServiceRegistration = null;
+        }
+
         if (httpResourcesRegistered)
         {
             try
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 c7d472f..f8a4e49 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
@@ -17,16 +17,19 @@
 package org.apache.felix.webconsole.internal.servlet;
 
 
+import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.AUTHENTICATION_SCHEME_BASIC;
+import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.HEADER_AUTHORIZATION;
+import static org.apache.felix.webconsole.internal.servlet.BasicWebConsoleSecurityProvider.HEADER_WWW_AUTHENTICATE;
+
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.net.URL;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.webconsole.User;
 import org.apache.felix.webconsole.WebConsoleSecurityProvider;
 import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
-import org.osgi.framework.BundleContext;
 import org.osgi.service.http.HttpContext;
 import org.osgi.service.http.HttpService;
 import org.osgi.util.tracker.ServiceTracker;
@@ -35,33 +38,17 @@ import org.osgi.util.tracker.ServiceTracker;
 final class OsgiManagerHttpContext implements HttpContext
 {
 
-    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
-    private static final String HEADER_AUTHORIZATION = "Authorization";
-
-    private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
-
-    private final BundleContext bundleContext;
-
     private final HttpContext base;
 
     private final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker;
 
-    private final String username;
-
-    private final Password password;
-
     private final String realm;
 
-
-    OsgiManagerHttpContext(final BundleContext bundleContext,
-        final HttpService httpService, final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker, final String username,
-        final String password, final String realm )
+    OsgiManagerHttpContext(final HttpService httpService,
+            final ServiceTracker<WebConsoleSecurityProvider, WebConsoleSecurityProvider> tracker,
+            final String realm)
     {
-        this.bundleContext = bundleContext;
         this.tracker = tracker;
-        this.username = username;
-        this.password = new Password(password);
         this.realm = realm;
         this.base = httpService.createDefaultHttpContext();
     }
@@ -162,8 +149,8 @@ final class OsgiManagerHttpContext implements HttpContext
                 {
                     try
                     {
-                        byte[][] userPass = base64Decode( authInfo );
-                        final String username = toString( userPass[0] );
+                        byte[][] userPass = BasicWebConsoleSecurityProvider.base64Decode( authInfo );
+                        final String username = BasicWebConsoleSecurityProvider.toString( userPass[0] );
 
                         // authenticate
                         if ( authenticate( provider, username, userPass[1] ) )
@@ -204,52 +191,11 @@ final class OsgiManagerHttpContext implements HttpContext
         return false;
     }
 
-    private static byte[][] base64Decode( String srcString )
-    {
-        byte[] transformed = Base64.decodeBase64( srcString );
-        for ( int i = 0; i < transformed.length; i++ )
-        {
-            if ( transformed[i] == ':' )
-            {
-                byte[] user = new byte[i];
-                byte[] pass = new byte[transformed.length - i - 1];
-                System.arraycopy( transformed, 0, user, 0, user.length );
-                System.arraycopy( transformed, i + 1, pass, 0, pass.length );
-                return new byte[][]
-                    { user, pass };
-            }
-        }
-
-        return new byte[][]
-            { transformed, new byte[0] };
-    }
-
-
-    private static String toString( final byte[] src )
-    {
-        try
-        {
-            return new String( src, "ISO-8859-1" );
-        }
-        catch ( UnsupportedEncodingException uee )
-        {
-            return new String( src );
-        }
-    }
-
-
     private boolean authenticate( WebConsoleSecurityProvider provider, String username, byte[] password )
     {
         if ( provider != null )
         {
-            return provider.authenticate( username, toString( password ) ) != null;
-        }
-        if ( this.username.equals( username ) && this.password.matches( password ) )
-        {
-            if (bundleContext.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS) == null) {
-                // Only allow username and password authentication if no mandatory security providers are registered
-                return true;
-            }
+            return provider.authenticate( username, BasicWebConsoleSecurityProvider.toString( password ) ) != null;
         }
         return false;
     }
diff --git a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java b/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
index 0b49dbd..70fae7e 100644
--- a/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
+++ b/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
@@ -33,14 +33,15 @@ public class OsgiManagerHttpContextTest {
     public void testAuthenticate() throws Exception {
         BundleContext bc = Mockito.mock(BundleContext.class);
         HttpService svc = Mockito.mock(HttpService.class);
-        OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(bc, svc, null, "foo", "bar", "blah");
+        OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(svc, null, "blah");
 
         Method authenticateMethod = OsgiManagerHttpContext.class.getDeclaredMethod(
                 "authenticate", new Class [] {WebConsoleSecurityProvider.class, String.class, byte[].class});
         authenticateMethod.setAccessible(true);
 
-        assertEquals(true, authenticateMethod.invoke(ctx, null, "foo", "bar".getBytes()));
-        assertEquals(false, authenticateMethod.invoke(ctx, null, "foo", "blah".getBytes()));
+        BasicWebConsoleSecurityProvider lastResortSp = new BasicWebConsoleSecurityProvider(bc, "foo", "bar", "blah");
+        assertEquals(true, authenticateMethod.invoke(ctx, lastResortSp, "foo", "bar".getBytes()));
+        assertEquals(false, authenticateMethod.invoke(ctx, lastResortSp, "foo", "blah".getBytes()));
 
         WebConsoleSecurityProvider sp = new TestSecurityProvider();
         assertEquals(true, authenticateMethod.invoke(ctx, sp, "xxx", "yyy".getBytes()));
@@ -54,7 +55,7 @@ public class OsgiManagerHttpContextTest {
         Mockito.when(bc.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS)).thenReturn("a");
 
         HttpService svc = Mockito.mock(HttpService.class);
-        OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(bc, svc, null, "foo", "bar", "blah");
+        OsgiManagerHttpContext ctx = new OsgiManagerHttpContext(svc, null, "blah");
 
         Method authenticateMethod = OsgiManagerHttpContext.class.getDeclaredMethod(
                 "authenticate", new Class [] {WebConsoleSecurityProvider.class, String.class, byte[].class});