You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2013/10/22 05:13:23 UTC

svn commit: r1534467 [4/5] - in /karaf/trunk: ./ assemblies/features/framework/src/main/feature/ assemblies/features/framework/src/main/resources/resources/etc/ itests/src/test/java/org/apache/karaf/itests/ jaas/command/src/main/java/org/apache/karaf/j...

Added: karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingEventHookTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingEventHookTest.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingEventHookTest.java (added)
+++ karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingEventHookTest.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,253 @@
+/*
+ * 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.karaf.service.guard.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.service.ListenerHook.ListenerInfo;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+public class GuardingEventHookTest {
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testEventHookEvents() throws Exception {
+        BundleContext frameworkBC = mockBundleContext(0L);
+
+        Dictionary<String, Object> config = new Hashtable<String, Object>();
+        config.put("service.guard", "(service.id=*)");
+        BundleContext hookBC = mockConfigAdminBundleContext(config);
+        GuardProxyCatalog gpc = new GuardProxyCatalog(hookBC);
+
+        Filter serviceFilter = FrameworkUtil.createFilter("(foo=bar)");
+        GuardingEventHook geh = new GuardingEventHook(hookBC, gpc, serviceFilter);
+
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_ID, 13L);
+        ServiceReference<?> sref = mockServiceReference(props);
+
+        BundleContext client1BC = mockBundleContext(123L);
+        Map<BundleContext, Collection<ListenerInfo>> listeners = new HashMap<BundleContext, Collection<ListenerInfo>>();
+        listeners.put(client1BC, Collections.<ListenerInfo>emptyList());
+
+        // Send the event. It should have no effect because the service doens't match the filter
+        assertEquals("Precondition", 0, gpc.proxyMap.size());
+        geh.event(new ServiceEvent(ServiceEvent.REGISTERED, sref), listeners);
+        assertEquals("No proxy should have been created because the service doesn't match the filter", 0, gpc.proxyMap.size());
+        assertEquals("Nothing should have been removed from the listeners", 1, listeners.size());
+
+        long service2ID = 887L;
+        Dictionary<String, Object> props2 = new Hashtable<String, Object>();
+        props2.put(Constants.SERVICE_ID, service2ID);
+        props2.put("a", "b");
+        props2.put("foo", "bar");
+        ServiceReference<?> sref2 = mockServiceReference(props2);
+        ServiceEvent se2 = new ServiceEvent(ServiceEvent.REGISTERED, sref2);
+
+        // Send the event to the system bundle and the bundle that contains the hook, should have no effect
+        Map<BundleContext, Collection<ListenerInfo>> listeners2 = new Hashtable<BundleContext, Collection<ListenerInfo>>();
+        listeners2.put(frameworkBC, Collections.<ListenerInfo>emptyList());
+        listeners2.put(hookBC, Collections.<ListenerInfo>emptyList());
+        geh.event(se2, listeners2);
+        assertEquals("No proxies to be created for the hook bundle or the system bundle", 0, gpc.proxyMap.size());
+        assertEquals("Nothing should have been removed from the listeners", 2, listeners2.size());
+
+        // This is the first time that a proxy actually does get created
+        Map<BundleContext, Collection<ListenerInfo>> listeners3 = new HashMap<BundleContext, Collection<ListenerInfo>>();
+        listeners3.put(client1BC, Collections.<ListenerInfo>emptyList());
+        geh.event(se2, listeners3);
+        assertEquals("The service should be hidden from these listeners", 0, listeners3.size());
+        assertEquals("Proxy should have been created for this client", 1, gpc.proxyMap.size());
+        assertEquals(new Long(service2ID), gpc.proxyMap.keySet().iterator().next());
+
+        // Update the service, now an additional client is interested
+        props2.put("a", "c"); // Will change the properties of sref
+        Map<BundleContext, Collection<ListenerInfo>> listeners4 = new HashMap<BundleContext, Collection<ListenerInfo>>();
+
+        BundleContext client2BC = mockBundleContext(11);
+        listeners4.put(client2BC, Collections.<ListenerInfo>emptyList());
+        listeners4.put(client1BC, Collections.<ListenerInfo>emptyList());
+        geh.event(new ServiceEvent(ServiceEvent.MODIFIED, sref2), listeners4);
+        assertEquals("The service should be hidden from these listeners", 0, listeners4.size());
+        assertEquals("There should not be an additional proxy for client 2", 1, gpc.proxyMap.size());
+        assertNotNull(gpc.proxyMap.get(service2ID));
+
+        long service3ID = 1L;
+        Dictionary<String, Object> props3 = new Hashtable<String, Object>();
+        props3.put(Constants.SERVICE_ID, service3ID);
+        props3.put("foo", "bar");
+        ServiceReference<?> sref3 = mockServiceReference(props3);
+
+        // An event for a new service
+        Map<BundleContext, Collection<ListenerInfo>> listeners5 = new HashMap<BundleContext, Collection<ListenerInfo>>();
+        listeners5.put(client1BC, Collections.<ListenerInfo>emptyList());
+        listeners5.put(client1BC, Collections.<ListenerInfo>emptyList()); // Should be ignored
+        geh.event(new ServiceEvent(ServiceEvent.REGISTERED, sref3), listeners5);
+        assertEquals("There should be an additional procy for client1 to the new service", 2, gpc.proxyMap.size());
+        assertEquals("The service should be hidden from these listeners", 0, listeners5.size());
+        assertNotNull(gpc.proxyMap.get(service2ID));
+        assertNotNull(gpc.proxyMap.get(service3ID));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testEventHookProxyEvents() throws Exception {
+        Dictionary<String, Object> config = new Hashtable<String, Object>();
+        config.put("service.guard", "(service.id=*)");
+        BundleContext hookBC = mockConfigAdminBundleContext(config);
+        GuardProxyCatalog gpc = new GuardProxyCatalog(hookBC);
+
+        Filter serviceFilter = FrameworkUtil.createFilter("(service.id=*)"); // any service will match
+        GuardingEventHook geh = new GuardingEventHook(hookBC, gpc, serviceFilter);
+
+        BundleContext client1BC = mockBundleContext(123L);
+
+        // Create a proxy service mock
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_ID, 13L);
+        props.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE);
+        ServiceReference<?> sref = mockServiceReference(props);
+        Map<BundleContext, Collection<ListenerInfo>> listeners = new HashMap<BundleContext, Collection<ListenerInfo>>();
+        listeners.put(client1BC, Collections.<ListenerInfo>emptyList());
+
+        // Send the event. It should have no effect because the service is already a proxy for the client
+        assertEquals("Precondition", 0, gpc.proxyMap.size());
+        geh.event(new ServiceEvent(ServiceEvent.REGISTERED, sref), listeners);
+        assertEquals("No changes expected for the proxy map.", 0, gpc.proxyMap.size());
+        assertEquals("The event should be delivered to the client", 1, listeners.size());
+
+        // send the event to a different client, this client should also see the event as the proxy Service Factory is shared
+        Map<BundleContext, Collection<ListenerInfo>> listeners2 = new HashMap<BundleContext, Collection<ListenerInfo>>();
+        listeners2.put(mockBundleContext(51L), Collections.<ListenerInfo>emptyList());
+        geh.event(new ServiceEvent(ServiceEvent.REGISTERED, sref), listeners2);
+        assertEquals("No changes expected for the proxy map.", 0, gpc.proxyMap.size());
+        assertEquals("The event should be delivered to the client", 1, listeners2.size());
+    }
+
+    @Test
+    public void testEventHookNoFilter() throws Exception {
+        BundleContext hookBC = mockBundleContext(5L);
+        GuardProxyCatalog gpc = new GuardProxyCatalog(hookBC);
+
+        GuardingEventHook geh = new GuardingEventHook(hookBC, gpc, null);
+        geh.event(null, null); // Should do nothing
+    }
+
+    private BundleContext mockBundleContext(long id) throws Exception {
+        Bundle bundle = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(bundle.getBundleId()).andReturn(id).anyTimes();
+
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+        EasyMock.expect(bc.getBundle()).andReturn(bundle).anyTimes();
+        EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() {
+            @Override
+            public Filter answer() throws Throwable {
+                return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        EasyMock.replay(bc);
+
+        EasyMock.expect(bundle.getBundleContext()).andReturn(bc).anyTimes();
+        EasyMock.replay(bundle);
+
+        return bc;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private BundleContext mockConfigAdminBundleContext(Dictionary<String, Object> ... configs) throws IOException,
+            InvalidSyntaxException {
+        Configuration [] configurations = new Configuration[configs.length];
+
+        for (int i = 0; i < configs.length; i++) {
+            Configuration conf = EasyMock.createMock(Configuration.class);
+            EasyMock.expect(conf.getProperties()).andReturn(configs[i]).anyTimes();
+            EasyMock.expect(conf.getPid()).andReturn((String) configs[i].get(Constants.SERVICE_PID)).anyTimes();
+            EasyMock.replay(conf);
+            configurations[i] = conf;
+        }
+
+        ConfigurationAdmin ca = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(ca.listConfigurations("(&(service.pid=org.apache.karaf.service.acl.*)(service.guard=*))")).andReturn(configurations).anyTimes();
+        EasyMock.replay(ca);
+
+        final ServiceReference caSR = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(caSR);
+
+        Bundle b = EasyMock.createMock(Bundle.class);
+        EasyMock.expect(b.getBundleId()).andReturn(877342449L).anyTimes();
+        EasyMock.replay(b);
+
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+        EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+        EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() {
+            @Override
+            public Filter answer() throws Throwable {
+                return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        String cmFilter = "(&(objectClass=" + ConfigurationAdmin.class.getName() + ")"
+                + "(!(" + GuardProxyCatalog.PROXY_SERVICE_KEY + "=*)))";
+        bc.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.eq(cmFilter));
+        EasyMock.expectLastCall().anyTimes();
+        EasyMock.expect(bc.getServiceReferences(EasyMock.anyObject(String.class), EasyMock.eq(cmFilter))).
+                andReturn(new ServiceReference<?> [] {caSR}).anyTimes();
+        EasyMock.expect(bc.getService(caSR)).andReturn(ca).anyTimes();
+        EasyMock.replay(bc);
+        return bc;
+    }
+
+    private ServiceReference<?> mockServiceReference(final Dictionary<String, Object> props) {
+        ServiceReference<?> sr = EasyMock.createMock(ServiceReference.class);
+
+        // Make sure the properties are 'live' in that if they change the reference changes too
+        EasyMock.expect(sr.getPropertyKeys()).andAnswer(new IAnswer<String[]>() {
+            @Override
+            public String[] answer() throws Throwable {
+                return Collections.list(props.keys()).toArray(new String [] {});
+            }
+        }).anyTimes();
+        EasyMock.expect(sr.getProperty(EasyMock.isA(String.class))).andAnswer(new IAnswer<Object>() {
+            @Override
+            public Object answer() throws Throwable {
+                return props.get(EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        EasyMock.replay(sr);
+        return sr;
+    }
+}

Added: karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingFindHookTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingFindHookTest.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingFindHookTest.java (added)
+++ karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/GuardingFindHookTest.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,241 @@
+/*
+ * 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.karaf.service.guard.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+public class GuardingFindHookTest {
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testFindHook() throws Exception {
+        Dictionary<String, Object> config = new Hashtable<String, Object>();
+        config.put("service.guard", "(|(moo=foo)(foo=*))");
+
+        BundleContext hookBC = mockConfigAdminBundleContext(config);
+        GuardProxyCatalog gpc = new GuardProxyCatalog(hookBC);
+
+        Filter serviceFilter = FrameworkUtil.createFilter("(foo=*)");
+        GuardingFindHook gfh = new GuardingFindHook(hookBC, gpc, serviceFilter);
+
+        BundleContext clientBC = mockBundleContext(31L);
+
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_ID, 16L);
+        props.put("moo", "foo");
+        ServiceReference<?> sref = mockServiceReference(props);
+
+        Collection<ServiceReference<?>> refs = new ArrayList<ServiceReference<?>>();
+        refs.add(sref);
+
+        assertEquals("Precondition", 0, gpc.proxyMap.size());
+        gfh.find(clientBC, null, null, true, refs);
+        assertEquals("The service doesn't match the filter so should have no effect", 0, gpc.proxyMap.size());
+        assertEquals("The service doesn't match the filter so should be presented to the client",
+                Collections.singletonList(sref), refs);
+
+        long service2ID = 17L;
+        Dictionary<String, Object> props2 = new Hashtable<String, Object>();
+        props2.put(Constants.SERVICE_ID, service2ID);
+        props2.put("foo", new Object());
+        ServiceReference<?> sref2 = mockServiceReference(props2);
+
+        Collection<ServiceReference<?>> refs2 = new ArrayList<ServiceReference<?>>();
+        refs2.add(sref2);
+
+        gfh.find(clientBC, null, null, true, refs2);
+        assertEquals("The service should be hidden from the client", 0, refs2.size());
+        assertEquals("The service should have caused a proxy creation", 1, gpc.proxyMap.size());
+        assertEquals("A proxy creation job should have been created", 1, gpc.createProxyQueue.size());
+        assertEquals(sref2.getProperty(Constants.SERVICE_ID), gpc.proxyMap.keySet().iterator().next());
+
+        Collection<ServiceReference<?>> refs3 = new ArrayList<ServiceReference<?>>();
+        refs3.add(sref2);
+
+        // Ensure that the hook bundle has nothing hidden
+        gfh.find(hookBC, null, null, true, refs3);
+        assertEquals("The service should not be hidden from the hook bundle", Collections.singletonList(sref2), refs3);
+        assertEquals("No proxy creation caused in this case", 1, gpc.proxyMap.size());
+        assertEquals("No change expected", sref2.getProperty(Constants.SERVICE_ID), gpc.proxyMap.keySet().iterator().next());
+
+        // Ensure that the system bundle has nothing hidden
+        gfh.find(mockBundleContext(0L), null, null, true, refs3);
+        assertEquals("The service should not be hidden from the framework bundle", Collections.singletonList(sref2), refs3);
+        assertEquals("No proxy creation caused in this case", 1, gpc.proxyMap.size());
+        assertEquals("No change expected", sref2.getProperty(Constants.SERVICE_ID), gpc.proxyMap.keySet().iterator().next());
+
+        // Ensure that if we ask for the same client again, it will not create another proxy
+        gpc.createProxyQueue.clear(); // Manually empty the queue
+        gfh.find(clientBC, null, null, true, refs3);
+        assertEquals("The service should be hidden from the client", 0, refs3.size());
+        assertEquals("There is already a proxy for this client, no need for an additional one", 1, gpc.proxyMap.size());
+        assertEquals("No additional jobs should have been scheduled", 0, gpc.createProxyQueue.size());
+        assertEquals("No change expected", sref2.getProperty(Constants.SERVICE_ID), gpc.proxyMap.keySet().iterator().next());
+
+        Collection<ServiceReference<?>> refs4 = new ArrayList<ServiceReference<?>>();
+        refs4.add(sref2);
+
+        // another client should not get another proxy
+        BundleContext client2BC = mockBundleContext(32768L);
+        gfh.find(client2BC, null, null, true, refs4);
+        assertEquals("The service should be hidden for this new client", 0, refs4.size());
+        assertEquals("No proxy creation job should have been created", 0, gpc.createProxyQueue.size());
+        assertEquals("No proxy creation caused in this case", 1, gpc.proxyMap.size());
+        assertEquals("No change expected", sref2.getProperty(Constants.SERVICE_ID), gpc.proxyMap.keySet().iterator().next());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testFindHookProxyServices() throws Exception {
+        Dictionary<String, Object> config = new Hashtable<String, Object>();
+        config.put("service.guard", "(service.id=*)");
+
+        BundleContext hookBC = mockConfigAdminBundleContext(config);
+        GuardProxyCatalog gpc = new GuardProxyCatalog(hookBC);
+
+        Filter serviceFilter = FrameworkUtil.createFilter("(service.id=*)"); // any service
+        GuardingFindHook gfh = new GuardingFindHook(hookBC, gpc, serviceFilter);
+
+        BundleContext clientBC = mockBundleContext(31L);
+
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_ID, 16L);
+        props.put(GuardProxyCatalog.PROXY_SERVICE_KEY, Boolean.TRUE);
+        ServiceReference<?> sref = mockServiceReference(props);
+
+        Collection<ServiceReference<?>> refs = new ArrayList<ServiceReference<?>>();
+        refs.add(sref);
+        gfh.find(clientBC, null, null, false, refs);
+        assertEquals("No proxy should have been created for the proxy find", 0, gpc.proxyMap.size());
+        assertEquals("As the proxy is for this bundle is should be visible and remain on the list",
+                Collections.singletonList(sref), refs);
+    }
+
+    @Test
+    public void testNullFilter() throws Exception {
+        BundleContext hookBC = mockBundleContext(5L);
+        GuardProxyCatalog gpc = new GuardProxyCatalog(hookBC);
+
+        GuardingFindHook gfh = new GuardingFindHook(hookBC, gpc, null);
+        gfh.find(null, null, null, true, null); // should just do nothing
+    }
+
+    private BundleContext mockBundleContext(long id) throws Exception {
+        Bundle bundle = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(bundle.getBundleId()).andReturn(id).anyTimes();
+
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+        EasyMock.expect(bc.getBundle()).andReturn(bundle).anyTimes();
+        EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() {
+            @Override
+            public Filter answer() throws Throwable {
+                Filter filter = FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
+                return filter;
+            }
+        }).anyTimes();
+        EasyMock.replay(bc);
+
+        EasyMock.expect(bundle.getBundleContext()).andReturn(bc).anyTimes();
+        EasyMock.replay(bundle);
+
+        return bc;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private BundleContext mockConfigAdminBundleContext(Dictionary<String, Object> ... configs) throws IOException,
+            InvalidSyntaxException {
+        Configuration [] configurations = new Configuration[configs.length];
+
+        for (int i = 0; i < configs.length; i++) {
+            Configuration conf = EasyMock.createMock(Configuration.class);
+            EasyMock.expect(conf.getProperties()).andReturn(configs[i]).anyTimes();
+            EasyMock.expect(conf.getPid()).andReturn((String) configs[i].get(Constants.SERVICE_PID)).anyTimes();
+            EasyMock.replay(conf);
+            configurations[i] = conf;
+        }
+
+        ConfigurationAdmin ca = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(ca.listConfigurations("(&(service.pid=org.apache.karaf.service.acl.*)(service.guard=*))")).andReturn(configurations).anyTimes();
+        EasyMock.replay(ca);
+
+        final ServiceReference caSR = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(caSR);
+
+        Bundle b = EasyMock.createMock(Bundle.class);
+        EasyMock.expect(b.getBundleId()).andReturn(877342449L).anyTimes();
+        EasyMock.replay(b);
+
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+        EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+        EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() {
+            @Override
+            public Filter answer() throws Throwable {
+                return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        String cmFilter = "(&(objectClass=" + ConfigurationAdmin.class.getName() + ")"
+                + "(!(" + GuardProxyCatalog.PROXY_SERVICE_KEY + "=*)))";
+        bc.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.eq(cmFilter));
+        EasyMock.expectLastCall().anyTimes();
+        EasyMock.expect(bc.getServiceReferences(EasyMock.anyObject(String.class), EasyMock.eq(cmFilter))).
+                andReturn(new ServiceReference<?> [] {caSR}).anyTimes();
+        EasyMock.expect(bc.getService(caSR)).andReturn(ca).anyTimes();
+        EasyMock.replay(bc);
+        return bc;
+    }
+
+    private ServiceReference<Object> mockServiceReference(final Dictionary<String, Object> props) {
+        @SuppressWarnings("unchecked")
+        ServiceReference<Object> sr = EasyMock.createMock(ServiceReference.class);
+
+        // Make sure the properties are 'live' in that if they change the reference changes too
+        EasyMock.expect(sr.getPropertyKeys()).andAnswer(new IAnswer<String[]>() {
+            @Override
+            public String[] answer() throws Throwable {
+                return Collections.list(props.keys()).toArray(new String [] {});
+            }
+        }).anyTimes();
+        EasyMock.expect(sr.getProperty(EasyMock.isA(String.class))).andAnswer(new IAnswer<Object>() {
+            @Override
+            public Object answer() throws Throwable {
+                return props.get(EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        EasyMock.replay(sr);
+        return sr;
+    }
+}

Modified: karaf/trunk/service/pom.xml
URL: http://svn.apache.org/viewvc/karaf/trunk/service/pom.xml?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/service/pom.xml (original)
+++ karaf/trunk/service/pom.xml Tue Oct 22 03:13:20 2013
@@ -36,6 +36,7 @@
     <modules>
         <module>core</module>
         <module>command</module>
+        <module>guard</module>
     </modules>
 
 </project>
\ No newline at end of file

Modified: karaf/trunk/shell/console/pom.xml
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/pom.xml?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/shell/console/pom.xml (original)
+++ karaf/trunk/shell/console/pom.xml Tue Oct 22 03:13:20 2013
@@ -147,6 +147,7 @@
                         </Export-Package>
                         <Private-Package>
                         	org.apache.karaf.shell.console.impl*,
+                            org.apache.karaf.shell.security.impl*,
                         </Private-Package>
                         <Main-Class>
                             org.apache.karaf.shell.console.impl.Main

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/Main.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/Main.java?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/Main.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/Main.java Tue Oct 22 03:13:20 2013
@@ -230,7 +230,7 @@ public class Main {
      * @throws Exception
      */
     protected ConsoleImpl createConsole(CommandProcessorImpl commandProcessor, InputStream in, PrintStream out, PrintStream err, Terminal terminal) throws Exception {
-        return new ConsoleImpl(commandProcessor, in, out, err, terminal, null, null);
+        return new ConsoleImpl(commandProcessor, in, out, err, terminal, null, null, null);
     }
 
     /**

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleFactoryService.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleFactoryService.java?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleFactoryService.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleFactoryService.java Tue Oct 22 03:13:20 2013
@@ -35,8 +35,15 @@ import org.apache.felix.service.command.
 import org.apache.karaf.jaas.boot.principal.UserPrincipal;
 import org.apache.karaf.shell.console.Console;
 import org.apache.karaf.shell.console.ConsoleFactory;
+import org.osgi.framework.BundleContext;
 
 public class ConsoleFactoryService implements ConsoleFactory {
+
+    private final BundleContext bundleContext;
+
+    public ConsoleFactoryService(BundleContext bc) {
+        bundleContext = bc;
+    }
     
     @Override
     public Console createLocal(CommandProcessor processor, final Terminal terminal, String encoding, Runnable closeCallback) {
@@ -52,7 +59,7 @@ public class ConsoleFactoryService imple
     @Override
     public Console create(CommandProcessor processor, InputStream in, PrintStream out, PrintStream err, final Terminal terminal,
             String encoding, Runnable closeCallback) {
-        ConsoleImpl console = new ConsoleImpl(processor, in, out, err, terminal, encoding, closeCallback);
+        ConsoleImpl console = new ConsoleImpl(processor, in, out, err, terminal, encoding, closeCallback, bundleContext);
         CommandSession session = console.getSession();
         session.put("APPLICATION", System.getProperty("karaf.name", "root"));
         session.put("#LINES", new Function() {

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleImpl.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleImpl.java?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleImpl.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/ConsoleImpl.java Tue Oct 22 03:13:20 2013
@@ -27,6 +27,7 @@ import java.io.InputStreamReader;
 import java.io.InterruptedIOException;
 import java.io.PrintStream;
 import java.io.Reader;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -47,7 +48,9 @@ import org.apache.karaf.shell.console.Co
 import org.apache.karaf.shell.console.Console;
 import org.apache.karaf.shell.console.SessionProperties;
 import org.apache.karaf.shell.console.completer.CommandsCompleter;
+import org.apache.karaf.shell.security.impl.SecuredCommandProcessorImpl;
 import org.apache.karaf.shell.util.ShellUtil;
+import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -73,6 +76,7 @@ public class ConsoleImpl implements Cons
     private PrintStream out;
     private PrintStream err;
     private Thread thread;
+    private final BundleContext bundleContext;
 
     public ConsoleImpl(CommandProcessor processor,
                        InputStream in,
@@ -80,18 +84,20 @@ public class ConsoleImpl implements Cons
                        PrintStream err,
                        Terminal term,
                        String encoding,
-                       Runnable closeCallback) {
+                       Runnable closeCallback,
+                       BundleContext bc) {
         this.in = in;
         this.out = out;
         this.err = err;
         this.queue = new ArrayBlockingQueue<Integer>(1024);
         this.terminal = term == null ? new UnsupportedTerminal() : term;
         this.consoleInput = new ConsoleInputStream();
-        this.session = processor.createSession(this.consoleInput, this.out, this.err);
+        this.session = new DelegateSession();
         this.session.put("SCOPE", "shell:bundle:*");
         this.session.put("SUBSHELL", "");
         this.setCompletionMode();
         this.closeCallback = closeCallback;
+        this.bundleContext = bc;
 
         try {
             reader = new ConsoleReader(null,
@@ -137,7 +143,6 @@ public class ConsoleImpl implements Cons
     }
 
     public void close(boolean closedByUser) {
-        //System.err.println("Closing");
         if (!running) {
             return;
         }
@@ -157,6 +162,7 @@ public class ConsoleImpl implements Cons
     }
 
     public void run() {
+        SecuredCommandProcessorImpl secCP = createSecuredCommandProcessor();
         thread = Thread.currentThread();
         CommandSessionHolder.setSession(session);
         running = true;
@@ -188,9 +194,25 @@ public class ConsoleImpl implements Cons
                 ShellUtil.logException(session, t);
             }
         }
+        secCP.close();
         close(true);
     }
 
+    SecuredCommandProcessorImpl createSecuredCommandProcessor() {
+        if (!(session instanceof DelegateSession)) {
+            throw new IllegalStateException("Should be an Delegate Session here, about to set the delegate");
+        }
+        DelegateSession is = (DelegateSession) session;
+
+        // make it active
+        SecuredCommandProcessorImpl secCP = new SecuredCommandProcessorImpl(bundleContext);
+        CommandSession s = secCP.createSession(consoleInput, out, err);
+
+        // before the session is activated attributes may have been set on it. Pass these on to the real session now
+        is.setDelegate(s);
+        return secCP;
+    }
+
     private void setCompletionMode() {
         try {
             File shellCfg = new File(System.getProperty("karaf.base"), "/etc/org.apache.karaf.shell.cfg");
@@ -442,4 +464,86 @@ public class ConsoleImpl implements Cons
         }
     }
 
+    static class DelegateSession implements CommandSession {
+        final Map<String, Object> attrs = new HashMap<String, Object>();
+        volatile CommandSession delegate;
+
+        @Override
+        public Object execute(CharSequence commandline) throws Exception {
+            if (delegate != null)
+                return delegate.execute(commandline);
+
+            throw new UnsupportedOperationException();
+        }
+
+        void setDelegate(CommandSession s) {
+            synchronized (this) {
+                for (Map.Entry<String, Object> entry : attrs.entrySet()) {
+                    s.put(entry.getKey(), entry.getValue());
+                }
+            }
+            delegate = s;
+        }
+
+        @Override
+        public void close() {
+            if (delegate != null)
+                delegate.close();
+        }
+
+        @Override
+        public InputStream getKeyboard() {
+            if (delegate != null)
+                return delegate.getKeyboard();
+
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public PrintStream getConsole() {
+            if (delegate != null)
+                return delegate.getConsole();
+
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Object get(String name) {
+            if (delegate != null)
+                return delegate.get(name);
+
+            return attrs.get(name);
+        }
+
+        // you can put attributes on this session before it's delegate is set...
+        @Override
+        public void put(String name, Object value) {
+            if (delegate != null) {
+                delegate.put(name, value);
+                return;
+            }
+
+            // there is no delegate yet, so we'll keep the attributes locally
+            synchronized (this) {
+                attrs.put(name, value);
+            }
+        }
+
+        @Override
+        public CharSequence format(Object target, int level) {
+            if (delegate != null)
+                return delegate.format(target, level);
+
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Object convert(Class<?> type, Object instance) {
+            if (delegate != null)
+                return delegate.convert(type, instance);
+
+            throw new UnsupportedOperationException();
+        }
+    }
+
 }

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/LocalConsoleManager.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/LocalConsoleManager.java?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/LocalConsoleManager.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/impl/jline/LocalConsoleManager.java Tue Oct 22 03:13:20 2013
@@ -25,6 +25,7 @@ import javax.security.auth.Subject;
 import jline.Terminal;
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
 import org.apache.karaf.jaas.boot.principal.UserPrincipal;
 import org.apache.karaf.shell.console.Console;
 import org.apache.karaf.shell.console.ConsoleFactory;
@@ -68,6 +69,13 @@ public class LocalConsoleManager {
         final Subject subject = new Subject();
         subject.getPrincipals().add(new UserPrincipal("karaf"));
 
+        String roles = System.getProperty("karaf.local.roles");
+        if (roles != null) {
+            for (String role : roles.split("[,]")) {
+                subject.getPrincipals().add(new RolePrincipal(role.trim()));
+            }
+        }
+
         final Terminal terminal = terminalFactory.getTerminal();
         Runnable callback = new Runnable() {
             public void run() {

Added: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformer.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformer.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformer.java (added)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformer.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,164 @@
+/*
+ * 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.karaf.shell.security.impl;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.*;
+
+public class SecuredCommandConfigTransformer implements ConfigurationListener {
+
+    static final String PROXY_COMMAND_ACL_PID_PREFIX = "org.apache.karaf.command.acl.";
+    static final String PROXY_SERVICE_ACL_PID_PREFIX = "org.apache.karaf.service.acl.command.";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SecuredCommandConfigTransformer.class);
+    private static final String CONFIGURATION_FILTER =
+            "(" + Constants.SERVICE_PID + "=" + PROXY_COMMAND_ACL_PID_PREFIX + "*)";
+
+    private ConfigurationAdmin configAdmin;
+
+    public void setConfigAdmin(ConfigurationAdmin configAdmin) {
+        this.configAdmin = configAdmin;
+    }
+
+    public void init() throws Exception {
+        Configuration[] configs = configAdmin.listConfigurations(CONFIGURATION_FILTER);
+        if (configs == null)
+            return;
+
+        for (Configuration config : configs) {
+            generateServiceGuardConfig(config);
+        }
+    }
+
+    void generateServiceGuardConfig(Configuration config) throws IOException {
+        if (!config.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX)) {
+            // not a command scope configuration file
+            return;
+        }
+
+        String scopeName = config.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length());
+        if (scopeName.indexOf('.') >= 0) {
+            // scopes don't contains dots, not a command scope
+            return;
+        }
+        scopeName = scopeName.trim();
+
+        Map<String, Dictionary<String, Object>> configMaps = new HashMap<String, Dictionary<String, Object>>();
+        for (Enumeration<String> e = config.getProperties().keys(); e.hasMoreElements(); ) {
+            String key = e.nextElement();
+            String bareCommand = key;
+            String arguments = "";
+            int idx = bareCommand.indexOf('[');
+            if (idx >= 0) {
+                arguments = convertArgs(bareCommand.substring(idx));
+                bareCommand = bareCommand.substring(0, idx);
+            }
+            if (bareCommand.indexOf('.') >= 0) {
+                // not a command
+                continue;
+            }
+            bareCommand = bareCommand.trim();
+
+            String pid = PROXY_SERVICE_ACL_PID_PREFIX + scopeName + "." + bareCommand;
+            Dictionary<String, Object> map;
+            if (!configMaps.containsKey(pid)) {
+                map = new Hashtable<String, Object>();
+                map.put("service.guard", "(&(" +
+                        CommandProcessor.COMMAND_SCOPE + "=" + scopeName + ")(" +
+                        CommandProcessor.COMMAND_FUNCTION + "=" + bareCommand + "))");
+                configMaps.put(pid, map);
+            } else {
+                map = configMaps.get(pid);
+            }
+
+            // put rules on the map twice, once for commands that 'execute' (implement Function) and
+            // once for commands that are invoked directly
+            Object roleString = config.getProperties().get(key);
+            map.put("execute" + arguments, roleString);
+            map.put(key, roleString);
+            map.put("*", "*"); // any other method may be invoked by anyone
+        }
+
+        LOGGER.info("Generating command ACL config {} into service ACL configs {}",
+                config.getPid(), configMaps.keySet());
+
+        // update config admin with the generated configuration
+        for (Map.Entry<String, Dictionary<String, Object>> entry : configMaps.entrySet()) {
+            Configuration genConfig = configAdmin.getConfiguration(entry.getKey());
+            genConfig.update(entry.getValue());
+        }
+    }
+
+    private String convertArgs(String commandACLArgs) {
+        if (!commandACLArgs.startsWith("[/")) {
+            throw new IllegalStateException("Badly formatted argument match: " + commandACLArgs + " Should start with '[/'");
+        }
+        if (!commandACLArgs.endsWith("/]")) {
+            throw new IllegalStateException("Badly formatted argument match: " + commandACLArgs + " Should end with '/]'");
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append("[/.*/,"); // add a wildcard argument since the Function execute method has the arguments as second arg
+        sb.append(commandACLArgs.substring(1));
+        return sb.toString();
+    }
+
+    void deleteServiceGuardConfig(String originatingPid, String scope) throws IOException, InvalidSyntaxException {
+        if (scope.contains("."))
+            // This is not a command scope as that should be a single word without any further dots
+            return;
+
+        // Delete all the generated configurations for this scope
+        Configuration[] configs = configAdmin.listConfigurations("(service.pid=" + PROXY_SERVICE_ACL_PID_PREFIX + scope + ".*)");
+        if (configs == null)
+            return;
+
+        LOGGER.info("Config ACL deleted: {}. Deleting generated service ACL configs {}", originatingPid, configs);
+        for (Configuration config : configs) {
+            config.delete();
+        }
+    }
+
+    @Override
+    public void configurationEvent(ConfigurationEvent event) {
+        if (!event.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX))
+            return;
+
+        try {
+            switch (event.getType()) {
+                case ConfigurationEvent.CM_DELETED:
+                    deleteServiceGuardConfig(event.getPid(), event.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length()));
+                    break;
+                case ConfigurationEvent.CM_UPDATED:
+                    generateServiceGuardConfig(configAdmin.getConfiguration(event.getPid()));
+                    break;
+            }
+        } catch (Exception e) {
+            LOGGER.error("Problem processing Configuration Event {}", event, e);
+        }
+    }
+
+}

