Author: davidb
Date: Thu Aug 15 15:08:31 2019
New Revision: 1865232

URL: http://svn.apache.org/viewvc?rev=1865232&view=rev
Log:
FELIX-6168 Enable WebConsole login only after specified Security Providers are 
present  

WebConsoleSecurityProvider implementations can identify themselves by 
registering a service property
  "webconsole.security.provider.id"="some.id"

The Web Console itself can then be configured through the OSGi Framework 
property:
  "felix.webconsole.security.providers"="id1,id2"

The framework property is a comma-separated list of provider IDs. The Web 
Console will not start 
until all listed security providers are present in the service registry.

Added:
    
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
    
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
Modified:
    felix/trunk/webconsole/pom.xml
    
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
    
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java

Modified: felix/trunk/webconsole/pom.xml
URL: 
http://svn.apache.org/viewvc/felix/trunk/webconsole/pom.xml?rev=1865232&r1=1865231&r2=1865232&view=diff
==============================================================================
--- felix/trunk/webconsole/pom.xml (original)
+++ felix/trunk/webconsole/pom.xml Thu Aug 15 15:08:31 2019
@@ -370,7 +370,7 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
-            <version>4.1.0</version>
+            <version>4.3.0</version>
             <scope>provided</scope>
         </dependency>
 

Modified: 
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java?rev=1865232&r1=1865231&r2=1865232&view=diff
==============================================================================
--- 
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
 (original)
+++ 
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
 Thu Aug 15 15:08:31 2019
@@ -24,6 +24,7 @@ import java.security.PrivilegedException
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -36,6 +37,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
 
 import javax.servlet.GenericServlet;
 import javax.servlet.ServletConfig;
