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;
+ }
+ };
+}