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 [2/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/main/java/org/apache/karaf/service/guard/tools/ACLConfigurationParser.java
URL: http://svn.apache.org/viewvc/karaf/trunk/service/guard/src/main/java/org/apache/karaf/service/guard/tools/ACLConfigurationParser.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/service/guard/src/main/java/org/apache/karaf/service/guard/tools/ACLConfigurationParser.java (added)
+++ karaf/trunk/service/guard/src/main/java/org/apache/karaf/service/guard/tools/ACLConfigurationParser.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,334 @@
+/*
+ * 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.tools;
+
+import java.util.*;
+
+public class ACLConfigurationParser {
+
+    // note that the order of the enums is important. Needs to be from most specific to least specific.
+    public enum Specificity {
+        ARGUMENT_MATCH,
+        SIGNATURE_MATCH,
+        NAME_MATCH,
+        WILDCARD_MATCH,
+        NO_MATCH
+    };
+
+    /**
+     * Returns the roles that can invoke the given operation. This is determined by matching the
+     * operation details against configuration provided.<p/>
+     *
+     * The following configuration is supported. Keys are used to match an invocation against. The value can contain
+     * a comma-separated list of roles. Spaces are ignored for the role values. Note that comments are allowed in the
+     * value field after the hash {@code #} character:
+     * <pre>
+     *     {@code
+     *     myMethod = role1, role2
+     *     methodName(int)[/17/] = role1                    # regex match, assume it's surrounded by ^ and $
+     *     methodName(int)[/[01]8/] = role2
+     *     methodName(int)["19"] = role3                    # exact value match
+     *     methodName(int) = role4                          # signature match
+     *     methodName(java.lang.String, int) = role5        # signature match
+     *     methodName =                                     # no roles can invoke this command
+     *     method* = role6                                  # name prefix/wildcard match. The asterisk must appear at the end.
+     *     }
+     * </pre>
+     *
+     * The following algorithm is used to find matching roles:
+     * <ol>
+     *     <li>Find all regex and exact value matches. For all parameters these matches are found by calling {@code toString()}
+     *         on the parameters passed in. If there are multiple matches in this category all the matching roles are collected.
+     *         If any is found return these roles.
+     *     </li>
+     *     <li>Find a signature match. If found return the associated roles.</li>
+     *     <li>Find a method name match. If found return the associated roles.</li>
+     *     <li>Find a method name prefix/wildcard match. If more than one prefix match, the roles associated with the longest
+     *         prefix is used. So for example, if there are rules for {@code get*} and {@code *} only the roles associated with
+     *         {@code get*} are returned.
+     *     </li>
+     *     <li>If none of the above criteria match, this method returns {@code null}.</li>
+     * </ol>
+     *
+     * @param methodName the method name to be invoked.
+     * @param params the parameters provided for the invocation. May be {@code null} for cases there the parameters are not yet
+     *               known. In this case the roles that can <em>potentially</em> invoke the method are returned, although based on
+     *               parameter values the actual invocation may still be denied.
+     * @param signature the signature of the method specified as an array of class name. For simple types, the simple type name
+     *                  is used (e.g. "int").
+     * @param config the configuration to check against.
+     * @param addToRoles the list of roles (which may be empty) if a matching configuration iteam has been found.
+     * @return the specificity
+     */
+    public static Specificity getRolesForInvocation(String methodName, Object[] params, String[] signature,
+                                                    Dictionary<String, Object> config, List<String> addToRoles) {
+        Dictionary<String, Object> properties = trimKeys(config);
+
+        Specificity s = getRolesBasedOnSignature(methodName, params, signature, properties, addToRoles);
+        if (s != Specificity.NO_MATCH) {
+            return s;
+        }
+
+        s = getRolesBasedOnSignature(methodName, params, null, properties, addToRoles);
+        if (s != Specificity.NO_MATCH) {
+            return s;
+        }
+
+        List<String> roles = getMethodNameWildcardRoles(properties, methodName);
+        if (roles != null) {
+            addToRoles.addAll(roles);
+            return Specificity.WILDCARD_MATCH;
+        } else {
+            return Specificity.NO_MATCH;
+        }
+    }
+
+    private static Specificity getRolesBasedOnSignature(String methodName, Object[] params, String[] signature,
+                                                        Dictionary<String, Object> properties, List<String> roles) {
+        if (params != null) {
+            boolean foundExactOrRegex = false;
+            Object exactArgMatchRoles = properties.get(getExactArgSignature(methodName, signature, params));
+            if (exactArgMatchRoles instanceof String) {
+                roles.addAll(parseRoles((String) exactArgMatchRoles));
+                foundExactOrRegex = true;
+            }
+
+            List<String> r = getRegexRoles(properties, methodName, signature, params);
+            if (r != null) {
+                foundExactOrRegex = true;
+                roles.addAll(r);
+            }
+
+            if (foundExactOrRegex) {
+                // since we have the actual parameters we can match them and if they do we won't look for any
+                // more generic rules...
+                return Specificity.ARGUMENT_MATCH;
+            }
+        } else {
+            // this is used in the case where parameters aren't known yet and the system wants to find out
+            // what roles in principle can invoke this method
+            List<String> r = getExactArgOrRegexRoles(properties, methodName, signature);
+            if (r != null) {
+               roles.addAll(r);
+            }
+        }
+
+        Object signatureRoles = properties.get(getSignature(methodName, signature));
+        if (signatureRoles instanceof String) {
+            roles.addAll(parseRoles((String) signatureRoles));
+            return signature == null ? Specificity.NAME_MATCH : Specificity.SIGNATURE_MATCH;
+        }
+
+        return Specificity.NO_MATCH;
+    }
+
+    private static Dictionary<String, Object> trimKeys(Dictionary<String, Object> properties) {
+        Dictionary<String, Object> d = new Hashtable<String, Object>();
+        for (Enumeration<String> e = properties.keys(); e.hasMoreElements(); ) {
+            String key = e.nextElement();
+            Object value = properties.get(key);
+            d.put(removeSpaces(key), value);
+        }
+        return d;
+    }
+
+    private static String removeSpaces(String key) {
+        StringBuilder sb = new StringBuilder();
+        char quoteChar = 0;
+        for (int i = 0; i < key.length(); i++) {
+            char c = key.charAt(i);
+
+            if (quoteChar == 0 && c == ' ')
+                continue;
+
+            if (quoteChar == 0 && (c == '\"' || c == '/') && sb.length() > 0 &&
+                    (sb.charAt(sb.length() - 1) == '[' || sb.charAt(sb.length() - 1) == ',')) {
+                // we're in a quoted string
+                quoteChar = c;
+            } else if (quoteChar != 0 && c == quoteChar) {
+                // look ahead to see if the next non-space is the closing bracket or a comma, which ends the quoted string
+                for (int j = i + 1; j < key.length(); j++) {
+                    if (key.charAt(j) == ' ')
+                        continue;
+                    if (key.charAt(j) == ']' || key.charAt(j) == ',')
+                        quoteChar = 0;
+                    break;
+                }
+            }
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    public static List<String> parseRoles(String roleStr) {
+        int hashIdx = roleStr.indexOf('#');
+        if (hashIdx >= 0) {
+            // you can put a comment at the end
+            roleStr = roleStr.substring(0, hashIdx);
+        }
+
+        List<String> roles = new ArrayList<String>();
+        for (String role : roleStr.split("[,]")) {
+            String trimmed = role.trim();
+            if (trimmed.length() > 0) {
+                roles.add(trimmed);
+            }
+        }
+
+        return roles;
+    }
+
+    private static Object getExactArgSignature(String methodName, String[] signature, Object[] params) {
+        StringBuilder sb = new StringBuilder(getSignature(methodName, signature));
+        sb.append('[');
+        boolean first = true;
+        for (Object param : params) {
+            if (first)
+                first = false;
+            else
+                sb.append(',');
+            sb.append('"');
+            sb.append(param.toString().trim());
+            sb.append('"');
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+
+    private static String getSignature(String methodName, String[] signature) {
+        StringBuilder sb = new StringBuilder(methodName);
+        if (signature == null)
+            return sb.toString();
+
+        sb.append('(');
+        boolean first = true;
+        for (String s : signature) {
+            if (first)
+                first = false;
+            else
+                sb.append(',');
+            sb.append(s);
+        }
+        sb.append(')');
+        return sb.toString();
+    }
+
+    private static List<String> getRegexRoles(Dictionary<String, Object> properties, String methodName, String[] signature, Object[] params) {
+        List<String> roles = new ArrayList<String>();
+        boolean matchFound = false;
+        String methodSig = getSignature(methodName, signature);
+        String prefix = methodSig + "[/";
+        for (Enumeration<String> e = properties.keys(); e.hasMoreElements(); ) {
+            String key = e.nextElement().trim();
+            if (key.startsWith(prefix) && key.endsWith("/]")) {
+                List<String> regexArgs = getRegexDecl(key.substring(methodSig.length()));
+                if (allParamsMatch(regexArgs, params)) {
+                    matchFound = true;
+                    Object roleStr = properties.get(key);
+                    if (roleStr instanceof String) {
+                        roles.addAll(parseRoles((String) roleStr));
+                    }
+                }
+            }
+        }
+        return matchFound ? roles : null;
+    }
+
+    private static List<String> getExactArgOrRegexRoles(Dictionary<String, Object> properties, String methodName, String[] signature) {
+        List<String> roles = new ArrayList<String>();
+        boolean matchFound = false;
+        String methodSig = getSignature(methodName, signature);
+        String prefix = methodSig + "[";
+        for (Enumeration<String> e = properties.keys(); e.hasMoreElements(); ) {
+            String key = e.nextElement().trim();
+            if (key.startsWith(prefix) && key.endsWith("]")) {
+                matchFound = true;
+                Object roleStr = properties.get(key);
+                if (roleStr instanceof String) {
+                    roles.addAll(parseRoles((String) roleStr));
+                }
+            }
+        }
+        return matchFound ? roles : null;
+    }
+
+    private static List<String> getMethodNameWildcardRoles(Dictionary<String, Object> properties, String methodName) {
+        SortedMap<String, String> wildcardRules = new TreeMap<String, String>(new Comparator<String>() {
+            public int compare(String s1, String s2) {
+                // returns longer entries before shorter ones...
+                return s2.length() - s1.length();
+            }
+        });
+        for (Enumeration<String> e = properties.keys(); e.hasMoreElements(); ) {
+            String key = e.nextElement();
+            if (key.endsWith("*")) {
+                String prefix = key.substring(0, key.length() - 1);
+                if (methodName.startsWith(prefix)) {
+                    wildcardRules.put(prefix, properties.get(key).toString());
+                }
+            }
+        }
+
+        if (wildcardRules.size() != 0) {
+            return parseRoles(wildcardRules.values().iterator().next());
+        } else {
+            return null;
+        }
+    }
+
+    private static boolean allParamsMatch(List<String> regexArgs, Object[] params) {
+        if (regexArgs.size() != params.length)
+            return false;
+
+        for (int i = 0; i < regexArgs.size(); i++) {
+            if (!params[i].toString().trim().matches(regexArgs.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static List<String> getRegexDecl(String key) {
+        List<String> l = new ArrayList<String>();
+
+        boolean inRegex = false;
+        StringBuilder curRegex = new StringBuilder();
+        for (int i = 0; i < key.length(); i++) {
+            if (!inRegex) {
+                if (key.length() > i + 1) {
+                    String s = key.substring(i, i + 2);
+                    if ("[/".equals(s) || ",/".equals(s)) {
+                        inRegex = true;
+                        i++;
+                        continue;
+                    }
+                }
+            } else {
+                String s = key.substring(i, i + 2);
+                if ("/]".equals(s) || "/,".equals(s)) {
+                    l.add(curRegex.toString());
+                    curRegex = new StringBuilder();
+                    inRegex = false;
+                    continue;
+                }
+                curRegex.append(key.charAt(i));
+            }
+        }
+        return l;
+    }
+}

Added: karaf/trunk/service/guard/src/main/resources/OSGI-INF/bundle.info
URL: http://svn.apache.org/viewvc/karaf/trunk/service/guard/src/main/resources/OSGI-INF/bundle.info?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/service/guard/src/main/resources/OSGI-INF/bundle.info (added)
+++ karaf/trunk/service/guard/src/main/resources/OSGI-INF/bundle.info Tue Oct 22 03:13:20 2013
@@ -0,0 +1,53 @@
+h1. Synopsis
+
+${project.name}
+
+${project.description}
+
+Maven URL:
+    [mvn:${project.groupId}/${project.artifactId}/${project.version}]
+
+h1. Description
+
+Add Role-based access to any OSGi Service
+
+Role-based access is added to existing OSGi services by hiding the
+original service from a clients through service registry hooks and
+presenting a proxy to the service that checks the required roles before
+an invocation is made.
+
+To enable the Role-based access for services, the service needs to match
+the filter in the karaf.secured.services system property (typically set
+in etc/system.properties), e.g:
+
+  karaf.secured.services = (|(objectClass=org.acme*)(foo=bar))
+
+Only services that match this filter have Role-based access applied,
+other services are left alone.
+
+Required roles are registered through the OSGi Configuration Admin
+Service. The service PID must start with org.apache.karaf.service.acl.
+For enabled services configurations with matching PIDs defining a
+service.guard property that matches are looked up.
+Then roles associated with the current method, its signature and/or the
+passed arguments are looked up. For example, take the following
+configuration:
+
+  service.guard = (objectClass=org.acme.MyServiceAPI)
+  myMethod = admin, manager
+
+A special role declaration of '*' means that role checking is disabled
+for this method.
+If a matching ACL is found, but no matching definition for the method
+being invoked can be found, no user can invoke the method.
+If no matching ACL is found, role checking is not enabled for the
+service.
+As with the role-based JMX access, method signatures and arguments
+(either literal or regex-based) can be matched.
+
+When roles are checked, these are obtained from the Subject associated
+with the current AccessControlContext. By default the Karaf
+RolePrincipal is checked for, but other role implementations can be
+supported through the following syntax:
+
+  my.custom.RoleImpl:someRole
\ No newline at end of file

Added: karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ACLConfigurationParserTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ACLConfigurationParserTest.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ACLConfigurationParserTest.java (added)
+++ karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ACLConfigurationParserTest.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,104 @@
+/*
+ * 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.tools;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.karaf.service.guard.tools.ACLConfigurationParser.Specificity;
+import org.junit.Test;
+
+public class ACLConfigurationParserTest {
+    @Test
+    public void testParseRoles() {
+        assertEquals(Arrays.asList("some_role"),
+                ACLConfigurationParser.parseRoles(" some_role   "));
+        assertEquals(Arrays.asList("a","b","C"),
+                ACLConfigurationParser.parseRoles("a,b,C"));
+        assertEquals(Collections.emptyList(),
+                ACLConfigurationParser.parseRoles("# test comment"));
+    }
+
+    @Test
+    public void testGetRolesForInvocation() {
+        Dictionary<String, Object> config = new Hashtable<String, Object>();
+        config.put("foo", "r1, r2");
+        config.put("bar(java.lang.String, int)[/aa/,/42/]", "ra");
+        config.put("bar(java.lang.String, int)[/bb/,/42/]", "rb");
+        config.put("bar(java.lang.String, int)[\"cc\", \"17\"]", "rc");
+        config.put("bar(java.lang.String, int)", "rd");
+        config.put("bar(java.lang.String)", "re");
+        config.put("bar", "rf");
+        config.put("ba*", "rg #Wildcard");
+
+        List<String> roles1 = new ArrayList<String>();
+        assertEquals(Specificity.NAME_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("foo", new Object [] {}, new String [] {}, config, roles1));
+        assertEquals(Arrays.asList("r1", "r2"), roles1);
+
+        List<String> roles2 = new ArrayList<String>();
+        assertEquals(Specificity.NAME_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("foo", new Object [] {"test"}, new String [] {"java.lang.String"}, config, roles2));
+        assertEquals(Arrays.asList("r1", "r2"), roles2);
+
+        List<String> roles3 = new ArrayList<String>();
+        assertEquals(Specificity.NO_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("test", new Object [] {}, new String [] {}, config, roles3));
+        assertEquals(0, roles3.size());
+
+        List<String> roles4 = new ArrayList<String>();
+        assertEquals(Specificity.ARGUMENT_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("bar", new Object [] {"aa", 42}, new String [] {"java.lang.String", "int"}, config, roles4));
+        assertEquals(Arrays.asList("ra"), roles4);
+
+        List<String> roles5 = new ArrayList<String>();
+        assertEquals(Specificity.ARGUMENT_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("bar", new Object [] {"bb", 42}, new String [] {"java.lang.String", "int"}, config, roles5));
+        assertEquals(Arrays.asList("rb"), roles5);
+
+        List<String> roles6 = new ArrayList<String>();
+        assertEquals(Specificity.ARGUMENT_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("bar", new Object [] {"cc", 17}, new String [] {"java.lang.String", "int"}, config, roles6));
+        assertEquals(Arrays.asList("rc"), roles6);
+
+        List<String> roles7 = new ArrayList<String>();
+        assertEquals(Specificity.SIGNATURE_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("bar", new Object [] {"aaa", 42}, new String [] {"java.lang.String", "int"}, config, roles7));
+        assertEquals(Arrays.asList("rd"), roles7);
+
+        List<String> roles8 = new ArrayList<String>();
+        assertEquals(Specificity.SIGNATURE_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("bar", new Object [] {"aa"}, new String [] {"java.lang.String"}, config, roles8));
+        assertEquals(Arrays.asList("re"), roles8);
+
+        List<String> roles9 = new ArrayList<String>();
+        assertEquals(Specificity.NAME_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("bar", new Object [] {42}, new String [] {"int"}, config, roles9));
+        assertEquals(Arrays.asList("rf"), roles9);
+
+        List<String> roles10 = new ArrayList<String>();
+        assertEquals(Specificity.WILDCARD_MATCH,
+                ACLConfigurationParser.getRolesForInvocation("barr", new Object [] {42}, new String [] {"int"}, config, roles10));
+        assertEquals(Arrays.asList("rg"), roles10);
+    }
+}

Added: karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ActivatorTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ActivatorTest.java?rev=1534467&view=auto
==============================================================================
--- karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ActivatorTest.java (added)
+++ karaf/trunk/service/guard/src/test/java/org/apache/karaf/service/guard/impl/ActivatorTest.java Tue Oct 22 03:13:20 2013
@@ -0,0 +1,128 @@
+/*
+ * 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.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+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.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.hooks.service.EventListenerHook;
+import org.osgi.framework.hooks.service.FindHook;
+
+import java.util.Dictionary;
+import java.util.Properties;
+
+public class ActivatorTest {
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testStartActivator() throws Exception {
+        // keep the old properties. Note that the Properties 'copy constructor' new Properties(props)
+        // doesn't actually copy, hence the awkward setup here...
+        Properties oldProps = new Properties();
+        oldProps.putAll(System.getProperties());
+
+        try {
+            System.setProperty(GuardProxyCatalog.KARAF_SECURED_SERVICES_SYSPROP, "(foo=bar)");
+            Bundle b = EasyMock.createMock(Bundle.class);
+            EasyMock.expect(b.getBundleId()).andReturn(768L).anyTimes();
+            EasyMock.replay(b);
+
+            BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+            EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+            EasyMock.expect(bc.createFilter(EasyMock.anyObject(String.class))).andAnswer(new IAnswer<Filter>() {
+                @Override
+                public Filter answer() throws Throwable {
+                    return FrameworkUtil.createFilter((String) EasyMock.getCurrentArguments()[0]);
+                }
+            }).anyTimes();
+
+            EasyMock.expect(bc.registerService(
+                    EasyMock.eq(EventListenerHook.class), EasyMock.isA(EventListenerHook.class), EasyMock.isNull(Dictionary.class)))
+                    .andReturn(null);
+            EasyMock.expect(bc.registerService(
+                    EasyMock.eq(FindHook.class), EasyMock.isA(FindHook.class), EasyMock.isNull(Dictionary.class)))
+                    .andReturn(null);
+
+            EasyMock.replay(bc);
+
+            Activator a = new Activator();
+            a.start(bc);
+
+            assertNotNull(a.guardProxyCatalog);
+            assertNotNull(a.guardingEventHook);
+            assertNotNull(a.guardingFindHook);
+
+            EasyMock.verify(bc);
+        } finally {
+            System.setProperties(oldProps);
+        }
+    }
+
+    @Test
+    public void testStartActivatorNoServicesSecured() throws Exception {
+        // keep the old properties. Note that the Properties 'copy constructor' new Properties(props)
+        // doesn't actually copy, hence the awkward setup here...
+        Properties oldProps = new Properties();
+        oldProps.putAll(System.getProperties());
+
+        try {
+            Properties newProps = removeProperties(System.getProperties(), GuardProxyCatalog.KARAF_SECURED_SERVICES_SYSPROP);
+            System.setProperties(newProps);
+
+            BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+            EasyMock.replay(bc);
+
+            Activator a = new Activator();
+            a.start(bc);
+
+            assertNull(a.guardProxyCatalog);
+        } finally {
+            System.setProperties(oldProps);
+        }
+    }
+
+    @Test
+    public void testStopActivator() throws Exception {
+        Activator a = new Activator();
+
+        a.guardProxyCatalog = EasyMock.createMock(GuardProxyCatalog.class);
+        a.guardProxyCatalog.close();
+        EasyMock.expectLastCall().once();
+        EasyMock.replay(a.guardProxyCatalog);
+
+        a.stop(EasyMock.createMock(BundleContext.class));
+
+        EasyMock.verify(a.guardProxyCatalog);
+    }
+
+    private Properties removeProperties(Properties props, String ... keys) {
+        Properties p = new Properties();
+        p.putAll(props);
+        for (String key : keys) {
+            p.remove(key);
+        }
+        return p;
+    }
+
+}