@@ -73,6 +75,7 @@ import org.osgi.service.http.HttpContext
 import org.osgi.service.http.HttpService;
 import org.osgi.service.log.LogService;
 import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
 /**
  * The <code>OSGi Manager</code> is the actual Web Console Servlet which
@@ -136,6 +139,10 @@ public class OsgiManager extends Generic
 
     private static final String FRAMEWORK_PROP_LOCALE = 
"felix.webconsole.locale"; //$NON-NLS-1$
 
+    static final String FRAMEWORK_PROP_SECURITY_PROVIDERS = 
"felix.webconsole.security.providers"; //$NON-NLS-1$
+
+    static final String SECURITY_PROVIDER_PROPERTY_NAME = 
"webconsole.security.provider.id"; //$NON-NLS-1$
+
     static final String PROP_MANAGER_ROOT = "manager.root"; //$NON-NLS-1$
 
     static final String PROP_DEFAULT_RENDER = "default.render"; //$NON-NLS-1$
@@ -206,7 +213,7 @@ public class OsgiManager extends Generic
 
     private HttpServiceTracker httpServiceTracker;
 
-    private HttpService httpService;
+    private volatile HttpService httpService;
 
     private PluginHolder holder;
 
@@ -239,6 +246,10 @@ public class OsgiManager extends Generic
 
     private Set enabledPlugins;
 
+    final ConcurrentSkipListSet<String> registeredSecurityProviders = new 
ConcurrentSkipListSet<String>();
+
+    final Set<String> requiredSecurityProviders;
+
     ResourceBundleManager resourceBundleManager;
 
     private int logLevel = DEFAULT_LOG_LEVEL;
@@ -318,9 +329,12 @@ public class OsgiManager extends Generic
         brandingTracker = new BrandingServiceTracker(this);
         brandingTracker.open();
 
+        this.requiredSecurityProviders = 
splitCommaSeparatedString(bundleContext.getProperty(FRAMEWORK_PROP_SECURITY_PROVIDERS));
+
         // add support for pluggable security
         securityProviderTracker = new ServiceTracker(bundleContext,
-            WebConsoleSecurityProvider.class.getName(), null);
+            WebConsoleSecurityProvider.class.getName(),
+            new UpdateDependenciesStateCustomizer());
         securityProviderTracker.open();
 
         // load the default configuration from the framework
@@ -382,6 +396,21 @@ public class OsgiManager extends Generic
             } );
     }
 
+    void updateRegistrationState() {
+        if (this.httpService != null) {
+            if 
(this.registeredSecurityProviders.containsAll(this.requiredSecurityProviders)) {
+                // register HTTP service
+                registerHttpService();
+                return;
+            } else {
+                log(LogService.LOG_INFO, "Not all requirements met for the Web 
Console. Required security providers: "
+                        + this.registeredSecurityProviders + " Registered 
security providers: " + this.registeredSecurityProviders);
+            }
+        }
+        // Not all requirements met, unregister service.
+        unregisterHttpService();
+    }
+
     public void dispose()
     {
         // dispose off held plugins
@@ -917,7 +946,7 @@ public class OsgiManager extends Generic
 
     }
 
-    protected synchronized void bindHttpService(HttpService httpService)
+    protected void bindHttpService(HttpService httpService)
     {
         // do not bind service, when we are already bound
         if (this.httpService != null)
@@ -927,6 +956,11 @@ public class OsgiManager extends Generic
             return;
         }
 
+        this.httpService = httpService;
+        updateRegistrationState();
+    }
+
+    synchronized void registerHttpService() {
         Map config = getConfiguration();
 
         // get authentication details
@@ -937,7 +971,7 @@ public class OsgiManager extends Generic
         // register the servlet and resources
         try
         {
-            HttpContext httpContext = new OsgiManagerHttpContext(httpService,
+            HttpContext httpContext = new 
OsgiManagerHttpContext(bundleContext, httpService,
                 securityProviderTracker, userId, password, realm);
 
             Dictionary servletConfig = toStringConfig(config);
@@ -957,11 +991,9 @@ public class OsgiManager extends Generic
         {
             log(LogService.LOG_ERROR, "bindHttpService: Problem setting up", 
e);
         }
-
-        this.httpService = httpService;
     }
 
-    protected synchronized void unbindHttpService(HttpService httpService)
+    protected void unbindHttpService(HttpService httpService)
     {
         if (this.httpService != httpService)
         {
@@ -972,7 +1004,10 @@ public class OsgiManager extends Generic
 
         // drop the service reference
         this.httpService = null;
+        updateRegistrationState();
+    }
 
+    synchronized void unregisterHttpService() {
         if (httpResourcesRegistered)
         {
             try
@@ -1149,6 +1184,20 @@ public class OsgiManager extends Generic
         return stringConfig;
     }
 
+    static Set<String> splitCommaSeparatedString(final String str) {
+        if (str == null)
+            return Collections.emptySet();
+
+        final Set<String> values = new HashSet<String>();
+        for (final String s : str.split(",")) {
+            String trimmed = s.trim();
+            if (trimmed.length() > 0) {
+                values.add(trimmed);
+            }
+        }
+        return Collections.unmodifiableSet(values);
+    }
+
     private Map langMap;
 
 
@@ -1177,4 +1226,33 @@ public class OsgiManager extends Generic
         return langMap = map;
     }
 
+    class UpdateDependenciesStateCustomizer implements 
ServiceTrackerCustomizer {
+        @Override
+        public Object addingService(ServiceReference reference) {
+            Object nameObj = 
reference.getProperty(SECURITY_PROVIDER_PROPERTY_NAME);
+            if (nameObj instanceof String) {
+                String name = (String) nameObj;
+                registeredSecurityProviders.add(name);
+                updateRegistrationState();
+            }
+            return bundleContext.getService(reference);
+        }
+
+        @Override
+        public void modifiedService(ServiceReference reference, Object 
service) {
+            removedService(reference, service);
+            addingService(reference);
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) 
{
+            Object nameObj = 
reference.getProperty(SECURITY_PROVIDER_PROPERTY_NAME);
+            if (nameObj instanceof String) {
+                String name = (String) nameObj;
+                registeredSecurityProviders.remove(name);
+                updateRegistrationState();
+            }
+        }
+
+    }
 }

Modified: 
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java?rev=1865232&r1=1865231&r2=1865232&view=diff
==============================================================================
--- 
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
 (original)
+++ 
felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
 Thu Aug 15 15:08:31 2019
@@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletRes
 
 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;
@@ -39,6 +40,8 @@ final class OsgiManagerHttpContext imple
 
     private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
 
+    private final BundleContext bundleContext;
+
     private final HttpContext base;
 
     private final ServiceTracker tracker;
@@ -50,9 +53,11 @@ final class OsgiManagerHttpContext imple
     private final String realm;
 
 
-    OsgiManagerHttpContext( HttpService httpService, final ServiceTracker 
tracker, final String username,
+    OsgiManagerHttpContext(final BundleContext bundleContext,
+        final HttpService httpService, final ServiceTracker tracker, final 
String username,
         final String password, final String realm )
     {
+        this.bundleContext = bundleContext;
         this.tracker = tracker;
         this.username = username;
         this.password = new Password(password);
@@ -228,9 +233,12 @@ final class OsgiManagerHttpContext imple
         }
         if ( this.username.equals( username ) && this.password.matches( 
password ) )
         {
-            return true;
+            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;
     }
 
-}
\ No newline at end of file
+}

Added: 
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java?rev=1865232&view=auto
==============================================================================
--- 
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
 (added)
+++ 
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContextTest.java
 Thu Aug 15 15:08:31 2019
@@ -0,0 +1,87 @@
+/*
+ * 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.internal.servlet;
+
+import org.apache.felix.webconsole.WebConsoleSecurityProvider;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.HttpService;
+
+import java.lang.reflect.Method;
+
+import static org.junit.Assert.assertEquals;
+
+public class OsgiManagerHttpContextTest {
+    @Test
+    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");
+
+        Method authenticateMethod = 
OsgiManagerHttpContext.class.getDeclaredMethod(
+                "authenticate", new Class [] {Object.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()));
+
+        WebConsoleSecurityProvider sp = new TestSecurityProvider();
+        assertEquals(true, authenticateMethod.invoke(ctx, sp, "xxx", 
"yyy".getBytes()));
+        assertEquals("The default username and password should not be accepted 
with security provider",
+                false, authenticateMethod.invoke(ctx, sp, "foo", 
"bar".getBytes()));
+    }
+
+    @Test
+    public void testAuthenticatePwdDisabledWithRequiredSecurityProvider() 
throws Exception {
+        BundleContext bc = Mockito.mock(BundleContext.class);
+        
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");
+
+        Method authenticateMethod = 
OsgiManagerHttpContext.class.getDeclaredMethod(
+                "authenticate", new Class [] {Object.class, String.class, 
byte[].class});
+        authenticateMethod.setAccessible(true);
+
+        assertEquals("A required security provider is configured, logging in 
using "
+                + "username and password should be disabled",
+                false, authenticateMethod.invoke(ctx, null, "foo", 
"bar".getBytes()));
+        assertEquals(false, authenticateMethod.invoke(ctx, null, "foo", 
"blah".getBytes()));
+        assertEquals(false, authenticateMethod.invoke(ctx, null, "blah", 
"bar".getBytes()));
+
+        WebConsoleSecurityProvider sp = new TestSecurityProvider();
+        assertEquals(true, authenticateMethod.invoke(ctx, sp, "xxx", 
"yyy".getBytes()));
+        assertEquals(false, authenticateMethod.invoke(ctx, sp, "foo", 
"bar".getBytes()));
+    }
+
+    private static class TestSecurityProvider implements 
WebConsoleSecurityProvider {
+        @Override
+        public Object authenticate(String username, String password) {
+            if ("xxx".equals(username) && "yyy".equals(password))
+                return new Object();
+            return null;
+        }
+
+        @Override
+        public boolean authorize(Object user, String role) {
+            return false;
+        }
+    }
+}

Added: 
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java?rev=1865232&view=auto
==============================================================================
--- 
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
 (added)
+++ 
felix/trunk/webconsole/src/test/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerTest.java
 Thu Aug 15 15:08:31 2019
@@ -0,0 +1,318 @@
+/*
+ * 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.internal.servlet;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.HttpService;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class OsgiManagerTest {
+    @Test
+    public void testSplitCommaSeparatedString() {
+        assertEquals(0, OsgiManager.splitCommaSeparatedString(null).size());
+        assertEquals(0, OsgiManager.splitCommaSeparatedString("").size());
+        assertEquals(0, OsgiManager.splitCommaSeparatedString(" ").size());
+        assertEquals(Collections.singleton("foo.bar"),
+                OsgiManager.splitCommaSeparatedString("foo.bar "));
+
+        Set<String> expected = new HashSet<String>();
+        expected.add("abc");
+        expected.add("x.y.z");
+        expected.add("123");
+        assertEquals(expected,
+                OsgiManager.splitCommaSeparatedString(" abc , x.y.z,123"));
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
+    @Test
+    public void testUpdateDependenciesCustomizerAdd() throws Exception {
+        BundleContext bc = mockBundleContext();
+
+        final List<Boolean> updateCalled = new ArrayList<Boolean>();
+        OsgiManager mgr = new OsgiManager(bc) {
+            void updateRegistrationState() {
+                updateCalled.add(true);
+            }
+        };
+
+        ServiceTrackerCustomizer stc = mgr.new 
UpdateDependenciesStateCustomizer();
+
+        ServiceReference sref = Mockito.mock(ServiceReference.class);
+        stc.addingService(sref);
+        assertEquals(0, updateCalled.size());
+
+        ServiceReference sref2 = Mockito.mock(ServiceReference.class);
+        
Mockito.when(sref2.getProperty(OsgiManager.SECURITY_PROVIDER_PROPERTY_NAME)).thenReturn("xyz");
+        stc.addingService(sref2);
+        assertEquals(Collections.singleton("xyz"), 
mgr.registeredSecurityProviders);
+        assertEquals(1, updateCalled.size());
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
+    @Test
+    public void testUpdateDependenciesCustomzerRemove() throws Exception {
+        BundleContext bc = mockBundleContext();
+
+        final List<Boolean> updateCalled = new ArrayList<Boolean>();
+        OsgiManager mgr = new OsgiManager(bc) {
+            void updateRegistrationState() {
+                updateCalled.add(true);
+            }
+        };
+        mgr.registeredSecurityProviders.add("abc");
+        mgr.registeredSecurityProviders.add("xyz");
+
+        ServiceTrackerCustomizer stc = mgr.new 
UpdateDependenciesStateCustomizer();
+
+        ServiceReference sref = Mockito.mock(ServiceReference.class);
+        stc.removedService(sref, null);
+        assertEquals(0, updateCalled.size());
+        assertEquals(2, mgr.registeredSecurityProviders.size());
+        assertTrue(mgr.registeredSecurityProviders.contains("abc"));
+        assertTrue(mgr.registeredSecurityProviders.contains("xyz"));
+
+        ServiceReference sref2 = Mockito.mock(ServiceReference.class);
+        
Mockito.when(sref2.getProperty(OsgiManager.SECURITY_PROVIDER_PROPERTY_NAME)).thenReturn("xyz");
+        stc.removedService(sref2, null);
+        assertEquals(Collections.singleton("abc"), 
mgr.registeredSecurityProviders);
+        assertEquals(1, updateCalled.size());
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void testUpdateDependenciesCustomzerModified() throws Exception {
+        BundleContext bc = mockBundleContext();
+
+        OsgiManager mgr = new OsgiManager(bc);
+
+        final List<String> invocations = new ArrayList<String>();
+        ServiceTrackerCustomizer stc = mgr.new 
UpdateDependenciesStateCustomizer() {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                invocations.add("added:" + reference);
+                return null;
+            }
+
+            @Override
+            public void removedService(ServiceReference reference, Object 
service) {
+                invocations.add("removed:" + reference);
+            }
+        };
+
+        ServiceReference sref = Mockito.mock(ServiceReference.class);
+        Mockito.when(sref.toString()).thenReturn("blah!");
+
+        assertEquals("Precondition", 0, invocations.size());
+        stc.modifiedService(sref, null);
+        assertEquals(2, invocations.size());
+        assertEquals("removed:blah!", invocations.get(0));
+        assertEquals("added:blah!", invocations.get(1));
+    }
+
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testUpdateRegistrationStateNoRequiredProviders() throws 
Exception {
+        BundleContext bc = mockBundleContext();
+
+        final List<String> invocations = new ArrayList<String>();
+        OsgiManager mgr = new OsgiManager(bc) {
+            @Override
+            protected synchronized void registerHttpService() {
+                invocations.add("register");
+            }
+
+            @Override
+            protected synchronized void unregisterHttpService() {
+                invocations.add("unregister");
+            }
+        };
+
+        // HTTP Service not present -> unregister
+        mgr.updateRegistrationState();
+        assertEquals(Collections.singletonList("unregister"), invocations);
+
+        // HTTP Service present, no required providers, no registered 
providers -> register
+        invocations.clear();
+        mgr.registeredSecurityProviders.clear();
+        mgr.requiredSecurityProviders.clear();
+        setPrivateField(OsgiManager.class, mgr, "httpService", 
Mockito.mock(HttpService.class));
+        mgr.updateRegistrationState();
+        assertEquals(Collections.singletonList("register"), invocations);
+    }
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testUpdateRegistrationStateSomeRequiredProviders() throws 
Exception {
+        BundleContext bc = mockBundleContext();
+        
Mockito.when(bc.getProperty(OsgiManager.FRAMEWORK_PROP_SECURITY_PROVIDERS)).
+            thenReturn("foo,blah");
+
+        final List<String> invocations = new ArrayList<String>();
+        OsgiManager mgr = new OsgiManager(bc) {
+            @Override
+            protected synchronized void registerHttpService() {
+                invocations.add("register");
+            }
+
+            @Override
+            protected synchronized void unregisterHttpService() {
+                invocations.add("unregister");
+            }
+        };
+
+        // HTTP Service present, some required providers, no registered 
providers -> unregister
+        invocations.clear();
+        mgr.registeredSecurityProviders.clear();
+        setPrivateField(OsgiManager.class, mgr, "httpService", 
Mockito.mock(HttpService.class));
+        mgr.updateRegistrationState();
+        assertEquals(Collections.singletonList("unregister"), invocations);
+
+        // HTTP Service present, some required providers, more registered ones 
-> register
+        invocations.clear();
+        mgr.registeredSecurityProviders.addAll(Arrays.asList("foo", "bar", 
"blah"));
+        setPrivateField(OsgiManager.class, mgr, "httpService", 
Mockito.mock(HttpService.class));
+        mgr.updateRegistrationState();
+        assertEquals(Collections.singletonList("register"), invocations);
+
+        // HTTP Service present, some required providers, different registered 
ones -> unregister
+        invocations.clear();
+        mgr.registeredSecurityProviders.clear();
+        mgr.registeredSecurityProviders.addAll(Arrays.asList("foo", "bar"));
+        setPrivateField(OsgiManager.class, mgr, "httpService", 
Mockito.mock(HttpService.class));
+        mgr.updateRegistrationState();
+        assertEquals(Collections.singletonList("unregister"), invocations);
+
+        // HTTP Service not present, some required providers, more registered 
ones -> unregister
+        invocations.clear();
+        mgr.registeredSecurityProviders.addAll(Arrays.asList("foo", "bar", 
"blah"));
+        setPrivateField(OsgiManager.class, mgr, "httpService", null);
+        mgr.updateRegistrationState();
+        assertEquals(Collections.singletonList("unregister"), invocations);
+    }
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testBindService() throws Exception {
+        BundleContext bc = mockBundleContext();
+
+        final List<Boolean> updateCalled = new ArrayList<Boolean>();
+        OsgiManager mgr = new OsgiManager(bc) {
+            void updateRegistrationState() {
+                updateCalled.add(true);
+            }
+        };
+
+        assertEquals("Precondition", 0, updateCalled.size());
+
+        HttpService svc = Mockito.mock(HttpService.class);
+        mgr.bindHttpService(svc);
+        assertSame(svc, getPrivateField(OsgiManager.class, mgr, 
"httpService"));
+        assertEquals(1, updateCalled.size());
+
+        updateCalled.clear();
+        mgr.bindHttpService(null);
+        assertSame(svc, getPrivateField(OsgiManager.class, mgr, 
"httpService"));
+        assertEquals(0, updateCalled.size());
+    }
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testUnbindService() throws Exception {
+        BundleContext bc = mockBundleContext();
+
+        final List<Boolean> updateCalled = new ArrayList<Boolean>();
+        OsgiManager mgr = new OsgiManager(bc) {
+            void updateRegistrationState() {
+                updateCalled.add(true);
+            }
+        };
+
+        HttpService svc = Mockito.mock(HttpService.class);
+        mgr.bindHttpService(svc);
+        assertSame(svc, getPrivateField(OsgiManager.class, mgr, 
"httpService"));
+        assertEquals(1, updateCalled.size());
+
+        updateCalled.clear();
+        mgr.unbindHttpService(null);
+        assertEquals(0, updateCalled.size());
+        assertSame(svc, getPrivateField(OsgiManager.class, mgr, 
"httpService"));
+
+        updateCalled.clear();
+        // unbind a different service, this should be ignored
+        mgr.unbindHttpService(Mockito.mock(HttpService.class));
+        assertEquals(0, updateCalled.size());
+        assertSame(svc, getPrivateField(OsgiManager.class, mgr, 
"httpService"));
+
+        updateCalled.clear();
+        // unbind the bound service, this should remove it
+        mgr.unbindHttpService(svc);
+        assertEquals(1, updateCalled.size());
+        assertNull(getPrivateField(OsgiManager.class, mgr, "httpService"));
+    }
+
+    private Object getPrivateField(Class<?> cls, Object obj, String field) 
throws Exception {
+        Field f = cls.getDeclaredField(field);
+        f.setAccessible(true);
+        return f.get(obj);
+    }
+
+    private void setPrivateField(Class<?> cls, Object obj, String field, 
Object value) throws Exception {
+        Field f = cls.getDeclaredField(field);
+        f.setAccessible(true);
+        f.set(obj, value);
+    }
+
+    private BundleContext mockBundleContext() throws InvalidSyntaxException {
+        Bundle bundle = Mockito.mock(Bundle.class);
+        BundleContext bc = Mockito.mock(BundleContext.class);
+        Mockito.when(bc.getBundle()).thenReturn(bundle);
+        Mockito.when(bundle.getBundleContext()).thenReturn(bc);
+        Mockito.when(bc.createFilter(Mockito.anyString())).then(new 
Answer<Filter>() {
+            @Override
+            public Filter answer(InvocationOnMock invocation) throws Throwable 
{
+                String fs = invocation.getArgumentAt(0, String.class);
+                return FrameworkUtil.createFilter(fs);
+            }
+        });
+        return bc;
+    }
+}


Reply via email to