Added: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImpl.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImpl.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImpl.java (added)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImpl.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,190 @@
+/*
+ * 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.karaf.shell.security.impl;
+
+import org.apache.felix.gogo.api.CommandSessionListener;
+import org.apache.felix.gogo.runtime.CommandProcessorImpl;
+import org.apache.felix.gogo.runtime.CommandProxy;
+import org.apache.felix.gogo.runtime.activator.Activator;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.service.threadio.ThreadIO;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import javax.security.auth.Subject;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class SecuredCommandProcessorImpl extends CommandProcessorImpl {
+
+    private final BundleContext bundleContext;
+    private final ServiceReference<ThreadIO> threadIOServiceReference;
+    private final ServiceTracker<Object, Object> commandTracker;
+    private final ServiceTracker<Converter, Converter> converterTracker;
+    private final ServiceTracker<CommandSessionListener, CommandSessionListener> listenerTracker;
+
+    public SecuredCommandProcessorImpl(BundleContext bc) {
+        this(bc, bc.getServiceReference(ThreadIO.class));
+    }
+
+    private SecuredCommandProcessorImpl(BundleContext bc, ServiceReference<ThreadIO> sr) {
+        super(bc.getService(sr));
+        bundleContext = bc;
+        threadIOServiceReference = sr;
+
+        AccessControlContext acc = AccessController.getContext();
+        Subject sub = Subject.getSubject(acc);
+        if (sub == null)
+            throw new SecurityException("No current Subject in the Access Control Context");
+
+        Set<RolePrincipal> rolePrincipals = sub.getPrincipals(RolePrincipal.class);
+        if (rolePrincipals.size() == 0)
+            throw new SecurityException("Current user has no associated roles.");
+
+        // TODO cater for custom roles
+        StringBuilder sb = new StringBuilder();
+        sb.append("(|");
+        for (RolePrincipal rp : rolePrincipals) {
+            sb.append('(');
+            sb.append("org.apache.karaf.service.guard.roles");
+            sb.append('=');
+            sb.append(rp.getName());
+            sb.append(')');
+        }
+        sb.append("(!(org.apache.karaf.service.guard.roles=*))"); // Or no roles specified at all
+        sb.append(')');
+        String roleClause = sb.toString();
+
+        addConstant(Activator.CONTEXT, bc);
+        addCommand("osgi", this, "addCommand");
+        addCommand("osgi", this, "removeCommand");
+        addCommand("osgi", this, "eval");
+
+        try {
+            // The role clause is used to only display commands that the current user can invoke.
+            commandTracker = trackCommands(bc, roleClause);
+            commandTracker.open();
+
+            converterTracker = trackConverters(bc);
+            converterTracker.open();
+            listenerTracker = trackListeners(bc);
+            listenerTracker.open();
+        } catch (InvalidSyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void close() {
+        commandTracker.close();
+        converterTracker.close();
+        listenerTracker.close();
+        bundleContext.ungetService(threadIOServiceReference);
+    }
+
+    private ServiceTracker<Object, Object> trackCommands(final BundleContext context, String roleClause) throws InvalidSyntaxException {
+        Filter filter = context.createFilter(String.format("(&(%s=*)(%s=*)%s)",
+                CommandProcessor.COMMAND_SCOPE, CommandProcessor.COMMAND_FUNCTION, roleClause));
+
+        return new ServiceTracker<Object, Object>(context, filter, null) {
+            @Override
+            public Object addingService(ServiceReference<Object> reference) {
+                Object scope = reference.getProperty(CommandProcessor.COMMAND_SCOPE);
+                Object function = reference.getProperty(CommandProcessor.COMMAND_FUNCTION);
+                List<Object> commands = new ArrayList<Object>();
+
+                if (scope != null && function != null) {
+                    if (function.getClass().isArray()) {
+                        for (Object f : ((Object[]) function)) {
+                            Function target = new CommandProxy(context, reference,
+                                    f.toString());
+                            addCommand(scope.toString(), target, f.toString());
+                            commands.add(target);
+                        }
+                    } else {
+                        Function target = new CommandProxy(context, reference,
+                                function.toString());
+                        addCommand(scope.toString(), target, function.toString());
+                        commands.add(target);
+                    }
+                    return commands;
+                }
+                return null;
+            }
+
+            @Override
+            public void removedService(ServiceReference<Object> reference, Object service) {
+                Object scope = reference.getProperty(CommandProcessor.COMMAND_SCOPE);
+                Object function = reference.getProperty(CommandProcessor.COMMAND_FUNCTION);
+
+                if (scope != null && function != null) {
+                    if (!function.getClass().isArray()) {
+                        removeCommand(scope.toString(), function.toString());
+                    } else {
+                        for (Object func : (Object[]) function) {
+                            removeCommand(scope.toString(), func.toString());
+                        }
+                    }
+                }
+                super.removedService(reference, service);
+            }
+        };
+    }
+
+    private ServiceTracker<Converter, Converter> trackConverters(BundleContext context) {
+        return new ServiceTracker<Converter, Converter>(context, Converter.class.getName(), null) {
+            @Override
+            public Converter addingService(ServiceReference<Converter> reference) {
+                Converter converter = super.addingService(reference);
+                addConverter(converter);
+                return converter;
+            }
+
+            @Override
+            public void removedService(ServiceReference<Converter> reference, Converter service) {
+                removeConverter(service);
+                super.removedService(reference, service);
+            }
+        };
+    }
+
+    private ServiceTracker<CommandSessionListener, CommandSessionListener> trackListeners(BundleContext context) {
+        return new ServiceTracker<CommandSessionListener, CommandSessionListener>(context, CommandSessionListener.class.getName(), null) {
+            @Override
+            public CommandSessionListener addingService(ServiceReference<CommandSessionListener> reference) {
+                CommandSessionListener listener = super.addingService(reference);
+                addListener(listener);
+                return listener;
+            }
+
+            @Override
+            public void removedService(ServiceReference<CommandSessionListener> reference, CommandSessionListener service) {
+                removeListener(service);
+                super.removedService(reference, service);
+            }
+        };
+    }
+
+}

Modified: karaf/trunk/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml (original)
+++ karaf/trunk/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml Tue Oct 22 03:13:20 2013
@@ -22,17 +22,19 @@
 
     <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]">
         <ext:default-properties>
-            <ext:property name="karaf.startLocalConsole" value="true" />
+            <ext:property name="karaf.startLocalConsole" value="true"/>
         </ext:default-properties>
     </ext:property-placeholder>
 
-    <reference id="commandProcessor" interface="org.apache.felix.service.command.CommandProcessor" />
+    <reference id="commandProcessor" interface="org.apache.felix.service.command.CommandProcessor"/>
 
     <bean id="consoleFactoryService" class="org.apache.karaf.shell.console.impl.jline.ConsoleFactoryService">
-    </bean>    
-    <service interface="org.apache.karaf.shell.console.ConsoleFactory" ref="consoleFactoryService" />
+        <argument ref="blueprintBundleContext"/>
+    </bean>
+    <service interface="org.apache.karaf.shell.console.ConsoleFactory" ref="consoleFactoryService"/>
 
-    <bean id="consoleFactory" class="org.apache.karaf.shell.console.impl.jline.LocalConsoleManager" destroy-method="stop">
+    <bean id="consoleFactory" class="org.apache.karaf.shell.console.impl.jline.LocalConsoleManager"
+          destroy-method="stop">
         <argument value="$[karaf.startLocalConsole]"/>
         <argument value="$[org.osgi.framework.startlevel.beginning]"/>
         <argument ref="blueprintBundleContext"/>
@@ -65,6 +67,17 @@
         </bean>
     </service>
 
-    <bean id="exit" class="org.apache.karaf.shell.console.ExitAction" activation="lazy" scope="prototype" />
+    <bean id="exit" class="org.apache.karaf.shell.console.ExitAction" activation="lazy" scope="prototype"/>
+
+    <!-- Get a reference to the Configuration Admin Service -->
+    <reference id="configAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
+
+    <!-- For role-based security on the shell commands -->
+    <bean id="secureCommandConfigTransformer"
+          class="org.apache.karaf.shell.security.impl.SecuredCommandConfigTransformer"
+          init-method="init">
+        <property name="configAdmin" ref="configAdmin"/>
+    </bean>
+    <service ref="secureCommandConfigTransformer" interface="org.osgi.service.cm.ConfigurationListener"/>
 
 </blueprint>

Modified: karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/ExampleSubclassMain.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/ExampleSubclassMain.java?rev=1534467&r1=1534466&r2=1534467&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/ExampleSubclassMain.java (original)
+++ karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/ExampleSubclassMain.java Tue Oct 22 03:13:20 2013
@@ -46,7 +46,7 @@ public class ExampleSubclassMain extends
 
     @Override
     protected ConsoleImpl createConsole(CommandProcessorImpl commandProcessor, InputStream in, PrintStream out, PrintStream err, Terminal terminal) throws Exception {
-        return new ConsoleImpl(commandProcessor, in, out, err, terminal, null, null) {
+        return new ConsoleImpl(commandProcessor, in, out, err, terminal, null, null, null) {
 
             /**
              * If you don't overwrite, then karaf will use the welcome message found in the

Added: karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/impl/jline/ConsoleImplTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/impl/jline/ConsoleImplTest.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/impl/jline/ConsoleImplTest.java (added)
+++ karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/impl/jline/ConsoleImplTest.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,194 @@
+/*
+ * 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.karaf.shell.console.impl.jline;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.apache.felix.gogo.api.CommandSessionListener;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.apache.karaf.shell.console.impl.jline.ConsoleImpl.DelegateSession;
+import org.apache.karaf.shell.security.impl.SecuredCommandProcessorImpl;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+public class ConsoleImplTest {
+    @Test
+    public void testConsoleImpl() throws Exception {
+        ServiceReference<?> cmRef = EasyMock.createMock(ServiceReference.class);
+        EasyMock.expect(cmRef.getProperty(CommandProcessor.COMMAND_SCOPE)).andReturn("myscope").anyTimes();
+        EasyMock.expect(cmRef.getProperty(CommandProcessor.COMMAND_FUNCTION)).andReturn("myfunction").anyTimes();
+        EasyMock.replay(cmRef);
+        ServiceReference<?>[] cmRefs = new ServiceReference[] {cmRef};
+
+        BundleContext bc = EasyMock.createMock(BundleContext.class);
+        EasyMock.expect(bc.getServiceReference((Class<?>) EasyMock.anyObject())).andReturn(null).anyTimes();
+        EasyMock.expect(bc.getService((ServiceReference<?>) EasyMock.anyObject())).andReturn(null).anyTimes();
+        bc.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.isA(String.class));
+        EasyMock.expectLastCall().anyTimes();
+        EasyMock.expect(bc.getServiceReferences((String) null,
+                "(&(osgi.command.scope=*)(osgi.command.function=*)" +
+                        "(|(org.apache.karaf.service.guard.roles=myrole)(!(org.apache.karaf.service.guard.roles=*))))")).andReturn(cmRefs).anyTimes();
+        EasyMock.expect(bc.getServiceReferences(Converter.class.getName(), null)).andReturn(null).anyTimes();
+        EasyMock.expect(bc.getServiceReferences(CommandSessionListener.class.getName(), null)).andReturn(null).anyTimes();
+        EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() {
+            @Override
+            public Filter answer() throws Throwable {
+                return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        EasyMock.replay(bc);
+
+        final ConsoleImpl console = new ConsoleImpl(null, System.in, System.out, System.err, null, "UTF-8", null, bc);
+        assertTrue(console.session instanceof DelegateSession);
+
+        console.session.put("foo", "bar");
+
+        final DelegateSession ds = (DelegateSession) console.session;
+        assertNull("Precondition", ds.delegate);
+
+        Subject subject = new Subject();
+        subject.getPrincipals().add(new RolePrincipal("myrole"));
+
+        Subject.doAs(subject, new PrivilegedAction<Object>() {
+            @Override
+            public Object run() {
+                SecuredCommandProcessorImpl secCP = console.createSecuredCommandProcessor();
+                assertNotNull(ds.delegate);
+                assertEquals("Attributes set before the delegate was set should have been transferred",
+                        "bar", ds.get("foo"));
+                assertEquals("Attributes set before the delegate was set should have been transferred",
+                        "bar", ds.delegate.get("foo"));
+                assertSame(System.out, ds.delegate.getConsole());
+                assertSame(System.out, ds.getConsole());
+
+                assertTrue(secCP.getCommands().contains("myscope:myfunction"));
+
+                return null;
+            }
+        });
+    }
+
+    @Test
+    public void testDelegateSession() throws Exception {
+        DelegateSession ds = new DelegateSession();
+
+        ds.put("a", "b");
+        assertEquals("b", ds.get("a"));
+
+        TestSession ts = new TestSession();
+
+        assertNull("Precondition", ts.lastInvoked);
+
+        ds.setDelegate(ts);
+        assertEquals("put(a,b)", ts.lastInvoked);
+
+        ds.put("c", "d");
+        assertEquals("put(c,d)", ts.lastInvoked);
+
+        ds.execute("hello 1234");
+        assertEquals("execute(hello 1234)", ts.lastInvoked);
+
+        ds.close();
+        assertEquals("close", ts.lastInvoked);
+
+        ds.getKeyboard();
+        assertEquals("getKeyboard", ts.lastInvoked);
+
+        ds.getConsole();
+        assertEquals("getConsole", ts.lastInvoked);
+
+        ds.get("xyz");
+        assertEquals("get(xyz)", ts.lastInvoked);
+
+        ds.format("foo", 12);
+        assertEquals("format(foo,12)", ts.lastInvoked);
+
+        ds.convert(TestSession.class, "a string");
+        assertEquals("convert(TestSession,a string)", ts.lastInvoked);
+    }
+
+    static class TestSession implements CommandSession {
+        String lastInvoked;
+
+        @Override
+        public Object execute(CharSequence commandline) throws Exception {
+            lastInvoked = "execute(" + commandline + ")";
+            return null;
+        }
+
+        @Override
+        public void close() {
+            lastInvoked = "close";
+        }
+
+        @Override
+        public InputStream getKeyboard() {
+            lastInvoked = "getKeyboard";
+            return null;
+        }
+
+        @Override
+        public PrintStream getConsole() {
+            lastInvoked = "getConsole";
+            return null;
+        }
+
+        @Override
+        public Object get(String name) {
+            lastInvoked = "get(" + name + ")";
+            return null;
+        }
+
+        @Override
+        public void put(String name, Object value) {
+            lastInvoked = "put(" + name + "," + value + ")";
+        }
+
+        @Override
+        public CharSequence format(Object target, int level) {
+            lastInvoked = "format(" + target + "," + level + ")";
+            return null;
+        }
+
+        @Override
+        public Object convert(Class<?> type, Object instance) {
+            lastInvoked = "convert(" + type.getSimpleName() + "," + instance + ")";
+            return null;
+        }
+    }
+}
\ No newline at end of file

Added: karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformerTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformerTest.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformerTest.java (added)
+++ karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandConfigTransformerTest.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,278 @@
+/*
+ * 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.karaf.shell.security.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+
+public class SecuredCommandConfigTransformerTest {
+    @Test
+    public void testTransformation() throws Exception {
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put("foo", "a,b,c");
+        props.put("bar[/.*[a]+*/]", "d");
+        props.put("bar", "e");
+        props.put("zar[/.*HiThere*/]", "f");
+        props.put("service.pid", SecuredCommandConfigTransformer.PROXY_COMMAND_ACL_PID_PREFIX + "abc");
+        Configuration commandConfig = mockConfiguration(props);
+
+        Dictionary<String, Object> props2 = new Hashtable<String, Object>();
+        props2.put("xxx", "yyy");
+        props2.put("service.pid", SecuredCommandConfigTransformer.PROXY_COMMAND_ACL_PID_PREFIX + "xyz.123");
+        Configuration commandConfig2 = mockConfiguration(props2);
+
+        Dictionary<String, Object> props3 = new Hashtable<String, Object>();
+        props3.put("test", "toast");
+        props3.put("service.pid", "xyz.123");
+        Configuration otherConfig = mockConfiguration(props3);
+
+        final Map<String, Configuration> configurations = new HashMap<String, Configuration>();
+
+        ConfigurationAdmin ca = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(ca.listConfigurations(
+                "(service.pid=" + SecuredCommandConfigTransformer.PROXY_COMMAND_ACL_PID_PREFIX + "*)")).
+                andReturn(new Configuration [] {commandConfig, commandConfig2, otherConfig}).anyTimes();
+        EasyMock.expect(ca.getConfiguration(EasyMock.isA(String.class))).andAnswer(new IAnswer<Configuration>() {
+            @Override
+            public Configuration answer() throws Throwable {
+                String pid = (String) EasyMock.getCurrentArguments()[0];
+                Configuration c = configurations.get(pid);
+                if (c == null) {
+                    c = EasyMock.createMock(Configuration.class);
+
+                    // Put some expectations in the various mocks
+                    Dictionary<String, Object> m = new Hashtable<String, Object>();
+                    if ("org.apache.karaf.service.acl.command.abc.foo".equals(pid)) {
+                        m.put("service.guard", "(&(osgi.command.scope=abc)(osgi.command.function=foo))");
+                        m.put("execute", "a,b,c");
+                        m.put("foo", "a,b,c");
+                    } else if ("org.apache.karaf.service.acl.command.abc.bar".equals(pid)) {
+                        m.put("service.guard", "(&(osgi.command.scope=abc)(osgi.command.function=bar))");
+                        m.put("execute[/.*/,/.*[a]+*/]", "d");
+                        m.put("execute", "e");
+                        m.put("bar[/.*[a]+*/]", "d");
+                        m.put("bar", "e");
+                    } else if ("org.apache.karaf.service.acl.command.abc.zar".equals(pid)) {
+                        m.put("service.guard", "(&(osgi.command.scope=abc)(osgi.command.function=zar))");
+                        m.put("execute[/.*/,/.*HiThere*/]", "f");
+                        m.put("zar[/.*HiThere*/]", "f");
+                    } else {
+                        fail("Unexpected PID: " + pid);
+                    }
+                    m.put("*", "*");
+                    c.update(m);
+                    EasyMock.expectLastCall().once();
+
+                    EasyMock.replay(c);
+                    configurations.put(pid, c);
+                }
+                return c;
+            }
+        }).anyTimes();
+        EasyMock.replay(ca);
+
+        SecuredCommandConfigTransformer scct = new SecuredCommandConfigTransformer();
+        scct.setConfigAdmin(ca);
+        scct.init();
+
+        assertEquals(3, configurations.size());
+
+        boolean foundFoo = false;
+        boolean foundBar = false;
+        boolean foundZar = false;
+        for (Map.Entry<String, Configuration> entry : configurations.entrySet()) {
+            Configuration c = entry.getValue();
+            EasyMock.verify(c);
+            if ("org.apache.karaf.service.acl.command.abc.foo".equals(entry.getKey())) {
+                foundFoo = true;
+            } else if ("org.apache.karaf.service.acl.command.abc.bar".equals(entry.getKey())) {
+                foundBar = true;
+            } else if ("org.apache.karaf.service.acl.command.abc.zar".equals(entry.getKey())) {
+                foundZar = true;
+            }
+        }
+
+        assertTrue(foundFoo);
+        assertTrue(foundBar);
+        assertTrue(foundZar);
+
+    }
+
+    Configuration mockConfiguration(Dictionary<String, Object> props) {
+        Configuration commandConfig = EasyMock.createMock(Configuration.class);
+        EasyMock.expect(commandConfig.getPid()).
+                andReturn((String) props.get(Constants.SERVICE_PID)).anyTimes();
+        EasyMock.expect(commandConfig.getProperties()).andReturn(props).anyTimes();
+        EasyMock.replay(commandConfig);
+        return commandConfig;
+    }
+
+    @Test
+    public void testConfigurationEventAdded() throws Exception {
+        String testPid = SecuredCommandConfigTransformer.PROXY_COMMAND_ACL_PID_PREFIX + "test123";
+        Configuration conf = EasyMock.createMock(Configuration.class);
+        EasyMock.expect(conf.getPid()).andReturn(testPid).anyTimes();
+        EasyMock.replay(conf);
+
+        ConfigurationAdmin cm = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(cm.listConfigurations(EasyMock.isA(String.class))).andReturn(null).anyTimes();
+        EasyMock.expect(cm.getConfiguration(testPid)).andReturn(conf).anyTimes();
+        EasyMock.replay(cm);
+
+        final List<String> generateCalled = new ArrayList<String>();
+        SecuredCommandConfigTransformer scct = new SecuredCommandConfigTransformer() {
+            @Override
+            void generateServiceGuardConfig(Configuration config) throws IOException {
+                generateCalled.add(config.getPid());
+            }
+        };
+        scct.setConfigAdmin(cm);
+        scct.init();
+
+        @SuppressWarnings("unchecked")
+        ServiceReference<ConfigurationAdmin> cmRef = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(cmRef);
+
+        ConfigurationEvent event = new ConfigurationEvent(cmRef, ConfigurationEvent.CM_UPDATED, null, testPid);
+
+        assertEquals("Precondition", 0, generateCalled.size());
+        scct.configurationEvent(event);
+        assertEquals(1, generateCalled.size());
+        assertEquals(testPid, generateCalled.iterator().next());
+    }
+
+    @Test
+    public void testConfigurationEventAddedNonCommand() throws Exception {
+        ConfigurationAdmin cm = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(cm.listConfigurations(EasyMock.isA(String.class))).andReturn(null).anyTimes();
+        EasyMock.replay(cm);
+
+        SecuredCommandConfigTransformer scct = new SecuredCommandConfigTransformer();
+        scct.setConfigAdmin(cm);
+        scct.init();
+
+        @SuppressWarnings("unchecked")
+        ServiceReference<ConfigurationAdmin> cmRef = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(cmRef);
+        ConfigurationEvent event = new ConfigurationEvent(cmRef, ConfigurationEvent.CM_UPDATED, null, "test123");
+
+        scct.configurationEvent(event);
+        EasyMock.verify(cm); // Ensure that this doesn't cause any unwanted calls on ConfigAdmin
+    }
+
+    @Test
+    public void testConfigurationEventDeleted() throws Exception {
+        String testPid = SecuredCommandConfigTransformer.PROXY_COMMAND_ACL_PID_PREFIX + "test123";
+
+        ConfigurationAdmin cm = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(cm.listConfigurations(EasyMock.isA(String.class))).andReturn(null).anyTimes();
+        EasyMock.replay(cm);
+
+        SecuredCommandConfigTransformer scct = new SecuredCommandConfigTransformer();
+        scct.setConfigAdmin(cm);
+        scct.init();
+
+        @SuppressWarnings("unchecked")
+        ServiceReference<ConfigurationAdmin> cmRef = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(cmRef);
+        ConfigurationEvent event = new ConfigurationEvent(cmRef, ConfigurationEvent.CM_DELETED, null, testPid);
+
+        Configuration c1 = EasyMock.createMock(Configuration.class);
+        c1.delete();
+        EasyMock.expectLastCall().once();
+        EasyMock.replay(c1);
+        Configuration c2 = EasyMock.createMock(Configuration.class);
+        c2.delete();
+        EasyMock.expectLastCall().once();
+        EasyMock.replay(c2);
+
+        EasyMock.reset(cm);
+        EasyMock.expect(cm.listConfigurations("(service.pid=org.apache.karaf.service.acl.command.test123.*)")).
+                andReturn(new Configuration[] {c1, c2}).once();
+        EasyMock.replay(cm);
+
+        scct.configurationEvent(event);
+
+        EasyMock.verify(cm);
+        EasyMock.verify(c1);
+        EasyMock.verify(c2);
+    }
+
+    @Test
+    public void testConfigurationEventDeletedNonScope() throws Exception {
+        String testPid = SecuredCommandConfigTransformer.PROXY_COMMAND_ACL_PID_PREFIX + "abc.def";
+
+        ConfigurationAdmin cm = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(cm.listConfigurations(EasyMock.isA(String.class))).andReturn(null).anyTimes();
+        EasyMock.replay(cm);
+
+        SecuredCommandConfigTransformer scct = new SecuredCommandConfigTransformer();
+        scct.setConfigAdmin(cm);
+        scct.init();
+
+        @SuppressWarnings("unchecked")
+        ServiceReference<ConfigurationAdmin> cmRef = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(cmRef);
+        ConfigurationEvent event = new ConfigurationEvent(cmRef, ConfigurationEvent.CM_DELETED, null, testPid);
+
+        EasyMock.reset(cm);
+        // Do not expect any further calls to cm...
+        EasyMock.replay(cm);
+
+        scct.configurationEvent(event);
+        EasyMock.verify(cm);
+    }
+
+    @Test
+    public void testConfigurationLocationChangedEventNoEffect() throws Exception {
+        String testPid = SecuredCommandConfigTransformer.PROXY_COMMAND_ACL_PID_PREFIX + "test123";
+
+        ConfigurationAdmin cm = EasyMock.createMock(ConfigurationAdmin.class);
+        EasyMock.expect(cm.listConfigurations(EasyMock.isA(String.class))).andReturn(null).anyTimes();
+        EasyMock.replay(cm);
+
+        SecuredCommandConfigTransformer scct = new SecuredCommandConfigTransformer();
+        scct.setConfigAdmin(cm);
+        scct.init();
+
+        @SuppressWarnings("unchecked")
+        ServiceReference<ConfigurationAdmin> cmRef = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(cmRef);
+        ConfigurationEvent event = new ConfigurationEvent(cmRef, ConfigurationEvent.CM_LOCATION_CHANGED, null, testPid);
+
+        scct.configurationEvent(event);
+        EasyMock.verify(cm); // Ensure that this doesn't cause any unwanted calls on ConfigAdmin
+    }
+}

