Skip to content

Commit

Permalink
Merge 6aabbce into 7db8c42
Browse files Browse the repository at this point in the history
  • Loading branch information
adamcin committed Sep 18, 2020
2 parents 7db8c42 + 6aabbce commit 042100b
Show file tree
Hide file tree
Showing 7 changed files with 1,602 additions and 42 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com)

## [Unreleased]

### Changed

- issue #79 refactored ExpectPaths and ExpectAces checks to account for delayed SlingInstallable effects.

### Fixed

- issue #59 provide a default, empty resource bundle for a SimpleProgressCheck extension when a bundle cannot be found for the returned resource bundle name.
Expand Down
169 changes: 140 additions & 29 deletions core/src/main/java/net/adamcin/oakpal/core/checks/ExpectAces.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package net.adamcin.oakpal.core.checks;

import net.adamcin.oakpal.api.EmbeddedPackageInstallable;
import net.adamcin.oakpal.api.Fun;
import net.adamcin.oakpal.api.JavaxJson;
import net.adamcin.oakpal.api.ProgressCheck;
Expand All @@ -25,6 +26,7 @@
import net.adamcin.oakpal.api.Rules;
import net.adamcin.oakpal.api.Severity;
import net.adamcin.oakpal.api.SimpleProgressCheckFactoryCheck;
import net.adamcin.oakpal.api.SlingInstallable;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
Expand All @@ -41,8 +43,10 @@
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.Privilege;
import javax.json.JsonObject;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -231,8 +235,11 @@ public ProgressCheck newInstance(final JsonObject config) throws Exception {
final List<AceCriteria> notExpectedAces = parseAceCriteria(config, precedingPrincipals, keys().notExpectedAces());
final Severity severity = Severity.valueOf(
config.getString(keys().severity(), DEFAULT_SEVERITY.name()).toUpperCase());
// TODO 2.3.0 export to JsonKeys interface
final boolean ignoreNestedPackages = config.getBoolean("ignoreNestedPackages", false);
return new Check(expectedAces, notExpectedAces,
Rules.fromJsonArray(JavaxJson.arrayOrEmpty(config, keys().afterPackageIdRules())), severity);
Rules.fromJsonArray(JavaxJson.arrayOrEmpty(config, keys().afterPackageIdRules())),
ignoreNestedPackages, severity);
}

