This is an automated email from the ASF dual-hosted git repository. davidb pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-apiregions.git
commit f8b59082f5dc4bf4be75ba75c64b976317cb9ae8 Author: David Bosschaert <[email protected]> AuthorDate: Sat Nov 3 10:11:24 2018 +0000 Rename feature whitelist component to feature apiregions --- pom.xml | 125 ++++++++++++ .../sling/feature/apiregions/impl/Activator.java | 35 ++++ .../feature/apiregions/impl/RegionEnforcer.java | 134 +++++++++++++ .../feature/apiregions/impl/ResolverHookImpl.java | 186 ++++++++++++++++++ .../apiregions/impl/ResolverHookImplTest.java | 209 +++++++++++++++++++++ .../apiregions/impl/WhitelistEnforcerTest.java | 62 ++++++ .../impl/WhitelistServiceFactoryImplTest.java | 64 +++++++ .../apiregions/impl/WhitelistServiceImplTest.java | 52 +++++ 8 files changed, 867 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5602b63 --- /dev/null +++ b/pom.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> + <!-- + 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>34</version> + <relativePath /> + </parent> + + <artifactId>org.apache.sling.feature.apiregions</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Apache Sling Feature API Regions Runtime</name> + <description> + A runtime component to enforce API Regions + </description> + + <properties> + <sling.java.version>8</sling.java.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <ExtensionBundle-Activator>org.apache.sling.feature.apiregions.impl.Activator</ExtensionBundle-Activator> + <Fragment-Host>system.bundle;extension:=framework</Fragment-Host> + </instructions> + + <!-- Skip baselining for 0.x version --> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.rat</groupId> + <artifactId>apache-rat-plugin</artifactId> + <configuration> + <excludes> + <exclude>*.md</exclude> + </excludes> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.feature.service</artifactId> + <version>0.0.1-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.annotation.versioning</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-json_1.1_spec</artifactId> + <version>1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.feature</artifactId> + <version>0.1.3-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.feature.launcher</artifactId> + <version>0.1.0-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + + + <!-- Testing --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>2.8.9</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.johnzon</groupId> + <artifactId>johnzon-core</artifactId> + <version>1.0.0</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java b/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java new file mode 100644 index 0000000..7d77d5e --- /dev/null +++ b/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java @@ -0,0 +1,35 @@ +/* + * 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.sling.feature.apiregions.impl; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; + +public class Activator implements BundleActivator { + @Override + public synchronized void start(BundleContext context) throws Exception { + RegionEnforcer enforcer = new RegionEnforcer(); + context.registerService(ResolverHookFactory.class, enforcer, null); + } + + @Override + public synchronized void stop(BundleContext context) throws Exception { + } +} diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/RegionEnforcer.java b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionEnforcer.java new file mode 100644 index 0000000..26807ef --- /dev/null +++ b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionEnforcer.java @@ -0,0 +1,134 @@ +/* + * 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.sling.feature.apiregions.impl; + +import org.osgi.framework.Version; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; +import org.osgi.framework.wiring.BundleRevision; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +class RegionEnforcer implements ResolverHookFactory { + private static final String idbsnverFileName = "idbsnver.properties"; + private static final String bundleFeatureFileName = "bundles.properties"; + private static final String regionPackageFileName = "regions.properties"; + private static final String featureRegionFileName = "features.properties"; + + private final Map<Map.Entry<String, Version>, List<String>> bsnVerMap; + private final Map<String, Set<String>> bundleFeatureMap; + private final Map<String, Set<String>> featureRegionMap; + private final Map<String, Set<String>> regionPackageMap; + + public RegionEnforcer() throws IOException { + bsnVerMap = populateBSNVerMap(); + bundleFeatureMap = populateBundleFeatureMap(); + featureRegionMap = populateFeatureRegionMap(); + regionPackageMap = populateRegionPackageMap(); + } + + private Map<Map.Entry<String, Version>, List<String>> populateBSNVerMap() throws IOException { + File idbsnverFile = getDataFile(idbsnverFileName); + if (idbsnverFile != null && idbsnverFile.exists()) { + Map<Map.Entry<String, Version>, List<String>> m = new HashMap<>(); + + Properties p = new Properties(); + try (InputStream is = new FileInputStream(idbsnverFile)) { + p.load(is); + } + + for (String n : p.stringPropertyNames()) { + String[] bsnver = p.getProperty(n).split("~"); + Map.Entry<String, Version> key = new AbstractMap.SimpleEntry<String, Version>(bsnver[0], Version.valueOf(bsnver[1])); + List<String> l = m.get(key); + if (l == null) { + l = new ArrayList<>(); + m.put(key, l); + } + } + + Map<Map.Entry<String, Version>, List<String>> m2 = new HashMap<>(); + + for (Map.Entry<Map.Entry<String, Version>, List<String>> entry : m.entrySet()) { + m2.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); + } + + return Collections.unmodifiableMap(m2); + } else { + return Collections.emptyMap(); + } + } + + private Map<String, Set<String>> populateBundleFeatureMap() throws IOException { + return loadMap(bundleFeatureFileName); + } + + private Map<String, Set<String>> populateFeatureRegionMap() throws IOException { + return loadMap(featureRegionFileName); + } + + private Map<String, Set<String>> populateRegionPackageMap() throws IOException { + return loadMap(regionPackageFileName); + } + + private Map<String, Set<String>> loadMap(String fileName) throws IOException { + Map<String, Set<String>> m = new HashMap<>(); + + File propsFile = getDataFile(fileName); + if (propsFile != null && propsFile.exists()) { + Properties p = new Properties(); + try (InputStream is = new FileInputStream(propsFile)) { + p.load(is); + } + + for (String n : p.stringPropertyNames()) { + String[] features = p.getProperty(n).split(","); + m.put(n, Collections.unmodifiableSet(new HashSet<>(Arrays.asList(features)))); + } + } + + return Collections.unmodifiableMap(m); + } + + private File getDataFile(String name) throws IOException { + String fn = System.getProperty("whitelisting." + name); + if (fn == null) + return null; + return new File(fn); + } + + @Override + public ResolverHook begin(Collection<BundleRevision> triggers) { + return new ResolverHookImpl(bsnVerMap, bundleFeatureMap, featureRegionMap, regionPackageMap); + } +} diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java b/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java new file mode 100644 index 0000000..5642d47 --- /dev/null +++ b/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java @@ -0,0 +1,186 @@ +/* + * 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.sling.feature.apiregions.impl; + +import org.osgi.framework.Bundle; +import org.osgi.framework.Version; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRequirement; +import org.osgi.framework.wiring.BundleRevision; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +class ResolverHookImpl implements ResolverHook { + private static final Logger LOG = Logger.getLogger(ResolverHookImpl.class.getName()); + + private final Map<Map.Entry<String, Version>, List<String>> bsnVerMap; + private final Map<String, Set<String>> bundleFeatureMap; + private final Map<String, Set<String>> featureRegionMap; + private final Map<String, Set<String>> regionPackageMap; + + public ResolverHookImpl(Map<Entry<String, Version>, List<String>> bsnVerMap, Map<String, Set<String>> bundleFeatureMap, + Map<String, Set<String>> featureRegionMap, Map<String, Set<String>> regionPackageMap) { + this.bsnVerMap = bsnVerMap; + this.bundleFeatureMap = bundleFeatureMap; + this.featureRegionMap = featureRegionMap; + this.regionPackageMap = regionPackageMap; + } + + @Override + public void filterResolvable(Collection<BundleRevision> candidates) { + // Nothing to do + } + + @Override + public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) { + // Nothing to do + } + + @Override + public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) { + // Filtering is only on package resolution. Any other kind of resolution is not limited + if (!PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())) + return; + + System.out.println("*** Filter Matches: " + requirement); + Bundle reqBundle = requirement.getRevision().getBundle(); + long reqBundleID = reqBundle.getBundleId(); + String reqBundleName = reqBundle.getSymbolicName(); + Version reqBundleVersion = reqBundle.getVersion(); + + List<String> aids = bsnVerMap.get(new AbstractMap.SimpleEntry<String, Version>(reqBundleName, reqBundleVersion)); + if (aids == null) + return; // TODO what to do? + List<String> reqFeatures = new ArrayList<>(); + for (String aid : aids) { + Set<String> fid = bundleFeatureMap.get(aid); + if (fid != null) + reqFeatures.addAll(fid); + } + + Set<String> regions = new HashSet<>(); + for (String feature : reqFeatures) { + Set<String> fr = featureRegionMap.get(feature); + if (fr != null) { + regions.addAll(fr); + } + } + + Set<BundleCapability> coveredCaps = new HashSet<>(); + + nextCapability: + for (BundleCapability bc : candidates) { + BundleRevision rev = bc.getRevision(); + + Bundle capBundle = rev.getBundle(); + long capBundleID = capBundle.getBundleId(); + if (capBundleID == 0) { + // always allow capability from the system bundle + coveredCaps.add(bc); + continue nextCapability; + } + + if (capBundleID == reqBundleID) { + // always allow capability from same bundle + coveredCaps.add(bc); + continue nextCapability; + } + + String capBundleName = capBundle.getSymbolicName(); + Version capBundleVersion = capBundle.getVersion(); + + List<String> capBundleArtifacts = bsnVerMap.get(new AbstractMap.SimpleEntry<String, Version>(capBundleName, capBundleVersion)); + if (capBundleArtifacts == null) + return; // TODO what to do? + List<String> capFeatures = new ArrayList<>(); + for (String ba : capBundleArtifacts) { + Set<String> capfeats = bundleFeatureMap.get(ba); + if (capfeats != null) + capFeatures.addAll(capfeats); + } + + if (capFeatures.isEmpty()) + capFeatures = Collections.singletonList(null); + + for (String capFeat : capFeatures) { + if (capFeat == null) { + // always allow capability not coming from a feature + coveredCaps.add(bc); + continue nextCapability; + } + + if (reqFeatures.contains(capFeat)) { + // Within a single feature everything can wire to everything else + coveredCaps.add(bc); + continue nextCapability; + } + + if (featureRegionMap.get(capFeat) == null) { + // If the feature hosting the capability has no regions defined, everyone can access + coveredCaps.add(bc); + continue nextCapability; + } + + Object pkg = bc.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); + if (pkg instanceof String) { + String packageName = (String) pkg; + + Set<String> globalPackages = regionPackageMap.get("global"); + if (globalPackages != null && globalPackages.contains(packageName)) { + // If the export is in the global region everyone can access + coveredCaps.add(bc); + continue nextCapability; + } + + for (String region : regions) { + Set<String> regionPackages = regionPackageMap.get(region); + if (regionPackages != null && regionPackages.contains(packageName)) { + // If the export is in a region that the feature is also in, then allow + coveredCaps.add(bc); + continue nextCapability; + } + } + } + } + } + + // Remove any capabilities that are not covered + if (candidates.retainAll(coveredCaps)) { + LOG.log(Level.INFO, + "Removed one ore more candidates for requirement {0} as they are not in the correct region", requirement); + } + } + + @Override + public void end() { + // Nothing to do + } +} diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java new file mode 100644 index 0000000..5bc60a1 --- /dev/null +++ b/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java @@ -0,0 +1,209 @@ +/* + * 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.sling.feature.apiregions.impl; + +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.osgi.framework.Bundle; +import org.osgi.framework.Version; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRequirement; +import org.osgi.framework.wiring.BundleRevision; + +import java.util.Collections; +import java.util.Map; + +public class ResolverHookImplTest { + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test @Ignore + public void testFilterMatches() throws Exception { + String f = "gid:aid:0.0.9"; + String f2 = "gid2:aid2:1.0.0-SNAPSHOT"; + String f3 = "gid3:aid3:1.2.3"; + String f4 = "gid4:aid4:1.2.3"; + String f5 = "gid5:aid5:1.2.3"; + + /* + Features fs = Mockito.mock(Features.class); + Mockito.when(fs.getFeaturesForBundle("a.b.c", new Version(0,0,0))) + .thenReturn(Collections.singleton(f)); // b7 + Mockito.when(fs.getFeaturesForBundle("some.other.bundle", new Version(9,9,9,"suffix"))) + .thenReturn(Collections.singleton(f2)); // b9 + Mockito.when(fs.getFeaturesForBundle("a-bundle", new Version(1,0,0,"SNAPSHOT"))) + .thenReturn(Collections.singleton(f2)); // b10 + Mockito.when(fs.getFeaturesForBundle("a.b.c", new Version(1,2,3))) + .thenReturn(Collections.singleton(f3)); // b17 + Mockito.when(fs.getFeaturesForBundle("z.z.z", new Version(3,2,1))) + .thenReturn(new HashSet<>(Arrays.asList(f, f3))); // b18 + Mockito.when(fs.getFeaturesForBundle("x.y.z", new Version(9,9,9))) + .thenReturn(Collections.singleton(f3)); // b19 + Mockito.when(fs.getFeaturesForBundle("zzz", new Version(1,0,0))) + .thenReturn(Collections.singleton(f4)); // b20 + Mockito.when(fs.getFeaturesForBundle("www", new Version(1,0,0))) + .thenReturn(Collections.singleton(f5)); // b20 + + ServiceTracker st = Mockito.mock(ServiceTracker.class); + Mockito.when(st.waitForService(Mockito.anyLong())).thenReturn(fs); + + Map<String, Set<String>> rpm = new HashMap<>(); + rpm.put("r0", Collections.singleton("org.bar")); + rpm.put("r1", new HashSet<>(Arrays.asList("org.blah", "org.foo"))); + rpm.put(WhitelistService.GLOBAL_REGION, Collections.singleton("org.bar.tar")); + rpm.put("r3", Collections.singleton("xyz")); + + Map<String, Set<String>> frm = new HashMap<>(); + frm.put("gid:aid:0.0.9", + new HashSet<>(Arrays.asList("r1", "r2", WhitelistService.GLOBAL_REGION))); + frm.put("gid2:aid2:1.0.0-SNAPSHOT", Collections.singleton("r2")); + frm.put("gid3:aid3:1.2.3", Collections.singleton("r3")); + frm.put("gid4:aid4:1.2.3", Collections.singleton("r3")); + frm.put("gid5:aid5:1.2.3", Collections.emptySet()); + + WhitelistService wls = new WhitelistServiceImpl(rpm, frm); + ResolverHookImpl rh = new ResolverHookImpl(st, wls); + + // Check that we can get the capability from another bundle in the same region + // Bundle 7 is in feature f with regions r1, r2. Bundle 9 is in feature f2 with regions r2 + BundleRequirement req = mockRequirement(7, "a.b.c", new Version(0,0,0)); + BundleCapability bc1 = mockCapability("org.foo", 9, "some.other.bundle", new Version(9,9,9,"suffix")); + List<BundleCapability> candidates = new ArrayList<>(Arrays.asList(bc1)); + rh.filterMatches(req, candidates); + assertEquals(Collections.singletonList(bc1), candidates); + + // Check that we cannot get the capability from another bundle in a different region + // Bundle 9 is in feature f2 with region r2 + BundleRequirement req2 = mockRequirement(9, "some.other.bundle", new Version(9,9,9,"suffix")); + BundleCapability bc2 = mockCapability("org.bar", 17, "a.b.c", new Version(1,2,3)); + Collection<BundleCapability> c2 = new ArrayList<>(Arrays.asList(bc2)); + rh.filterMatches(req2, c2); + assertEquals(0, c2.size()); + + // Check that we can get the capability from the same bundle + BundleRequirement req3 = mockRequirement(9, "some.other.bundle", new Version(9,9,9,"suffix")); + BundleCapability bc3 = mockCapability("org.bar", 9, "some.other.bundle", new Version(9,9,9,"suffix")); + Collection<BundleCapability> c3 = new ArrayList<>(Arrays.asList(bc3)); + rh.filterMatches(req3, c3); + assertEquals(Collections.singletonList(bc3), c3); + + // Check that we can get the capability from the another bundle in the same feature + BundleRequirement req4 = mockRequirement(9, "some.other.bundle", new Version(9,9,9,"suffix")); + BundleCapability bc4 = mockCapability("org.bar", 10, "a-bundle", new Version(1,0,0,"SNAPSHOT")); + Collection<BundleCapability> c4 = new ArrayList<>(Arrays.asList(bc4)); + rh.filterMatches(req4, c4); + assertEquals(Collections.singletonList(bc4), c4); + + // Check that we can get the capability from another bundle where the capability + // is globally visible (from bundle 9, f2) + BundleRequirement req5 = mockRequirement(17, "a.b.c", new Version(1,2,3)); + BundleCapability bc5 = mockCapability("org.bar.tar", 9, "some.other.bundle", new Version(9,9,9,"suffix")); + Collection<BundleCapability> c5 = new ArrayList<>(Arrays.asList(bc5)); + rh.filterMatches(req5, c5); + assertEquals(Collections.singletonList(bc5), c5); + + // Check that we can get the capability from another bundle where the capability + // is globally visible (from bundle 7, f) + BundleRequirement req6 = mockRequirement(7, "a.b.c", new Version(0,0,0)); + BundleCapability bc6 = mockCapability("org.bar.tar", 17, "a.b.c", new Version(1,2,3)); + Collection<BundleCapability> c6 = new ArrayList<>(Arrays.asList(bc6)); + rh.filterMatches(req6, c6); + assertEquals(Collections.singletonList(bc6), c6); + + // Check that capabilities in non-package namespaces are ignored + BundleRequirement req7 = Mockito.mock(BundleRequirement.class); + Mockito.when(req7.getNamespace()).thenReturn("some.other.namespace"); + BundleCapability bc7 = mockCapability("org.bar", 17, "a.b.c", new Version(1,2,3)); + Collection<BundleCapability> c7 = new ArrayList<>(Arrays.asList(bc7)); + rh.filterMatches(req7, c7); + assertEquals(Collections.singletonList(bc7), c7); + + // Check that we can get the capability from another provider in the same region + BundleRequirement req8 = mockRequirement(20, "zzz", new Version(1,0,0)); + BundleCapability bc8 = mockCapability("xyz", 19, "x.y.z", new Version(9,9,9)); + Collection<BundleCapability> c8 = new ArrayList<>(Arrays.asList(bc8)); + rh.filterMatches(req8, c8); + + assertEquals(Collections.singletonList(bc8), c8); + // A requirement from a bundle that has no feature cannot access one in a region + BundleRequirement req9 = mockRequirement(11, "qqq", new Version(6,6,6)); + BundleCapability bc9 = mockCapability("org.bar", 17, "a.b.c", new Version(1,2,3)); + ArrayList c9 = new ArrayList<>(Arrays.asList(bc9)); + rh.filterMatches(req9, c9); + assertEquals(0, c9.size()); + + // A requirement from a bundle that has no feature can still access on in the global region + BundleRequirement req10 = mockRequirement(11, "qqq", new Version(6,6,6)); + BundleCapability bc10 = mockCapability("org.bar.tar", 18, "z.z.z", new Version(3,2,1)); + ArrayList c10 = new ArrayList<>(Arrays.asList(bc10)); + rh.filterMatches(req10, c10); + assertEquals(Collections.singletonList(bc10), c10); + + // A requirement from a bundle that has no feature can be satisfied by a capability + // from a bundle that has no feature + BundleRequirement req11 = mockRequirement(11, "qqq", new Version(6,6,6)); + BundleCapability bc11 = mockCapability("org.bar.tar", 20, "www", new Version(1,0,0)); + ArrayList c11 = new ArrayList<>(Arrays.asList(bc11)); + rh.filterMatches(req11, c11); + assertEquals(Collections.singletonList(bc11), c11); + + // A capability from the system bundle is always accessible + BundleRequirement req12 = mockRequirement(11, "qqq", new Version(6,6,6)); + BundleCapability bc12 = mockCapability("ping.pong", 0, "system.bundle", new Version(3,2,1)); + ArrayList c12 = new ArrayList<>(Arrays.asList(bc12)); + rh.filterMatches(req12, c12); + assertEquals(Collections.singletonList(bc12), c12); + */ + } + + private BundleCapability mockCapability(String pkg, long bundleID, String bsn, Version version) { + Map<String, Object> attrs = + Collections.singletonMap(PackageNamespace.PACKAGE_NAMESPACE, pkg); + + Bundle bundle = Mockito.mock(Bundle.class); + Mockito.when(bundle.getBundleId()).thenReturn(bundleID); + Mockito.when(bundle.getSymbolicName()).thenReturn(bsn); + Mockito.when(bundle.getVersion()).thenReturn(version); + + BundleRevision br = Mockito.mock(BundleRevision.class); + Mockito.when(br.getBundle()).thenReturn(bundle); + + + BundleCapability cap = Mockito.mock(BundleCapability.class); + Mockito.when(cap.getAttributes()).thenReturn(attrs); + Mockito.when(cap.getRevision()).thenReturn(br); + return cap; + } + + private BundleRequirement mockRequirement(long bundleID, String bsn, Version version) { + Bundle bundle = Mockito.mock(Bundle.class); + Mockito.when(bundle.getBundleId()).thenReturn(bundleID); + Mockito.when(bundle.getSymbolicName()).thenReturn(bsn); + Mockito.when(bundle.getVersion()).thenReturn(version); + + BundleRevision br = Mockito.mock(BundleRevision.class); + Mockito.when(br.getBundle()).thenReturn(bundle); + + BundleRequirement req = Mockito.mock(BundleRequirement.class); + Mockito.when(req.getNamespace()).thenReturn(PackageNamespace.PACKAGE_NAMESPACE); + Mockito.when(req.getRevision()).thenReturn(br); + + return req; + } +} diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistEnforcerTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistEnforcerTest.java new file mode 100644 index 0000000..e83c27f --- /dev/null +++ b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistEnforcerTest.java @@ -0,0 +1,62 @@ +/* + * 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.sling.feature.apiregions.impl; + +public class WhitelistEnforcerTest { + /* + @Test + public void testWhitelistEnforcerConfigUpdate() throws ConfigurationException { + BundleContext bc = Mockito.mock(BundleContext.class); + WhitelistEnforcer enf = new WhitelistEnforcer(bc, null); + + assertNull("Precondition", enf.whitelistService); + + Dictionary<String, Object> props = new Hashtable<>(); + props.put("ignored", "ignored-too"); + props.put("whitelist.region.region1", "org.foo.pkg1"); + props.put("whitelist.region.region2", new String[] {"pkga", "pkgb"}); + props.put("whitelist.region.region.3", Arrays.asList("a.b.c", "d.e.f", "g.h.i")); + props.put("whitelist.feature.gid:aid:slingfeature:testfeature:1.0.0", WhitelistService.GLOBAL_REGION); + props.put("whitelist.feature.gid:myfeature:1.0.0", new String [] {"region1", "region2"}); + enf.updated(props); + + assertNotNull(enf.whitelistService); + Mockito.verify(bc, Mockito.times(1)).registerService( + Mockito.eq(WhitelistService.class), Mockito.isA(WhitelistService.class), Mockito.any()); + + // check that the configuration parsing worked + assertTrue(enf.whitelistService.regionWhitelistsPackage("region1", "org.foo.pkg1")); + assertTrue(enf.whitelistService.regionWhitelistsPackage("region2", "pkga")); + assertTrue(enf.whitelistService.regionWhitelistsPackage("region2", "pkgb")); + assertFalse(enf.whitelistService.regionWhitelistsPackage("region1", "pkg1")); + assertTrue(enf.whitelistService.regionWhitelistsPackage("region.3", "d.e.f")); + assertFalse(enf.whitelistService.regionWhitelistsPackage("region.3", "d.e.f.g")); + assertNull(enf.whitelistService.regionWhitelistsPackage("unknown", "pkga")); + + Set<String> regions = enf.whitelistService.listRegions("gid:myfeature:1.0.0"); + assertEquals(new HashSet<String>(Arrays.asList("region1", "region2")), regions); + assertEquals(Collections.singleton("global"), enf.whitelistService + .listRegions("gid:aid:slingfeature:testfeature:1.0.0")); + assertNull(enf.whitelistService.listRegions("unknown")); + + enf.updated(null); + assertNull("A null configuration should put back the null whitelist service", enf.whitelistService); + } + */ +} diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceFactoryImplTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceFactoryImplTest.java new file mode 100644 index 0000000..f9120d3 --- /dev/null +++ b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceFactoryImplTest.java @@ -0,0 +1,64 @@ +/* + * 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.sling.feature.apiregions.impl; + +public class WhitelistServiceFactoryImplTest { + /* + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void testWhitelistServiceFactory() { + List<ResolverHookFactory> resolverHookFactory = new ArrayList<>(); + Map<String, Map<String, Set<String>>> wlsCfg = new HashMap<>(); + + ServiceTracker st = Mockito.mock(ServiceTracker.class); + BundleContext bc = Mockito.mock(BundleContext.class); + Mockito.when(bc.registerService(Mockito.isA(Class.class), Mockito.isA(Object.class), Mockito.isA(Dictionary.class))) + .then(i -> { resolverHookFactory.add(i.getArgument(1)); return null; }); + + WhitelistServiceFactory wsf = new WhitelistServiceFactoryImpl(bc, st) { + @Override + WhitelistService createWhitelistService(Map<String, Set<String>> packages, Map<String, Set<String>> regions) { + wlsCfg.put("packages", packages); + wlsCfg.put("regions", regions); + return super.createWhitelistService(packages, regions); + } + }; + + Map<String, Map<String, Set<String>>> m = new HashMap<>(); + Map<String, Set<String>> packages = new HashMap<>(); + packages.put("region1", new HashSet<>(Arrays.asList("org.foo", "org.bar"))); + packages.put("region2", Collections.singleton("org.foo.bar")); + m.put("packages", packages); + + Map<String, Set<String>> regions = new HashMap<>(); + regions.put("f1", new HashSet<String>(Arrays.asList("region1", "region3"))); + regions.put("f2", Collections.singleton("region2")); + regions.put("f3", Collections.singleton("region4")); + regions.put("f4", Collections.singleton("region2")); + m.put("regions", regions); + + wsf.initialize(m); + + assertEquals(wlsCfg, m); + + ResolverHookFactory rhf = resolverHookFactory.get(0); + assertTrue(rhf instanceof WhitelistEnforcer); + } + */ +} diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceImplTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceImplTest.java new file mode 100644 index 0000000..832e199 --- /dev/null +++ b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceImplTest.java @@ -0,0 +1,52 @@ +/* + * 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.sling.feature.apiregions.impl; + +import org.junit.Ignore; +import org.junit.Test; + +public class WhitelistServiceImplTest { + @Test + @Ignore + public void testListRegions() { + /* + Map<String, Set<String>> frm = new HashMap<>(); + frm.put("myfeature", new HashSet<>(Arrays.asList("rega", "regb", "regc"))); + frm.put("myotherfeature", Collections.emptySet()); + WhitelistService wls = new WhitelistServiceImpl(Collections.emptyMap(), frm); + + assertEquals(new HashSet<>(Arrays.asList("rega", "regb", "regc")), + wls.listRegions("myfeature")); + assertEquals(0, wls.listRegions("myotherfeature").size()); + assertNull(wls.listRegions("nonexistent")); + } + + @Test + public void testRegionContainsPackage() { + Map<String, Set<String>> rpm = new HashMap<>(); + rpm.put("region1", new HashSet<>(Arrays.asList("org.foo", "org.bar", "org.foo.bar"))); + WhitelistService wls = new WhitelistServiceImpl(rpm, Collections.emptyMap()); + + assertTrue(wls.regionWhitelistsPackage("region1", "org.foo")); + assertTrue(wls.regionWhitelistsPackage("region1", "org.foo.bar")); + assertFalse(wls.regionWhitelistsPackage("region1", "org.bar.foo")); + assertNull(wls.regionWhitelistsPackage("nonexitent", "org.foo")); + */ + } +}
