From 00da8680041ab2b6391ff55aaac071f22038e6dc Mon Sep 17 00:00:00 2001 From: Roy Teeuwen Date: Tue, 16 Dec 2025 09:21:23 +0100 Subject: [PATCH] solves #34: Allow to add paths that should not be reported when it contains properties that have violations --- aem-classification-validator/README.md | 17 +++--- aem-classification-validator/pom.xml | 8 ++- .../AemClassificationValidator.java | 52 ++++++++++++++++--- .../AemClassificationValidatorFactory.java | 39 +++++++++----- ...AemClassificationValidatorFactoryTest.java | 24 ++++++--- .../AemClassificationValidatorTest.java | 37 ++++++++----- 6 files changed, 126 insertions(+), 51 deletions(-) diff --git a/aem-classification-validator/README.md b/aem-classification-validator/README.md index 56dbee3..b5038c9 100644 --- a/aem-classification-validator/README.md +++ b/aem-classification-validator/README.md @@ -14,9 +14,10 @@ The following options are supported apart from the default settings mentioned in Option | Mandatory | Description --- | --- | --- -maps | yes | a comma-separated list of URLs specifying the source for a classification map. Each URL might use the protocols `file:`, for file-based classification maps, `http(s):` for classification maps in the internet or `tccl:` for classification maps being provided via the ThreadContextClassloader. The latter is especially useful with Maven as the TCCL during the execution of a goal of a Maven Plugin is the [Maven Plugin Classpath][4]. -whitelistedResourcePathPatterns | no | a comma-separated list of regular expressions matching an absolute resource path which should not be reported (no matter if its usage violates content classifications or not). The path is referring to the referenced/inherited/overlaid resource path (not the path containing the reference/supertype/overlay). -severitiesPerClassification | no | the severity per classification (this will overwrite the default severity which otherwise used for all classifications). The format is `={,=}`, where `classification` is one of `INTERNAL`, `INTERNAL_DEPRECATED_ANNOTATION`, `INTERNAL_DEPRECATED`, `FINAL` or `ABSTRACT` and `severity` is one of `DEBUG`, `INFO`, `WARN` or `ERROR`. + maps | yes | a comma-separated list of URLs specifying the source for a classification map. Each URL might use the protocols `file:`, for file-based classification maps, `http(s):` for classification maps in the internet or `tccl:` for classification maps being provided via the ThreadContextClassloader. The latter is especially useful with Maven as the TCCL during the execution of a goal of a Maven Plugin is the [Maven Plugin Classpath][4]. + whitelistedResourcePathPatterns | no | a comma-separated list of regular expressions matching an absolute resource path which should not be reported (no matter if its usage violates content classifications or not). The path is referring to the referenced/inherited/overlaid resource path (not the path containing the reference/supertype/overlay). + ignoreViolationsInPropertiesMatchingPathPatterns | no | a comma-separated list of regular expressions matching a path which should not be reported if it contains properties that have violations (no matter if its usage violates content classifications or not). Use this if you know there is an issue with classification for a specific component, but you don't want the problem to spread to other components. + severitiesPerClassification | no | the severity per classification (this will overwrite the default severity which otherwise used for all classifications). The format is `={,=}`, where `classification` is one of `INTERNAL`, `INTERNAL_DEPRECATED_ANNOTATION`, `INTERNAL_DEPRECATED`, `FINAL` or `ABSTRACT` and `severity` is one of `DEBUG`, `INFO`, `WARN` or `ERROR`. All validation messages are emitted with the [`defaultSeverity`][2] @@ -33,16 +34,16 @@ The file is a CSV serialization of the map where each line represents one item i ,(,) ``` -where `classification` is one of +where `classification` is one of 1. `INTERNAL` 2. `INTERNAL_DEPRECATED_ANNOTATION`, same restrictions as `INTERNAL` but due to being marked as deprecated via some annotation e.g. `cq:deprecated` property 3. `INTERNAL_DEPRECATED`, same restrictions as `INTERNAL` but due to being marked as deprecated in some external sources like release notes 4. `FINAL` 5. `ABSTRACT` -6. `PUBLIC` +6. `PUBLIC` -(in order from most restricted to least restricted). +(in order from most restricted to least restricted). The explanation for those can be found in the [Adobe documentation][1]. The CSV format is based on [RFC 4180][7]. In addition a comment starting with `#` on the first line is supposed to contain a label for the map (like the underlying AEM version). `path` is supposed to be an absolute JCR path of a specific node. @@ -94,8 +95,8 @@ There are several reasons: 1. You should detect violations as early as possible, preferably already in your CI pipeline. The later you detect those the more effort it is to fix. 2. If you don't care about content classifications - 1. there is a high chance that you cannot easily upgrade to a newer AEM version (AMS or on-premise) - 2. it might break with every new [AEM as a Cloud Service][5] release + 1. there is a high chance that you cannot easily upgrade to a newer AEM version (AMS or on-premise) + 2. it might break with every new [AEM as a Cloud Service][5] release [1]: https://docs.adobe.com/content/help/en/experience-manager-65/deploying/upgrading/sustainable-upgrades.html#content-classifications [2]: https://jackrabbit.apache.org/filevault/validation.html diff --git a/aem-classification-validator/pom.xml b/aem-classification-validator/pom.xml index f6ec196..42595d6 100644 --- a/aem-classification-validator/pom.xml +++ b/aem-classification-validator/pom.xml @@ -92,6 +92,12 @@ 2.2 test + + org.mockito + mockito-core + 5.20.0 + test + javax.jcr @@ -112,4 +118,4 @@ test - \ No newline at end of file + diff --git a/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidator.java b/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidator.java index 2372aa9..0dcb104 100644 --- a/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidator.java +++ b/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidator.java @@ -41,9 +41,13 @@ import org.apache.sling.jcr.resource.api.JcrResourceConstants; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class AemClassificationValidator implements DocumentViewXmlValidator, GenericJcrDataValidator, NodePathValidator { + private static final Logger LOGGER = LoggerFactory.getLogger(AemClassificationValidator.class); + /** * Example HTL code which should be matched by the RegEx: *
@@ -51,11 +55,11 @@ public class AemClassificationValidator implements DocumentViewXmlValidator, Gen *
* The first subgroup must contain the value of the resource type. * This pattern only works if the resource type is given as literal! - * + * * @see data-sly-resource */ static final Pattern HTL_INCLUDE_OVERWRITING_RESOURCE_TYPE = Pattern.compile("data-sly-resource\\s*=[^@]*.*?resourceType\\s*=\\s*(?:\"|\')([^'\"]*)(?:\"|\')"); - + /** * Example JSP code which should be matched by the RegEx: *
@@ -64,7 +68,7 @@ public class AemClassificationValidator implements DocumentViewXmlValidator, Gen *
* The first subgroup must contain the value of the resource type. * This pattern only works if the resource type is given as literal! - * + * * @see CQ/Sling Tag Library */ private static final Pattern JSP_INCLUDE_OVERWRITING_RESOURCE_TYPE = Pattern.compile("(?: whitelistedResourcePaths; + private final Collection ignoreViolationsInPropertiesMatchingPaths; private final Collection whitelistedResourcePathPatterns; + private final Collection ignoreViolationsInPropertiesMatchingPathPatterns; private final Map severityPerClassification; private @NotNull ValidationMessageSeverity defaultSeverity; private final Collection overlaidNodePaths; - public AemClassificationValidator(@NotNull ValidationMessageSeverity defaultSeverity, @NotNull ContentClassificationMap classificationMap, @NotNull Collection whitelistedResourcePaths, @NotNull Map severityPerClassification) { + public AemClassificationValidator(@NotNull ValidationMessageSeverity defaultSeverity, @NotNull ContentClassificationMap classificationMap, @NotNull Collection whitelistedResourcePaths, @NotNull Collection ignoreViolationsInPropertiesMatchingPaths, @NotNull Map severityPerClassification) { super(); this.defaultSeverity = defaultSeverity; this.classificationMap = classificationMap; this.whitelistedResourcePaths = whitelistedResourcePaths; this.whitelistedResourcePathPatterns = whitelistedResourcePaths.stream().map(Pattern::compile).collect(Collectors.toList()); + this.ignoreViolationsInPropertiesMatchingPaths = ignoreViolationsInPropertiesMatchingPaths; + this.ignoreViolationsInPropertiesMatchingPathPatterns = ignoreViolationsInPropertiesMatchingPaths.stream().map(Pattern::compile).collect(Collectors.toList()); this.severityPerClassification = severityPerClassification; this.overlaidNodePaths = new LinkedList<>(); } @@ -106,6 +114,11 @@ public Collection done() { @Override public Collection validate(@NotNull String path) { + if (isIgnoredViolationBasedOnPathPattern(path, ignoreViolationsInPropertiesMatchingPathPatterns)) { + LOGGER.debug("Path '{}' is explicitly whitelisted even if it contains violations and therefore has no restrictions!", path); + return null; + } + if (!overlaidNodePaths.contains(path)) { // check overlay usage in addition for non-docview files ValidationMessage message = validateClassification(path, ContentUsage.OVERLAY, MESSAGE_SUBJECT_FILE); @@ -118,6 +131,10 @@ public Collection validate(@NotNull String path) { @Override public boolean shouldValidateJcrData(@NotNull Path filePath) { + if (isIgnoredViolationBasedOnPathPattern(filePath.toString(), ignoreViolationsInPropertiesMatchingPathPatterns)) { + LOGGER.debug("Path '{}' is explicitly whitelisted even if it contains violations and therefore has no restrictions!", filePath); + return false; + } return (isHtlFile(filePath) || isJspFile(filePath)); } @@ -167,6 +184,11 @@ static String jcrExpandedFormNameToReadableFormat(String jcrExpandedFormName) { @Override public Collection validate(@NotNull DocViewNode node, @NotNull String nodePath, @NotNull Path filePath, boolean isRoot) { + if (isIgnoredViolationBasedOnPathPattern(nodePath, ignoreViolationsInPropertiesMatchingPathPatterns)) { + LOGGER.debug("Path '{}' is explicitly whitelisted even if it contains violations and therefore has no restrictions!", nodePath); + return null; + } + Collection messages = new LinkedList<>(); String subject = String.format(MESSAGE_SUBJECT_NODE, jcrExpandedFormNameToReadableFormat(node.label)); @@ -190,7 +212,7 @@ public Collection validate(@NotNull DocViewNode node, @NotNul messages.add(message); overlaidNodePaths.add(nodePath); } - + // TODO: check usage of clientlib dependencies/embeds return messages; } @@ -204,7 +226,7 @@ public Collection validate(@NotNull DocViewNode node, @NotNul // add subject and usage to message return new ValidationMessage(defaultSeverity, "Resource path must not end with '/' but is '" + resourcePath + "'"); } - + if (usage == ContentUsage.OVERLAY) { if (!resourcePath.startsWith("/apps/")) { return null; // this is not an overlay at all, therefore no violation @@ -228,19 +250,26 @@ private static boolean isJspFile(Path file) { return JSP_PATH_MATCHER.matches(file); } + private static boolean isIgnoredViolationBasedOnPathPattern(@NotNull String path, @Nullable Collection ignoreViolationsInPropertiesMatchingPathPatterns) { + if (ignoreViolationsInPropertiesMatchingPathPatterns == null) { + return false; + } + return ignoreViolationsInPropertiesMatchingPathPatterns.stream().anyMatch(r -> r.matcher(path).matches()); + } + static @NotNull String extendMessageWithRemark(@NotNull String message, String remark) { if (remark != null && !remark.isEmpty()) { return message + " Remark: " + remark; } return message; } - + @NotNull ValidationMessageSeverity getSeverityForClassification(ContentClassification classification) { ValidationMessageSeverity severity = severityPerClassification.get(classification); return severity != null ? severity : defaultSeverity; } - + @Override public int hashCode() { final int prime = 31; @@ -248,6 +277,7 @@ public int hashCode() { result = prime * result + ((classificationMap == null) ? 0 : classificationMap.hashCode()); result = prime * result + ((defaultSeverity == null) ? 0 : defaultSeverity.hashCode()); result = prime * result + ((whitelistedResourcePaths == null) ? 0 : whitelistedResourcePaths.hashCode()); + result = prime * result + ((ignoreViolationsInPropertiesMatchingPaths == null) ? 0 : ignoreViolationsInPropertiesMatchingPaths.hashCode()); result = prime * result + ((severityPerClassification == null) ? 0 : severityPerClassification.hashCode()); return result; } @@ -273,6 +303,11 @@ public boolean equals(Object obj) { return false; } else if (!whitelistedResourcePaths.equals(other.whitelistedResourcePaths)) return false; + if (ignoreViolationsInPropertiesMatchingPaths == null) { + if (other.ignoreViolationsInPropertiesMatchingPaths != null) + return false; + } else if (!ignoreViolationsInPropertiesMatchingPaths.equals(other.ignoreViolationsInPropertiesMatchingPaths)) + return false; if (severityPerClassification == null) { if (other.severityPerClassification != null) return false; @@ -285,6 +320,7 @@ public boolean equals(Object obj) { public String toString() { return "AemClassificationValidator [" + (classificationMap != null ? "classificationMap=" + classificationMap + ", " : "") + (whitelistedResourcePaths != null ? "resourceTypeWhitelist=" + whitelistedResourcePaths + ", " : "") + + (ignoreViolationsInPropertiesMatchingPaths != null ? "ignoreViolationsInPropertiesMatchingPaths=" + ignoreViolationsInPropertiesMatchingPaths + ", " : "") + (severityPerClassification != null ? "severityPerClassification=" + severityPerClassification + ", " : "") + (defaultSeverity != null ? "defaultSeverity=" + defaultSeverity : "") + "]"; } diff --git a/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactory.java b/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactory.java index b426fae..e6353ae 100644 --- a/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactory.java +++ b/aem-classification-validator/src/main/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactory.java @@ -52,6 +52,9 @@ public class AemClassificationValidatorFactory implements ValidatorFactory { private static final String OPTION_WHITELISTED_RESOURCE_PATH_PATTERNS = "whitelistedResourcePathPatterns"; private static final String OPTION_WHITELISTED_RESOURCE_PATH_PATTERNS_OLD = "whitelistedResourcePathsPatterns"; + /** optional list of comma-separated path patterns to ignore violations if properties match inside (should be absolute) */ + private static final String OPTION_IGNORE_VIOLATIONS_IN_PROPERTIES_MATCHING_PATH_PATTERNS = "ignoreViolationsInPropertiesMatchingPathPatterns"; + private static final Object OPTION_SEVERITIES_PER_CLASSIFICATION = "severitiesPerClassification"; private static final Logger LOGGER = LoggerFactory.getLogger(AemClassificationValidatorFactory.class); @@ -72,18 +75,14 @@ public Validator createValidator(@NotNull ValidationContext context, @NotNull Va optionWhitelistedResourcePaths = settings.getOptions().get(OPTION_WHITELISTED_RESOURCE_PATH_PATTERNS); } - Collection whitelistedResourcePaths = - Optional.ofNullable(optionWhitelistedResourcePaths) - .map(op -> Arrays.stream(op.split(",")) - .map(String::trim) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()); - - try { - whitelistedResourcePaths.stream().forEach(AemClassificationValidatorFactory::validateResourcePathPattern); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("At least one value given in option " + OPTION_WHITELISTED_RESOURCE_PATH_PATTERNS + " is invalid", e); + String optionIgnoreViolationsInPropertiesMatchingPathPatterns = null; + if (settings.getOptions().containsKey(OPTION_IGNORE_VIOLATIONS_IN_PROPERTIES_MATCHING_PATH_PATTERNS)) { + optionIgnoreViolationsInPropertiesMatchingPathPatterns = settings.getOptions().get(OPTION_IGNORE_VIOLATIONS_IN_PROPERTIES_MATCHING_PATH_PATTERNS); } + + Collection whitelistedResourcePaths = getPathsFromOption(optionWhitelistedResourcePaths); + Collection ignoreViolationsInPropertiesMatchingPaths = getPathsFromOption(optionIgnoreViolationsInPropertiesMatchingPathPatterns); + try { Collection maps = new LinkedList<>(); for (String mapUrl : mapUrls.split("\\s*,\\s*")) { @@ -96,7 +95,7 @@ public Validator createValidator(@NotNull ValidationContext context, @NotNull Va throw new IllegalArgumentException("At least one valid map must be given!"); } return new AemClassificationValidator(settings.getDefaultSeverity(), new CompositeContentClassificationMap(maps), whitelistedResourcePaths, - getSeverityPerClassification(settings.getOptions().get(OPTION_SEVERITIES_PER_CLASSIFICATION))); + ignoreViolationsInPropertiesMatchingPaths, getSeverityPerClassification(settings.getOptions().get(OPTION_SEVERITIES_PER_CLASSIFICATION))); } catch (IOException e) { throw new IllegalStateException("Could not read from " + mapUrls, e); } @@ -141,6 +140,22 @@ private static Map parseSeveri return result; } + private static Collection getPathsFromOption(String optionPaths) { + Collection result = + Optional.ofNullable(optionPaths) + .map(op -> Arrays.stream(op.split(",")) + .map(String::trim) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); + + try { + result.stream().forEach(AemClassificationValidatorFactory::validateResourcePathPattern); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("At least one value given in option " + OPTION_WHITELISTED_RESOURCE_PATH_PATTERNS + " is invalid", e); + } + return result; + } + static void validateResourcePathPattern(String resourcePathPattern) throws IllegalArgumentException { Matcher matcher = Pattern.compile(resourcePathPattern).matcher("/"); matcher.matches(); diff --git a/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactoryTest.java b/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactoryTest.java index 4afeb47..2b740d8 100644 --- a/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactoryTest.java +++ b/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorFactoryTest.java @@ -14,12 +14,14 @@ */ import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import org.apache.jackrabbit.vault.validation.spi.ValidationContext; import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity; import org.apache.jackrabbit.vault.validation.spi.ValidatorSettings; import org.jetbrains.annotations.NotNull; @@ -88,6 +90,7 @@ void testCreateValidator() { options.put("maps", "tccl:valid-classification.map"); // deprecated option for whitelisting options.put("whitelistedResourcePathsPatterns", "/resourceType1/.*,/resourceType2,\n/resourceType3"); + options.put("ignoreViolationsInPropertiesMatchingPathPatterns", "/apps/mysite/components/reference,\n/apps/mysite/components/old-components/.*"); options.put("severitiesPerClassification", "INTERNAL=DEBUG,\nINTERNAL_DEPRECATED=INFO"); ValidatorSettings settings = new ValidatorSettingsImpl(false, ValidationMessageSeverity.WARN, options); MutableContentClassificationMap map = new MutableContentClassificationMapImpl("Simple"); @@ -96,37 +99,42 @@ void testCreateValidator() { whiteListedResourceTypes.add("/resourceType1/.*"); whiteListedResourceTypes.add("/resourceType2"); whiteListedResourceTypes.add("/resourceType3"); + Collection ignoreViolationsInPropertiesMatchingPathPatterns = new LinkedList<>(); + ignoreViolationsInPropertiesMatchingPathPatterns.add("/apps/mysite/components/reference"); + ignoreViolationsInPropertiesMatchingPathPatterns.add("/apps/mysite/components/old-components/.*"); Map severitiesPerClassification = new HashMap<>(); severitiesPerClassification.put(ContentClassification.INTERNAL, ValidationMessageSeverity.DEBUG); severitiesPerClassification.put(ContentClassification.INTERNAL_DEPRECATED, ValidationMessageSeverity.INFO); - AemClassificationValidator expectedValidator = new AemClassificationValidator(ValidationMessageSeverity.WARN, new CompositeContentClassificationMap(map), whiteListedResourceTypes, severitiesPerClassification); - Assertions.assertEquals(expectedValidator, factory.createValidator(null, settings)); + AemClassificationValidator expectedValidator = new AemClassificationValidator(ValidationMessageSeverity.WARN, new CompositeContentClassificationMap(map), whiteListedResourceTypes, ignoreViolationsInPropertiesMatchingPathPatterns, severitiesPerClassification); + Assertions.assertEquals(expectedValidator, factory.createValidator(mock(ValidationContext.class), settings)); options = new HashMap<>(); options.put("maps", "tccl:valid-classification.map"); // new option for whitelisting options.put("whitelistedResourcePathPatterns", "/resourceType1/.*,/resourceType2,\n/resourceType3"); + options.put("ignoreViolationsInPropertiesMatchingPathPatterns", "/apps/mysite/components/reference,\n/apps/mysite/components/old-components/.*"); options.put("severitiesPerClassification", "INTERNAL=DEBUG,\nINTERNAL_DEPRECATED=INFO"); settings = new ValidatorSettingsImpl(false, ValidationMessageSeverity.WARN, options); - Assertions.assertEquals(expectedValidator, factory.createValidator(null, settings)); + Assertions.assertEquals(expectedValidator, factory.createValidator(mock(ValidationContext.class), settings)); // test with multiple validation maps including whitespaces in the maps string options = new HashMap<>(); options.put("maps", "tccl:valid-classification.map,tccl:empty-map-1.map,\n \t tccl:empty-map-2.map"); options.put("whitelistedResourcePathPatterns", "/resourceType1/.*,/resourceType2,\n/resourceType3"); + options.put("ignoreViolationsInPropertiesMatchingPathPatterns", "/apps/mysite/components/reference,\n/apps/mysite/components/old-components/.*"); options.put("severitiesPerClassification", "INTERNAL=DEBUG,\nINTERNAL_DEPRECATED=INFO"); settings = new ValidatorSettingsImpl(false, ValidationMessageSeverity.WARN, options); ContentClassificationMap emptyMap = new ContentClassificationMapImpl(""); - expectedValidator = new AemClassificationValidator(ValidationMessageSeverity.WARN, new CompositeContentClassificationMap(map, emptyMap, emptyMap), whiteListedResourceTypes, severitiesPerClassification); - Assertions.assertEquals(expectedValidator, factory.createValidator(null, settings)); + expectedValidator = new AemClassificationValidator(ValidationMessageSeverity.WARN, new CompositeContentClassificationMap(map, emptyMap, emptyMap), whiteListedResourceTypes, ignoreViolationsInPropertiesMatchingPathPatterns, severitiesPerClassification); + Assertions.assertEquals(expectedValidator, factory.createValidator(mock(ValidationContext.class), settings)); } - + private static final class ValidatorSettingsImpl implements ValidatorSettings { - + private final boolean isDisabled; private final ValidationMessageSeverity defaultSeverity; private Map options; - + ValidatorSettingsImpl(boolean isDisabled, ValidationMessageSeverity defaultSeverity, Map options) { super(); this.isDisabled = isDisabled; diff --git a/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorTest.java b/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorTest.java index c28b22e..e6fee7d 100644 --- a/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorTest.java +++ b/aem-classification-validator/src/test/java/biz/netcentric/filevault/validator/aem/classification/AemClassificationValidatorTest.java @@ -24,11 +24,7 @@ import java.nio.file.FileSystemException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.stream.Collectors; @@ -59,6 +55,7 @@ class AemClassificationValidatorTest { private static final Path SIMPLEFILE_JSP_PATH = Paths.get("/apps/example.jsp"); private static final Path OVERLAY_DOCVIEW_PATH = Paths.get("/apps/.content.xml"); private static final Path EXAMPLE_DOCVIEW_PATH = Paths.get("/apps/example/.content.xml"); + private static final Path EXAMPLE_WHITELISTED_DOCVIEW_PATH = Paths.get("/apps/example/whitelisted/.content.xml"); @BeforeEach void setUp() { @@ -68,7 +65,11 @@ void setUp() { classificationMap.put("/libs/internal", ContentClassification.INTERNAL, "internalremark"); classificationMap.put("/libs/public", ContentClassification.PUBLIC, "publicremark"); classificationMap.put("/", ContentClassification.PUBLIC, ""); - validator = new AemClassificationValidator(ValidationMessageSeverity.ERROR, classificationMap, Collections.emptyList(), Collections.emptyMap()); + Collection ignoreViolationsInPropertiesMatchingPathPatterns = new ArrayList<>(); + ignoreViolationsInPropertiesMatchingPathPatterns.add("/apps/mytest/component/whitelisted/whitelisted.jsp"); + ignoreViolationsInPropertiesMatchingPathPatterns.add("/apps/internal/whitelisted"); + ignoreViolationsInPropertiesMatchingPathPatterns.add("/apps/example/whitelisted"); + validator = new AemClassificationValidator(ValidationMessageSeverity.ERROR, classificationMap, Collections.emptyList(), ignoreViolationsInPropertiesMatchingPathPatterns, Collections.emptyMap()); } @Test @@ -85,7 +86,7 @@ void testHtlIncludePattern() { matcher = AemClassificationValidator.HTL_INCLUDE_OVERWRITING_RESOURCE_TYPE.matcher("
"); assertTrue(matcher.find()); assertEquals("resourceType", matcher.group(1)); - + // use double quotes in expression string literals matcher = AemClassificationValidator.HTL_INCLUDE_OVERWRITING_RESOURCE_TYPE.matcher("
"); assertTrue(matcher.find()); @@ -96,7 +97,7 @@ void testHtlIncludePattern() { matcher = AemClassificationValidator.HTL_INCLUDE_OVERWRITING_RESOURCE_TYPE.matcher("
"); assertTrue(matcher.find()); assertEquals("resourceType", matcher.group(1)); - + // use attributes without quotes matcher = AemClassificationValidator.HTL_INCLUDE_OVERWRITING_RESOURCE_TYPE.matcher("
"); assertTrue(matcher.find()); @@ -131,10 +132,16 @@ void testReferencingWithTrailingSlashesInDocviewXml() ), validateJcrDocView(validator, "myId", "/invalid-resource-type.xml", EXAMPLE_DOCVIEW_PATH)); } + @Test + void testWhitelistedDocviewXml() + throws SAXException, IOException, ParserConfigurationException, URISyntaxException, FileSystemException { + assertEquals(0, validateJcrDocView(validator, "myId", "/invalid-resource-type.xml", EXAMPLE_WHITELISTED_DOCVIEW_PATH).size()); + } + @Test void testInheritingViolationsInDocviewXml() throws SAXException, IOException, ParserConfigurationException, URISyntaxException, FileSystemException { - + ContentUsage usage = ContentUsage.INHERIT; assertJcrDocViewValidationMessages(validator, EXAMPLE_DOCVIEW_PATH, "/inheriting.xml", //new ClassificationViolation("/apps/example/test1", 4, 82, usage, "/libs/abstract", ContentClassification.ABSTRACT, "abstractremark"), @@ -171,6 +178,7 @@ void testOverlayingViolationsInSimpleFiles() assertEquals(Collections.singleton(getSimpleFileViolationMessage(ValidationMessageSeverity.ERROR, ContentUsage.OVERLAY, "/libs/final/test21", ContentClassification.INTERNAL_CHILD, "finalremark")), validator.validate("/apps/final/test21")); assertEquals(Collections.singleton(getSimpleFileViolationMessage(ValidationMessageSeverity.ERROR, ContentUsage.OVERLAY, "/libs/internal", ContentClassification.INTERNAL, "internalremark")), validator.validate("/apps/internal")); assertEquals(Collections.singleton(getSimpleFileViolationMessage(ValidationMessageSeverity.ERROR, ContentUsage.OVERLAY, "/libs/internal/test21",ContentClassification.INTERNAL, "internalremark")), validator.validate("/apps/internal/test21")); + assertNull(validator.validate("/apps/internal/whitelisted")); assertNull(validator.validate("/apps/public")); assertNull(validator.validate("/apps/public/test41")); } @@ -181,6 +189,7 @@ void testReferencingViolationsInHtlAndJsp() throws IOException { assertTrue(validator.shouldValidateJcrData(Paths.get("/apps/mytest/component/componentA/componentA.html"))); assertTrue(validator.shouldValidateJcrData(Paths.get("/apps/mytest/component/componentA/componentA.jsp"))); assertFalse(validator.shouldValidateJcrData(Paths.get("/apps/mytest/component/componentA/componentA.js"))); + assertFalse(validator.shouldValidateJcrData(Paths.get("/apps/mytest/component/whitelisted/whitelisted.jsp"))); // check HTL which references protected resource type try (InputStream input = this.getClass().getClassLoader().getResourceAsStream("htl-example.html")) { @@ -188,7 +197,7 @@ void testReferencingViolationsInHtlAndJsp() throws IOException { // and check violations assertEquals(Collections.singletonList(getSimpleFileViolationMessage(ValidationMessageSeverity.ERROR, ContentUsage.REFERENCE, "/libs/abstract/test", ContentClassification.ABSTRACT, "abstractremark")), messages); } - + // check JSP which references protected resource type try (InputStream input = this.getClass().getClassLoader().getResourceAsStream("example.jsp")) { Collection messages = validator.validateJcrData(input, SIMPLEFILE_JSP_PATH, new HashMap()); @@ -210,7 +219,7 @@ static final class ClassificationViolation { private final int line; private final int column; private final ContentUsage usage; - private final String targetResourceType; + private final String targetResourceType; private final ContentClassification classification; private final String remark; @@ -230,11 +239,11 @@ static final class ClassificationViolation { static void assertJcrDocViewValidationMessages(AemClassificationValidator validator, Path path, String name, ClassificationViolation... violations) throws IOException, ParserConfigurationException, SAXException { Collection actualMessages = validateJcrDocView(validator, "myid", name, path); - Collection expectedMessages = Arrays.stream(violations).map(a -> - ValidationViolation.wrapMessage("myid", + Collection expectedMessages = Arrays.stream(violations).map(a -> + ValidationViolation.wrapMessage("myid", getDocviewViolationMessage(ValidationMessageSeverity.ERROR, a.name, a.usage, a.targetResourceType, a.classification, a.remark), path, Paths.get(""), a.nodePath, a.line, a.column)).collect(Collectors.toList()); - + assertEquals(expectedMessages, actualMessages); }