static boolean isPrincipalSpec(final @NotNull String spec) {
Expand Down Expand Up @@ -279,25 +286,30 @@ static List<AceCriteria> parseAceCriteria(final @NotNull JsonObject config,
static final class Check extends SimpleProgressCheckFactoryCheck<ExpectAces> {
final List<AceCriteria> expectedAces;
final List<AceCriteria> notExpectedAces;
final PackageGraph graph = new PackageGraph();
final Map<AceCriteria, List<PackageId>> expectedViolators = new LinkedHashMap<>();
final Map<AceCriteria, List<PackageId>> notExpectedViolators = new LinkedHashMap<>();
final List<Rule> afterPackageIdRules;
final boolean ignoreNestedPackages;
final Severity severity;

Check(final @NotNull List<AceCriteria> expectedAces,
final @NotNull List<AceCriteria> notExpectedAces,
final @NotNull List<Rule> afterPackageIdRules,
final boolean ignoreNestedPackages,
final @NotNull Severity severity) {
super(ExpectAces.class);
this.expectedAces = expectedAces;
this.notExpectedAces = notExpectedAces;
this.afterPackageIdRules = afterPackageIdRules;
this.ignoreNestedPackages = ignoreNestedPackages;
this.severity = severity;
}

@Override
public void startedScan() {
super.startedScan();
graph.startedScan();
expectedViolators.clear();
notExpectedViolators.clear();
}
Expand All @@ -306,10 +318,6 @@ static Map<String, List<AceCriteria>> groupCriteriaByPath(final @NotNull List<Ac
return criteriaList.stream().collect(Collectors.groupingBy(AceCriteria::getPath));
}

boolean shouldExpectAfterExtract(final @NotNull PackageId packageId) {
return Rules.lastMatch(afterPackageIdRules, packageId.toString()).isInclude();
}

static List<PackageId> getViolatorListForExpectedCriteria(final @NotNull Map<AceCriteria, List<PackageId>> violatorsMap,
final @NotNull AceCriteria criteria) {
if (!violatorsMap.containsKey(criteria)) {
Expand All @@ -318,37 +326,140 @@ static List<PackageId> getViolatorListForExpectedCriteria(final @NotNull Map<Ace
return violatorsMap.get(criteria);
}

boolean shouldExpectAfterExtract(final @NotNull PackageId packageId) {
return (graph.isRoot(packageId) || !ignoreNestedPackages)
&& Rules.lastMatch(afterPackageIdRules, packageId.toString()).isInclude();
}

/**
* Exposed for testing.
*
* @return the package graph
*/
PackageGraph getGraph() {
return graph;
}

@Override
public void afterExtract(final PackageId packageId, final Session inspectSession) throws RepositoryException {
if (shouldExpectAfterExtract(packageId)) {
final JackrabbitAccessControlManager aclManager = (JackrabbitAccessControlManager) inspectSession.getAccessControlManager();
final Map<String, List<AceCriteria>> expectedsByPath = groupCriteriaByPath(expectedAces);
final Map<String, List<AceCriteria>> notExpectedsByPath = groupCriteriaByPath(notExpectedAces);
final Set<String> allPaths = new LinkedHashSet<>(expectedsByPath.keySet());
allPaths.addAll(notExpectedsByPath.keySet());
for (String path : allPaths) {
final JackrabbitAccessControlList[] policiesAtPath =
// provide null path for rep:repoPolicy evaluation
(path.isEmpty() ? Stream.of(aclManager.getPolicies((String) null))
: (inspectSession.nodeExists(path) ? Stream.of(aclManager.getPolicies(path))
: Stream.empty()))
.filter(JackrabbitAccessControlList.class::isInstance)
.map(JackrabbitAccessControlList.class::cast)
.toArray(JackrabbitAccessControlList[]::new);
for (AceCriteria criteria : expectedsByPath.getOrDefault(path, Collections.emptyList())) {
if (Stream.of(policiesAtPath).noneMatch(criteria::satisfiedBy)) {
getViolatorListForExpectedCriteria(expectedViolators, criteria).add(packageId);
}
public void identifyPackage(final PackageId packageId, final File file) {
graph.identifyPackage(packageId, file);
}

@Override
public void identifySubpackage(final PackageId packageId, final PackageId parentId) {
graph.identifySubpackage(packageId, parentId);
}

@Override
public void identifyEmbeddedPackage(final PackageId packageId, final PackageId parentId, final EmbeddedPackageInstallable slingInstallable) {
graph.identifyEmbeddedPackage(packageId, parentId, slingInstallable);
}

/**
* Perform the logic to validate expectations against current state using the provided {@code inspectSession}.
* Any new violations detected for a particular set of path criteria will be blamed on the collection of
* packageIds provided by the {@code possibleViolators} argument.
*
* @param possibleViolators the collection of possible violator packages with influence over the current
* repository state
* @param inspectSession the JCR session to inspect for conformance to configured expectations
* @throws RepositoryException if JCR error occurs during validation
*/
void blameViolatorsForMissedExpectations(final @NotNull Collection<PackageId> possibleViolators,
final @NotNull Session inspectSession) throws RepositoryException {
final JackrabbitAccessControlManager aclManager =
(JackrabbitAccessControlManager) inspectSession.getAccessControlManager();
final Map<String, List<AceCriteria>> expectedsByPath = groupCriteriaByPath(expectedAces);
final Map<String, List<AceCriteria>> notExpectedsByPath = groupCriteriaByPath(notExpectedAces);
final Set<String> allPaths = new LinkedHashSet<>(expectedsByPath.keySet());
allPaths.addAll(notExpectedsByPath.keySet());
for (String path : allPaths) {
final JackrabbitAccessControlList[] policiesAtPath =
// provide null path for rep:repoPolicy evaluation
(path.isEmpty() ? Stream.of(aclManager.getPolicies((String) null))
: (inspectSession.nodeExists(path) ? Stream.of(aclManager.getPolicies(path))
: Stream.empty()))
.filter(JackrabbitAccessControlList.class::isInstance)
.map(JackrabbitAccessControlList.class::cast)
.toArray(JackrabbitAccessControlList[]::new);
for (AceCriteria criteria : expectedsByPath.getOrDefault(path, Collections.emptyList())) {
// only look for sling violators of an expected ace criteria if a violation for said criteria
// has not already been collected.
final List<PackageId> violators = getViolatorListForExpectedCriteria(expectedViolators, criteria);
if (violators.isEmpty() && Stream.of(policiesAtPath).noneMatch(criteria::satisfiedBy)) {
violators.addAll(possibleViolators);
} else if (!violators.isEmpty() && Stream.of(policiesAtPath).anyMatch(criteria::satisfiedBy)) {
violators.removeAll(possibleViolators);
}
for (AceCriteria criteria : notExpectedsByPath.getOrDefault(path, Collections.emptyList())) {
if (Stream.of(policiesAtPath).anyMatch(criteria::satisfiedBy)) {
getViolatorListForExpectedCriteria(notExpectedViolators, criteria).add(packageId);
}
}
for (AceCriteria criteria : notExpectedsByPath.getOrDefault(path, Collections.emptyList())) {
// only look for sling violators of an unexpected path if a violation for said path has not already
// been collected.
final List<PackageId> violators = getViolatorListForExpectedCriteria(notExpectedViolators, criteria);
if (violators.isEmpty() && Stream.of(policiesAtPath).anyMatch(criteria::satisfiedBy)) {
violators.addAll(possibleViolators);
} else if (!violators.isEmpty() && Stream.of(policiesAtPath).noneMatch(criteria::satisfiedBy)) {
violators.removeAll(possibleViolators);
}
}
}
}

/**
* Validate expectations immediately after extracting a package whose ID matches the
* {@code config.afterPackageIdRules}.
*
* @param packageId the current package
* @param inspectSession session providing access to repository state
* @throws RepositoryException if a JCR error occurs during validation of expectations
*/
@Override
public void afterExtract(final PackageId packageId, final Session inspectSession) throws RepositoryException {
if (shouldExpectAfterExtract(packageId)) {
blameViolatorsForMissedExpectations(graph.getSelfAndAncestors(packageId), inspectSession);
}
}

/**
* Validate expectations immediately after applying repo init scripts.
*
* @param scanPackageId the last preinstall or scan package
* @param scripts the repoinit scripts that were applied
* @param slingInstallable the associated {@link SlingInstallable} identifying the source JCR event that provided
* the repo init scripts
* @param inspectSession session providing access to repository state
* @throws RepositoryException if an error occurs while validating expectations
*/
@Override
public void appliedRepoInitScripts(final PackageId scanPackageId, final List<String> scripts,
final SlingInstallable slingInstallable, final Session inspectSession)
throws RepositoryException {
if (shouldExpectAfterExtract(slingInstallable.getParentId())) {
blameViolatorsForMissedExpectations(graph.getSelfAndAncestors(slingInstallable.getParentId()),
inspectSession);
}
}

/**
* If configured to {@code ignoreNestedPackages}, this handler will evaluate the expectations after
* the package scan in case any violations with a transitive relationship to
* a packageId matching the {@code config.afterPackageIdRules} can be detected.
*
* @param scanPackageId the scanned package id
* @param inspectSession session providing access to repository state
* @throws RepositoryException if an error occurs while validating expectations
*/
@Override
public void afterScanPackage(final PackageId scanPackageId, final Session inspectSession)
throws RepositoryException {
if (ignoreNestedPackages && shouldExpectAfterExtract(scanPackageId)) {
blameViolatorsForMissedExpectations(graph.getSelfAndDescendants(scanPackageId), inspectSession);
}
}

/**
* Report all violations collected during the scan.
*/
@Override
public void finishedScan() {
for (Map.Entry<AceCriteria, List<PackageId>> violatorsEntry : expectedViolators.entrySet()) {
Expand Down
Loading

0 comments on commit 042100b

Please sign in to comment.