Added: karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImplTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImplTest.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImplTest.java (added)
+++ karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/security/impl/SecuredCommandProcessorImplTest.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,142 @@
+/*
+ * 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.karaf.shell.security.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+import org.apache.felix.gogo.api.CommandSessionListener;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.threadio.ThreadIO;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+public class SecuredCommandProcessorImplTest {
+    @Test
+    public void testCommandProcessor() throws Exception {
+        ThreadIO tio = EasyMock.createMock(ThreadIO.class);
+        EasyMock.replay(tio);
+
+        @SuppressWarnings("unchecked")
+        ServiceReference<ThreadIO> tioRef = EasyMock.createMock(ServiceReference.class);
+        EasyMock.replay(tioRef);
+
+        final BundleContext bc = EasyMock.createMock(BundleContext.class);
+        EasyMock.expect(bc.getServiceReference(ThreadIO.class)).andReturn(tioRef).anyTimes();
+        EasyMock.expect(bc.getService(tioRef)).andReturn(tio).anyTimes();
+        EasyMock.expect(bc.createFilter(EasyMock.isA(String.class))).andAnswer(new IAnswer<Filter>() {
+            @Override
+            public Filter answer() throws Throwable {
+                return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        EasyMock.expect(bc.getServiceReferences((String) EasyMock.anyObject(), (String) EasyMock.anyObject())).andReturn(null).anyTimes();
+
+        // Capture the listeners
+        final Map<String, ServiceListener> listeners = new HashMap<String, ServiceListener>();
+
+        // Here are the expected calls
+        final String commandFilter = "(&(osgi.command.scope=*)(osgi.command.function=*)" +
+                "(|(org.apache.karaf.service.guard.roles=aaabbbccc)(!(org.apache.karaf.service.guard.roles=*))))";
+        expectServiceTracker(bc, commandFilter, listeners);
+        expectServiceTracker(bc, "(objectClass=" + Converter.class.getName() + ")", listeners);
+        expectServiceTracker(bc, "(objectClass=" + CommandSessionListener.class.getName() + ")", listeners);
+        EasyMock.replay(bc);
+
+        Subject subject = new Subject();
+        subject.getPrincipals().add(new RolePrincipal("aaabbbccc"));
+
+        Subject.doAs(subject, new PrivilegedAction<Object>() {
+            @Override
+            public Object run() {
+                MySecuredCommandProcessorImpl scp = new MySecuredCommandProcessorImpl(bc) {};
+
+                assertEquals(3, scp.getCommands().size());
+                assertTrue(scp.getCommands().contains("osgi:addcommand"));
+                assertTrue(scp.getCommands().contains("osgi:removecommand"));
+                assertTrue(scp.getCommands().contains("osgi:eval"));
+                assertEquals(1, scp.getConstants().size());
+                assertEquals(bc, scp.getConstants().get(".context"));
+
+                // Now let's make a command appear...
+                ServiceListener commandListener = listeners.get(commandFilter);
+
+                ServiceReference<?> cdRef = EasyMock.createMock(ServiceReference.class);
+                EasyMock.expect(cdRef.getProperty(CommandProcessor.COMMAND_SCOPE)).andReturn("foo");
+                EasyMock.expect(cdRef.getProperty(CommandProcessor.COMMAND_FUNCTION)).andReturn("bar");
+                EasyMock.replay(cdRef);
+
+                ServiceEvent event = new ServiceEvent(ServiceEvent.REGISTERED, cdRef);
+                commandListener.serviceChanged(event);
+                assertEquals(4, scp.getCommands().size());
+                assertTrue(scp.getCommands().contains("foo:bar"));
+
+                ServiceReference<?> cd2Ref = EasyMock.createMock(ServiceReference.class);
+                EasyMock.expect(cd2Ref.getProperty(CommandProcessor.COMMAND_SCOPE)).andReturn("xxx");
+                EasyMock.expect(cd2Ref.getProperty(CommandProcessor.COMMAND_FUNCTION)).andReturn(
+                        new String[] {"aaa", "bbb"});
+                EasyMock.replay(cd2Ref);
+
+                ServiceEvent event2 = new ServiceEvent(ServiceEvent.REGISTERED, cd2Ref);
+                commandListener.serviceChanged(event2);
+                assertEquals(6, scp.getCommands().size());
+                assertTrue(scp.getCommands().contains("xxx:aaa"));
+                assertTrue(scp.getCommands().contains("xxx:bbb"));
+
+                return null;
+            }
+        });
+    }
+
+    void expectServiceTracker(final BundleContext bc, final String expectedFilter, final Map<String, ServiceListener> listeners) throws InvalidSyntaxException {
+        bc.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.eq(expectedFilter));
+        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
+            @Override
+            public Object answer() throws Throwable {
+                listeners.put(expectedFilter, (ServiceListener) EasyMock.getCurrentArguments()[0]);
+                return null;
+            }
+        }).once();
+    }
+
+    // Subclass to provide access to some protected members
+    static class MySecuredCommandProcessorImpl extends SecuredCommandProcessorImpl {
+        public MySecuredCommandProcessorImpl(BundleContext bc) {
+            super(bc);
+        }
+
+        Map<String, Object> getConstants() {
+            return constants;
+        }
+    };
+}