Skip to content

Commit 0633005

Browse files
committed
Add validator for embedded bundles
This closes #5
1 parent a9966f9 commit 0633005

File tree

10 files changed

+246
-1
lines changed

10 files changed

+246
-1
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ There are several validators included in this artifact, all relate to namespacin
1919
1. [OSGi Configuration][osgi-installer-configurations]
2020
1. [Sling Resource Type and Resource Super Type][sling-resource-type] (`sling:resourceType` and `sling:resourceSuperType` properties)
2121
1. [AEM Client Library][aem-clientlibrary] (`categories` property)
22+
1. [Embedded Bundles][embedded] (the `Bundle-SymbolicName` of embedded bundles)
2223

2324
Namespacing has been explicitly mentioned in [Achim Koch's Blog: Hosting Multiple Tenants on AEM](https://blog.developer.adobe.com/hosting-multiple-tenants-on-aem-815c8ed0c9f9) but obviously namespacing is just one of multiple aspects to consider for multi-tenant AEM environments.
2425

@@ -48,6 +49,7 @@ Validator ID | Option | Description
4849
`netcentric-resourcetype-namespace` | `allowedTypePatterns` | Comma-separated list of regular expression patterns. Each `sling:resourceType` property of arbitrary JCR nodes must match at least one of the given patterns.
4950
`netcentric-resourcetype-namespace` | `allowedSuperTypePatterns` | Comma-separated list of regular expression patterns. Each `sling:resourceSuperType` property of arbitrary JCR nodes must match at least one of the given patterns.
5051
`netcentric-clientlibrary-namespace` | `allowedCategoryPatterns` | Comma-separated list of regular expression patterns. Each [client library's `categories` value][aem-clientlibrary] must match at least one of the given patterns.
52+
`netcentric-embedded-namespace` | `allowedBundleSymbolicNamePatterns` | Comma-separated list of regular expression patterns. Each embedded bundle in the package must have a `Bundle-SymbolicName` in its manifest which matches at least one of the given patterns.
5153

5254
*Due to the use of comma-separated strings it is not possible to use a comma within the regular expressions. However, as those are matched against names/paths (which don't allow a comma anyhow) using the comma inside the regular expressions shouldn't be necessary anyhow.*
5355

@@ -131,4 +133,5 @@ Adobe, and AEM are either registered trademarks or trademarks of Adobe in the Un
131133
[filevault-package-id]: https://jackrabbit.apache.org/filevault/properties.html
132134
[sling-resource-type]: https://sling.apache.org/documentation/the-sling-engine/resources.html#resource-types
133135
[oak-authorizables]: https://jackrabbit.apache.org/oak/docs/security/user/default.html#representation-in-the-repository
136+
[embedded]: https://jackrabbit.apache.org/filevault-package-maven-plugin/osgi.html#bundles-and-configurations
134137

src/it/inside-namespace/container-package/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
<artifactId>filevault-package-maven-plugin</artifactId>
1717
<configuration>
1818
<packageType>container</packageType>
19+
<embeddeds>
20+
<embedded>
21+
<artifactId>commons-lang3</artifactId>
22+
<filter>true</filter>
23+
</embedded>
24+
</embeddeds>
25+
<embeddedTarget>/apps/mytenant/install</embeddedTarget>
1926
</configuration>
2027
</plugin>
2128
</plugins>
2229
</build>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.apache.commons</groupId>
34+
<artifactId>commons-lang3</artifactId>
35+
<version>3.17.0</version>
36+
</dependency>
37+
</dependencies>
2338
</project>

src/it/inside-namespace/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@
9494
<allowedTypePatterns>/apps/mytenant2/components/.*</allowedTypePatterns>
9595
</options>
9696
</netcentric-resourcetype-namespace>
97+
<netcentric-embedded-namespace>
98+
<options>
99+
<allowedBundleSymbolicNamePatterns>org.apache.commons.lang3</allowedBundleSymbolicNamePatterns>
100+
</options>
101+
</netcentric-embedded-namespace>
97102
</validatorsSettings>
98103
</configuration>
99104
<dependencies>

src/it/no-config/container-package/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
<artifactId>filevault-package-maven-plugin</artifactId>
1717
<configuration>
1818
<packageType>container</packageType>
19+
<embeddeds>
20+
<embedded>
21+
<artifactId>commons-lang3</artifactId>
22+
<filter>true</filter>
23+
</embedded>
24+
</embeddeds>
25+
<embeddedTarget>/apps/mytenant/install</embeddedTarget>
1926
</configuration>
2027
</plugin>
2128
</plugins>
2229
</build>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.apache.commons</groupId>
34+
<artifactId>commons-lang3</artifactId>
35+
<version>3.17.0</version>
36+
</dependency>
37+
</dependencies>
2338
</project>

src/it/outside-namespace/container-package/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
<artifactId>filevault-package-maven-plugin</artifactId>
1717
<configuration>
1818
<packageType>container</packageType>
19+
<embeddeds>
20+
<embedded>
21+
<artifactId>commons-lang3</artifactId>
22+
<filter>true</filter>
23+
</embedded>
24+
</embeddeds>
25+
<embeddedTarget>/apps/mytenant/install</embeddedTarget>
1926
</configuration>
2027
</plugin>
2128
</plugins>
2229
</build>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.apache.commons</groupId>
34+
<artifactId>commons-lang3</artifactId>
35+
<version>3.17.0</version>
36+
</dependency>
37+
</dependencies>
2338
</project>

src/it/outside-namespace/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@
100100
<allowedTypePatterns>/apps/mytenant2/components/.*</allowedTypePatterns>
101101
</options>
102102
</netcentric-resourcetype-namespace>
103+
<netcentric-embedded-namespace>
104+
<options>
105+
<allowedBundleSymbolicNamePatterns>some-unused-prefix</allowedBundleSymbolicNamePatterns>
106+
</options>
107+
</netcentric-embedded-namespace>
103108
</validatorsSettings>
104109
</configuration>
105110
<dependencies>

src/it/outside-namespace/verify.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ assert buildLog.contains("""[ERROR] ValidationViolation: Filter root '/home/user
2323

2424
// container-package
2525
assert buildLog.contains("""[ERROR] ValidationViolation: Filter root '/apps/mytenant/config' is not allowed (does not match any of the allowed patterns [/apps/mytenant2(/.*)?,/conf/mytenant2(/.*)?,/home/users/mytenant2(/.*)?,/oak:index/mytenant2-(.*)]) @ META-INF${File.separator}vault${File.separator}filter.xml, validator: netcentric-filter-namespace
26+
[ERROR] ValidationViolation: Filter root '/apps/mytenant/install/commons-lang3-3.17.0.jar' is not allowed (does not match any of the allowed patterns [/apps/mytenant2(/.*)?,/conf/mytenant2(/.*)?,/home/users/mytenant2(/.*)?,/oak:index/mytenant2-(.*)]) @ META-INF${File.separator}vault${File.separator}filter.xml, validator: netcentric-filter-namespace
2627
[ERROR] ValidationViolation: Package group 'biz.netcentric.filevault.validator.aem.namespace.it' is not allowed (does not match any of the group patterns [invalid-group]) @ META-INF${File.separator}vault${File.separator}properties.xml, validator: netcentric-packageid-namespace
2728
[ERROR] ValidationViolation: Package name 'container-package' is not allowed (does not match any of the name patterns [invalid-name]) @ META-INF${File.separator}vault${File.separator}properties.xml, validator: netcentric-packageid-namespace
2829
[ERROR] ValidationViolation: OSGi configuration PID 'com.example.mytenant.MyComponent2' is not allowed to be configured (does not match any of the allowed patterns [com\\.example\\.mytenant2\\..*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent2.cfg.json, validator: jackrabbit-osgiconfigparser
2930
[ERROR] ValidationViolation: OSGi configuration PID 'com.example.mytenant.MyComponent' is not allowed to be configured (does not match any of the allowed patterns [com\\.example\\.mytenant2\\..*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent~name.cfg.json, validator: jackrabbit-osgiconfigparser
30-
[ERROR] ValidationViolation: OSGi factory configuration PID 'com.example.mytenant.MyComponent' is not allowed with the given subname 'name' (does not match any of the allowed patterns [othername.*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent~name.cfg.json, validator: jackrabbit-osgiconfigparser""") : 'container-package'
31+
[ERROR] ValidationViolation: OSGi factory configuration PID 'com.example.mytenant.MyComponent' is not allowed with the given subname 'name' (does not match any of the allowed patterns [othername.*]) @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}config${File.separator}com.example.mytenant.MyComponent~name.cfg.json, validator: jackrabbit-osgiconfigparser
32+
[ERROR] ValidationViolation: Bundle-SymbolicName 'org.apache.commons.lang3' does not match any of the allowed patterns [some-unused-prefix] @ jcr_root${File.separator}apps${File.separator}mytenant${File.separator}install${File.separator}commons-lang3-3.17.0.jar, validator: netcentric-embedded-namespace""") : 'container-package'
3133

3234
return true
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*-
2+
* #%L
3+
* AEM FileVault Content Package Namespace Validators
4+
* %%
5+
* Copyright (C) 2024 Cognizant Netcentric
6+
* %%
7+
* All rights reserved. This program and the accompanying materials are made available under the terms of the
8+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
9+
* https://www.eclipse.org/legal/epl-v20.html
10+
* SPDX-License-Identifier: EPL-2.0
11+
* #L%
12+
*/
13+
package biz.netcentric.filevault.validator.aem.namespace;
14+
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.nio.file.Path;
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.Map;
21+
import java.util.Set;
22+
import java.util.Spliterators;
23+
import java.util.jar.JarInputStream;
24+
import java.util.jar.Manifest;
25+
import java.util.regex.Pattern;
26+
import java.util.stream.Collectors;
27+
import java.util.stream.StreamSupport;
28+
29+
import org.apache.jackrabbit.vault.validation.spi.GenericJcrDataValidator;
30+
import org.apache.jackrabbit.vault.validation.spi.ValidationMessage;
31+
import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
32+
import org.jetbrains.annotations.NotNull;
33+
import org.jetbrains.annotations.Nullable;
34+
35+
public class EmbeddedNamespaceValidator implements GenericJcrDataValidator {
36+
37+
private final ValidationMessageSeverity severity;
38+
private final Set<Pattern> allowedBundleSymbolicNamePatterns;
39+
40+
public EmbeddedNamespaceValidator(
41+
ValidationMessageSeverity severity, Set<Pattern> allowedBundleSymbolicNamePatterns) {
42+
super();
43+
this.severity = severity;
44+
this.allowedBundleSymbolicNamePatterns = allowedBundleSymbolicNamePatterns;
45+
}
46+
47+
@Override
48+
public @Nullable Collection<ValidationMessage> validateJcrData(
49+
@NotNull InputStream input,
50+
@NotNull Path filePath,
51+
@NotNull Path basePath,
52+
@NotNull Map<String, Integer> nodePathsAndLineNumbers)
53+
throws IOException {
54+
try (JarInputStream jarInputStream = new JarInputStream(input)) {
55+
String bundleSymbolicName = getBundleSymbolicName(jarInputStream.getManifest());
56+
if (bundleSymbolicName == null) {
57+
return Collections.singleton(new ValidationMessage(
58+
ValidationMessageSeverity.WARN,
59+
"Either no manifest or no Bundle-SymbolicName header found in manifest. Skip evaluation!"));
60+
}
61+
if (allowedBundleSymbolicNamePatterns.stream()
62+
.noneMatch(pattern -> pattern.matcher(bundleSymbolicName).matches())) {
63+
return Collections.singleton(new ValidationMessage(
64+
severity,
65+
String.format(
66+
"Bundle-SymbolicName '%s' does not match any of the allowed patterns [%s]",
67+
bundleSymbolicName,
68+
allowedBundleSymbolicNamePatterns.stream()
69+
.map(Pattern::pattern)
70+
.collect(Collectors.joining(",")))));
71+
}
72+
}
73+
return null;
74+
}
75+
76+
String getBundleSymbolicName(Manifest manifest) {
77+
if (manifest == null) {
78+
return null;
79+
}
80+
return manifest.getMainAttributes().getValue("Bundle-SymbolicName");
81+
}
82+
83+
@Override
84+
public boolean shouldValidateJcrData(@NotNull Path filePath, @NotNull Path basePath) {
85+
return isEmbeddedBundle(filePath);
86+
}
87+
88+
static boolean isEmbeddedBundle(@NotNull Path filePath) {
89+
if (!filePath.getName(0).toString().equals("apps")) {
90+
return false;
91+
}
92+
if (!filePath.getFileName().toString().endsWith(".jar")) {
93+
return false;
94+
}
95+
return StreamSupport.stream(Spliterators.spliterator(filePath.iterator(), filePath.getNameCount(), 0), false)
96+
.limit(5) // max depth
97+
.map(Path::getFileName)
98+
.map(Path::toString)
99+
.anyMatch(name -> name.startsWith("install.") || name.equals("install"));
100+
}
101+
102+
@Override
103+
public @Nullable Collection<ValidationMessage> done() {
104+
return null;
105+
}
106+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*-
2+
* #%L
3+
* AEM FileVault Content Package Namespace Validators
4+
* %%
5+
* Copyright (C) 2024 Cognizant Netcentric
6+
* %%
7+
* All rights reserved. This program and the accompanying materials are made available under the terms of the
8+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
9+
* https://www.eclipse.org/legal/epl-v20.html
10+
* SPDX-License-Identifier: EPL-2.0
11+
* #L%
12+
*/
13+
package biz.netcentric.filevault.validator.aem.namespace;
14+
15+
import java.util.Set;
16+
import java.util.regex.Pattern;
17+
18+
import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
19+
import org.apache.jackrabbit.vault.validation.spi.Validator;
20+
import org.apache.jackrabbit.vault.validation.spi.ValidatorFactory;
21+
import org.apache.jackrabbit.vault.validation.spi.ValidatorSettings;
22+
import org.jetbrains.annotations.NotNull;
23+
import org.kohsuke.MetaInfServices;
24+
25+
@MetaInfServices(ValidatorFactory.class)
26+
public class EmbeddedNamespaceValidatorFactory extends AbstractPatternSettingsValidatorFactory {
27+
28+
public EmbeddedNamespaceValidatorFactory() {
29+
super("netcentric-embedded-namespace", "allowedBundleSymbolicNamePatterns", false);
30+
}
31+
32+
@Override
33+
protected Validator createValidator(
34+
@NotNull Set<Pattern> patterns, @NotNull ValidationContext context, @NotNull ValidatorSettings settings) {
35+
return new EmbeddedNamespaceValidator(settings.getDefaultSeverity(), patterns);
36+
}
37+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*-
2+
* #%L
3+
* AEM FileVault Content Package Namespace Validators
4+
* %%
5+
* Copyright (C) 2024 Cognizant Netcentric
6+
* %%
7+
* All rights reserved. This program and the accompanying materials are made available under the terms of the
8+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
9+
* https://www.eclipse.org/legal/epl-v20.html
10+
* SPDX-License-Identifier: EPL-2.0
11+
* #L%
12+
*/
13+
package biz.netcentric.filevault.validator.aem.namespace;
14+
15+
import java.nio.file.Paths;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
import static org.junit.jupiter.api.Assertions.*;
20+
21+
class EmbeddedNamespaceValidatorTest {
22+
23+
@Test
24+
void testIsEmbeddedBundle() {
25+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(Paths.get("apps", "myapp", "install", "mybundle.jar")));
26+
// with run modes
27+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(
28+
Paths.get("apps", "myapp", "install.author", "mybundle.jar")));
29+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(
30+
Paths.get("apps", "myapp", "install.author.test", "mybundle.jar")));
31+
// outside /apps
32+
assertFalse(EmbeddedNamespaceValidator.isEmbeddedBundle(Paths.get("conf", "myapp", "install", "mybundle.jar")));
33+
// at max depth
34+
assertTrue(EmbeddedNamespaceValidator.isEmbeddedBundle(
35+
Paths.get("apps", "myapp", "child", "child", "install", "mybundle.jar")));
36+
// below max depth
37+
assertFalse(EmbeddedNamespaceValidator.isEmbeddedBundle(
38+
Paths.get("apps", "myapp", "child", "child", "child", "child", "install", "mybundle.jar")));
39+
// no jar
40+
assertFalse(EmbeddedNamespaceValidator.isEmbeddedBundle(Paths.get("apps", "myapp", "install", "mybundle.zip")));
41+
}
42+
}

0 commit comments

Comments
 (0)