SLING-11243 handle conflict between aggregate and leaf (#15)
Modifying an ACE should not include a allow/deny aggregate privilege
when there is a deny/allow child privilege with the same restrictions as
the parent
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/LocalRestriction.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/LocalRestriction.java
index 58e5a61..645ee11 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/LocalRestriction.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/LocalRestriction.java
@@ -69,7 +69,7 @@
builder.append("LocalRestriction [name=");
builder.append(rd == null ? null : rd.getName());
builder.append(", value=");
- builder.append(getValues());
+ builder.append(Arrays.toString(getValues()));
builder.append("]");
return builder.toString();
}
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelper.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelper.java
index 6404271..7fd2459 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelper.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelper.java
@@ -80,6 +80,46 @@
}
/**
+ * Populates a local allow and/or deny privilege in the privilegeToLocalPrivilegesMap
+ *
+ * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
+ * @param privilege the privilege to allow
+ * @param allow true or false to set the allow value of the LocalPrivilege
+ * @param allowRestrictions if allow is true, the set of restrictions
+ * @param deny true or false to set the allow value of the LocalPrivilege
+ * @param denyRestrictions if deny is true, the set of restrictions
+ * @return the LocalPrivileges that was populated
+ */
+ public static LocalPrivilege localAllowAndDenyPriv(Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap,
+ Privilege privilege,
+ boolean allow, @NotNull Set<LocalRestriction> allowRestrictions,
+ boolean deny, @NotNull Set<LocalRestriction> denyRestrictions
+ ) {
+ LocalPrivilege localPrivilege = privilegeToLocalPrivilegesMap.computeIfAbsent(privilege, LocalPrivilege::new);
+ if (allow) {
+ localPrivilege.setAllow(true);
+ localPrivilege.setAllowRestrictions(allowRestrictions);
+ } else {
+ localPrivilege.setAllow(false);
+ localPrivilege.setAllowRestrictions(Collections.emptySet());
+ }
+ if (deny) {
+ localPrivilege.setDeny(true);
+ localPrivilege.setDenyRestrictions(denyRestrictions);
+ } else {
+ localPrivilege.setDeny(false);
+ localPrivilege.setDenyRestrictions(Collections.emptySet());
+ }
+ if (localPrivilege.isAllow() && localPrivilege.isDeny() && localPrivilege.sameAllowAndDenyRestrictions()) {
+ // same restrictions to we prefer the allow
+ localPrivilege.setDeny(false);
+ localPrivilege.setDenyRestrictions(Collections.emptySet());
+ }
+
+ return localPrivilege;
+ }
+
+ /**
* Populates a local allow privilege in the privilegeToLocalPrivilegesMap
*
* @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
@@ -132,6 +172,43 @@
}
/**
+ * Populates each of the local allow and/or deny privilege in the privilegeToLocalPrivilegesMap. If the supplied
+ * privilege is an aggregate then the data is populated for each of non-aggregate privileges contained in
+ * the aggregate privilege. Otherwise, the data is populated for the privilege itself.
+ *
+ * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
+ * @param p the privilege to update
+ * @param allow true or false to set the allow value of the LocalPrivilege
+ * @param allowRestrictions if allow is true, the set of restrictions
+ * @param deny true or false to set the allow value of the LocalPrivilege
+ * @param denyRestrictions if deny is true, the set of restrictions
+ */
+ private static void expandAllowAndDenyPrivWithoutAggregates(Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap,
+ Privilege p,
+ boolean allow, @NotNull Set<LocalRestriction> allowRestrictions,
+ boolean deny, @NotNull Set<LocalRestriction> denyRestrictions
+ ) throws RepositoryException {
+ if (p.isAggregate()) {
+ Privilege[] aggregatePrivileges = p.getDeclaredAggregatePrivileges();
+ for (Privilege aggregatePrivilege : aggregatePrivileges) {
+ if (aggregatePrivilege.isAggregate()) {
+ expandAllowAndDenyPrivWithoutAggregates(privilegeToLocalPrivilegesMap, aggregatePrivilege,
+ allow, allowRestrictions,
+ deny, denyRestrictions);
+ } else {
+ localAllowAndDenyPriv(privilegeToLocalPrivilegesMap, aggregatePrivilege,
+ allow, allowRestrictions,
+ deny, denyRestrictions);
+ }
+ }
+ } else {
+ localAllowAndDenyPriv(privilegeToLocalPrivilegesMap, p,
+ allow, allowRestrictions,
+ deny, denyRestrictions);
+ }
+ }
+
+ /**
* Populates each of the local allow privilege in the privilegeToLocalPrivilegesMap. If the supplied
* privilege is an aggregate then the data is populated for each of non-aggregate privileges contained in
* the aggregate privilege. Otherwise, the data is populated for the privilege itself.
@@ -184,6 +261,28 @@
}
/**
+ * Populates each of the local allow and/or deny privilege in the privilegeToLocalPrivilegesMap. If the supplied
+ * privilege is an aggregate then the data is populated for each of non-aggregate privileges contained in
+ * the aggregate privilege. Otherwise, the data is populated for the privilege itself.
+ *
+ * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
+ * @param allow true or false to set the allow value of the LocalPrivilege
+ * @param allowRestrictions if allow is true, the set of restrictions
+ * @param deny true or false to set the allow value of the LocalPrivilege
+ * @param denyRestrictions if deny is true, the set of restrictions
+ * @param privileges the privilege to update
+ */
+ public static void allowAndDeny(Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap,
+ boolean allow, @NotNull Set<LocalRestriction> allowRestrictions,
+ boolean deny, @NotNull Set<LocalRestriction> denyRestrictions,
+ @NotNull Collection<Privilege> privileges) throws RepositoryException {
+ for (Privilege privilege : privileges) {
+ expandAllowAndDenyPrivWithoutAggregates(privilegeToLocalPrivilegesMap, privilege,
+ allow, allowRestrictions, deny, denyRestrictions);
+ }
+ }
+
+ /**
* Populates each of the allow privilege in the privilegeToLocalPrivilegesMap.
*
* @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
@@ -577,43 +676,63 @@
if (childPrivileges.length == childLocalPrivileges.size()) {
boolean allAllow = childLocalPrivileges.stream().allMatch(LocalPrivilege::isAllow);
if (allAllow) {
- // all the child privileges are allow so we can mark the parent as allow
- LocalPrivilege alp = privilegeToLocalPrivilegesMap.computeIfAbsent(aggregatePrivilege, LocalPrivilege::new);
- alp.setAllow(true);
-
// if the restrictions of all the items is the same then we should copy it up
// and unset the data from each child
Set<LocalRestriction> firstAllowRestrictions = childLocalPrivileges.get(0).getAllowRestrictions();
boolean allRestrictionsSame = childLocalPrivileges.stream().allMatch(lp -> firstAllowRestrictions.equals(lp.getAllowRestrictions()));
+ Set<LocalRestriction> restrictions;
if (allRestrictionsSame) {
- alp.setAllowRestrictions(firstAllowRestrictions);
+ restrictions = firstAllowRestrictions;
+ } else {
+ restrictions = Collections.emptySet();
}
- // each child with the same restrictions can be unset
- for (LocalPrivilege lp : childLocalPrivileges) {
- if (lp.sameAllowRestrictions(alp.getAllowRestrictions())) {
- lp.setAllow(false);
- lp.setAllowRestrictions(Collections.emptySet());
+
+ // if any deny child has the same restrictions, then the parent should not be set
+ // as that would cause the deny privilege to get excluded when persisted
+ boolean anyDenyWithSameRestrictions = childLocalPrivileges.stream().anyMatch(p -> p.isDeny() && p.sameDenyRestrictions(restrictions));
+ if (!anyDenyWithSameRestrictions) {
+ // all the child privileges are allow so we can mark the parent as allow
+ LocalPrivilege alp = privilegeToLocalPrivilegesMap.computeIfAbsent(aggregatePrivilege, LocalPrivilege::new);
+ alp.setAllow(true);
+ alp.setAllowRestrictions(restrictions);
+
+ // each child with the same restrictions can be unset
+ for (LocalPrivilege lp : childLocalPrivileges) {
+ if (lp.sameAllowRestrictions(alp.getAllowRestrictions())) {
+ lp.setAllow(false);
+ lp.setAllowRestrictions(Collections.emptySet());
+ }
}
}
}
boolean allDeny = childLocalPrivileges.stream().allMatch(LocalPrivilege::isDeny);
if (allDeny) {
- // all the child privileges are deny so we can mark the parent as deny
- LocalPrivilege alp = privilegeToLocalPrivilegesMap.computeIfAbsent(aggregatePrivilege, LocalPrivilege::new);
- alp.setDeny(true);
-
// if the restrictions of all the items is the same then we should copy it up
// and unset the data from each child
Set<LocalRestriction> firstDenyRestrictions = childLocalPrivileges.get(0).getDenyRestrictions();
boolean allRestrictionsSame = childLocalPrivileges.stream().allMatch(lp -> firstDenyRestrictions.equals(lp.getDenyRestrictions()));
+ Set<LocalRestriction> restrictions;
if (allRestrictionsSame) {
- alp.setDenyRestrictions(firstDenyRestrictions);
+ restrictions = firstDenyRestrictions;
+ } else {
+ restrictions = Collections.emptySet();
}
- // each child with the same restrictions can be unset
- for (LocalPrivilege lp : childLocalPrivileges) {
- if (lp.sameDenyRestrictions(alp.getDenyRestrictions())) {
- lp.setDeny(false);
- lp.setDenyRestrictions(Collections.emptySet());
+
+ // if any allow child has the same restrictions, then the parent should not be set
+ // as that would cause the allow privilege to get excluded when persisted
+ boolean anyAllowWithSameRestrictions = childLocalPrivileges.stream().anyMatch(p -> p.isAllow() && p.sameAllowRestrictions(restrictions));
+ if (!anyAllowWithSameRestrictions) {
+ // all the child privileges are deny so we can mark the parent as deny
+ LocalPrivilege alp = privilegeToLocalPrivilegesMap.computeIfAbsent(aggregatePrivilege, LocalPrivilege::new);
+ alp.setDeny(true);
+ alp.setDenyRestrictions(restrictions);
+
+ // each child with the same restrictions can be unset
+ for (LocalPrivilege lp : childLocalPrivileges) {
+ if (lp.sameDenyRestrictions(alp.getDenyRestrictions())) {
+ lp.setDeny(false);
+ lp.setDenyRestrictions(Collections.emptySet());
+ }
}
}
}
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java
index 9669aa4..d46ccee 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java
@@ -21,7 +21,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashMap;
@@ -169,6 +168,10 @@
this.paramValue = paramValue;
}
+ public String getParamValue() {
+ return paramValue;
+ }
+
public static PrivilegeValues valueOfParam(String value) {
return Stream.of(PrivilegeValues.values())
.filter(item -> item.paramValue.equalsIgnoreCase(value))
@@ -234,8 +237,7 @@
// and now merge the changes from the request parameters
processPostedPrivilegeDeleteParams(acm, request, privilegeToLocalPrivilegesMap);
processPostedRestrictionDeleteParams(acm, request, srMap, privilegeToLocalPrivilegesMap);
- processPostedPrivilegeParams(acm, request, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap);
- processPostedRestrictionParams(acm, request, srMap, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap);
+ processPostedPrivilegeAndRestrictionParams(acm, request, srMap, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap);
// consolidate any aggregates that are still valid
PrivilegesHelper.consolidateAggregates(session, resourcePath, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap);
@@ -480,25 +482,23 @@
}
/**
- * Merge into the privilegeToLocalPrivilegesMap the changes requested in restriction
- * request parameters.
+ * Populate the restrictions that that were posted and applicable
+ * to the requested privilege
*
- * @param acm the access control manager
* @param request the current request
* @param srMap map of restriction names to the restriction definition
- * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
+ * @param forPrivilege the privilege to load the restrictions for
+ * @param forAllowOrDeny either {@link PrivilegeValues#ALLOW} or {@link PrivilegeValues#DENY}
+ * @param generalRestrictions the general restrictions that are not for a specific privilege
*/
- protected void processPostedRestrictionParams(@NotNull AccessControlManager acm,
+ protected Set<LocalRestriction> postedRestrictionsForPrivilege(
@NotNull SlingHttpServletRequest request,
@NotNull Map<String, RestrictionDefinition> srMap,
- @NotNull Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap,
- @NotNull Map<Privilege, Integer> privilegeLongestDepthMap) throws RepositoryException {
- Session session = request.getResourceResolver().adaptTo(Session.class);
- ValueFactory vf = session.getValueFactory();
+ @NotNull Privilege forPrivilege,
+ @NotNull PrivilegeValues forAllowOrDeny,
+ @NotNull Set<LocalRestriction> generalRestrictions) throws RepositoryException {
+ Set<LocalRestriction> restrictions = new HashSet<>(generalRestrictions);
- // first pass to collect all the restrictions so we can
- // process them in the right order
- Map<Privilege, Map<LocalRestriction, Set<String>>> privilegeToLocalRestrictionMap = new HashMap<>();
@NotNull
Map<String, Matcher> postedRestrictionParams = getMatchedRequestParameterNames(request, RESTRICTION_PATTERN);
for (Entry<String, Matcher> entry : postedRestrictionParams.entrySet()) {
@@ -506,146 +506,214 @@
Matcher matcher = entry.getValue();
String privilegeName;
String restrictionName;
- String allowOrDeny;
+ PrivilegeValues allowOrDeny;
if (matcher.group(2) != null) {
privilegeName = matcher.group(1);
restrictionName = matcher.group(3);
- allowOrDeny = matcher.group(4);
+ allowOrDeny = PrivilegeValues.valueOfParam(matcher.group(4));
} else {
privilegeName = null;
restrictionName = matcher.group(1);
allowOrDeny = null;
}
- Privilege privilege = privilegeName == null ? null : acm.privilegeFromName(privilegeName);
-
- RestrictionDefinition rd = srMap.get(restrictionName);
- if (rd == null) {
- //illegal restriction name?
- throw new AccessControlException(INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED);
- }
- LocalRestriction localRestriction;
- int restrictionType = rd.getRequiredType().tag();
- if (rd.getRequiredType().isArray()) {
- // multi-value
- String[] parameterValues = request.getParameterValues(paramName);
- Value[] restrictionValue = new Value[parameterValues.length];
- for (int i = 0; i < parameterValues.length; i++) {
- restrictionValue[i] = vf.createValue(parameterValues[i], restrictionType);
- }
- localRestriction = new LocalRestriction(rd, restrictionValue);
- } else {
- // single value
- Value restrictionValue = vf.createValue(request.getParameter(paramName), restrictionType);
- localRestriction = new LocalRestriction(rd, restrictionValue);
- }
-
- Map<LocalRestriction, Set<String>> lrMap = privilegeToLocalRestrictionMap.computeIfAbsent(privilege, k -> new HashMap<>());
- Set<String> valuesSet = lrMap.computeIfAbsent(localRestriction, k -> new HashSet<>());
- valuesSet.add(allowOrDeny);
- }
-
- List<Entry<Privilege, Map<LocalRestriction, Set<String>>>> sortedEntries = new ArrayList<>(privilegeToLocalRestrictionMap.entrySet());
- // sort the entries to process the most shallow last
- Collections.sort(sortedEntries, Comparator.nullsFirst(Comparator.comparing(entry -> privilegeLongestDepthMap.get(entry.getKey()))));
- for (Entry<Privilege, Map<LocalRestriction, Set<String>>> entry : sortedEntries) {
- Privilege privilege = entry.getKey();
-
- Collection<Privilege> privileges;
- if (privilege == null) {
- // process for every privilege
- privileges = privilegeToLocalPrivilegesMap.keySet();
- } else {
- // process for the specific privilege only
- privileges = Collections.singletonList(privilege);
- }
-
- Map<LocalRestriction, Set<String>> lrMap = entry.getValue();
- for (Entry<LocalRestriction, Set<String>> lrEntry : lrMap.entrySet()) {
- LocalRestriction localRestriction = lrEntry.getKey();
- // sort the values to ensure it processes the Allow entry last when
- // there is a conflict
- List<PrivilegeValues> privilegeValues = lrEntry.getValue().stream()
- .map(item -> item == null ? null : PrivilegeValues.valueOfParam(item))
- .sorted(Comparator.nullsLast(Comparator.comparing(PrivilegeValues::ordinal).reversed()))
- .collect(Collectors.toList());
- for (PrivilegeValues allowOrDeny : privilegeValues) {
- if (allowOrDeny == null) {
- // not specified try both the deny and allow sets
- PrivilegesHelper.allowOrDenyRestriction(privilegeToLocalPrivilegesMap, localRestriction, privileges);
- } else {
- switch (allowOrDeny) {
- case DENY:
- PrivilegesHelper.denyRestriction(privilegeToLocalPrivilegesMap, localRestriction, privileges);
- break;
- case ALLOW:
- PrivilegesHelper.allowRestriction(privilegeToLocalPrivilegesMap, localRestriction, privileges);
- break;
- default:
- break;
- }
- }
- }
+ if ((privilegeName == null || forPrivilege.getName().equals(privilegeName)) &&
+ (allowOrDeny == null || forAllowOrDeny.equals(allowOrDeny))) {
+ LocalRestriction localRestriction = toLocalRestriction(request, srMap, restrictionName, paramName);
+ restrictions.removeIf(r -> r.getName().equals(localRestriction.getName()));
+ restrictions.add(localRestriction);
}
}
+
+ return restrictions;
}
/**
- * Merge into the privilegeToLocalPrivilegesMap the changes requested in privilege
- * request parameters.
+ * Construct a LocalRestriction using data a request parameter
+ *
+ * @param request the current request
+ * @param srMap map of restriction names to the restriction definition
+ * @param restrictionName the name of the restriction
+ * @param paramName the request parameter name that contains the restriction values
+ */
+ protected LocalRestriction toLocalRestriction(
+ @NotNull SlingHttpServletRequest request,
+ @NotNull Map<String, RestrictionDefinition> srMap,
+ @NotNull String restrictionName,
+ @NotNull String paramName) throws RepositoryException {
+ RestrictionDefinition rd = srMap.get(restrictionName);
+ if (rd == null) {
+ //illegal restriction name?
+ throw new AccessControlException(INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED);
+ }
+ Session session = request.getResourceResolver().adaptTo(Session.class);
+ ValueFactory vf = session.getValueFactory();
+ LocalRestriction localRestriction;
+ int restrictionType = rd.getRequiredType().tag();
+ if (rd.getRequiredType().isArray()) {
+ // multi-value
+ String[] parameterValues = request.getParameterValues(paramName);
+ Value[] restrictionValue = new Value[parameterValues.length];
+ for (int i = 0; i < parameterValues.length; i++) {
+ restrictionValue[i] = vf.createValue(parameterValues[i], restrictionType);
+ }
+ localRestriction = new LocalRestriction(rd, restrictionValue);
+ } else {
+ // single value
+ Value restrictionValue = vf.createValue(request.getParameter(paramName), restrictionType);
+ localRestriction = new LocalRestriction(rd, restrictionValue);
+ }
+ return localRestriction;
+ }
+
+ /**
+ * Merge into the privilegeToLocalPrivilegesMap the changes requested in privilege and
+ * restriction request parameters.
*
* @param acm the access control manager
* @param request the current request
+ * @param srMap map of restriction names to the restriction definition
* @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
+ * @param privilegeLongestDepthMap the map of privileges to their longest depth
*/
- protected void processPostedPrivilegeParams(@NotNull AccessControlManager acm,
+ protected void processPostedPrivilegeAndRestrictionParams(@NotNull AccessControlManager acm,
@NotNull SlingHttpServletRequest request,
+ @NotNull Map<String, RestrictionDefinition> srMap,
@NotNull Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap,
@NotNull Map<Privilege, Integer> privilegeLongestDepthMap) throws RepositoryException {
@NotNull
Map<String, Matcher> postedPrivilegeNameKeys = getMatchedRequestParameterNames(request, PRIVILEGE_PATTERN);
- // first pass to collect all the privileges so we can
- // process them in the right order
- Map<Privilege, String> privilegeToParamNameMap = new HashMap<>();
+ // collect all the privileges so we can process them in the right order
+ Map<Privilege, Set<String>> privilegeToParamValuesMap = new HashMap<>();
for (Entry<String, Matcher> entry : postedPrivilegeNameKeys.entrySet()) {
String paramName = entry.getKey();
Matcher matcher = entry.getValue();
String privilegeName = matcher.group(1);
Privilege privilege = acm.privilegeFromName(privilegeName);
- privilegeToParamNameMap.put(privilege, paramName);
+ Set<String> paramValues = privilegeToParamValuesMap.computeIfAbsent(privilege, p -> new HashSet<>());
+ paramValues.addAll(Arrays.asList(request.getParameterValues(paramName)));
}
- List<Entry<Privilege, String>> sortedEntries = new ArrayList<>(privilegeToParamNameMap.entrySet());
+
+ //also check for any restriction params
+ Set<LocalRestriction> generalRestrictions = new HashSet<>();
+ Map<String, Matcher> postedRestrictionParams = getMatchedRequestParameterNames(request, RESTRICTION_PATTERN);
+ for (Entry<String, Matcher> entry : postedRestrictionParams.entrySet()) {
+ Matcher matcher = entry.getValue();
+ if (matcher.group(2) != null) {
+ PrivilegeValues allowOrDeny = PrivilegeValues.valueOfParam(matcher.group(4));
+ if (PrivilegeValues.ALLOW.equals(allowOrDeny) ||
+ PrivilegeValues.DENY.equals(allowOrDeny)) {
+ String privilegeName = matcher.group(1);
+ Privilege privilege = acm.privilegeFromName(privilegeName);
+ Set<String> paramValues = privilegeToParamValuesMap.computeIfAbsent(privilege, p -> new HashSet<>());
+ paramValues.add(allowOrDeny.getParamValue());
+ }
+ } else {
+ // restriction but not for a specific privilege
+ String restrictionName = matcher.group(1);
+ String paramName = entry.getKey();
+ LocalRestriction localRestriction = toLocalRestriction(request, srMap, restrictionName, paramName);
+ generalRestrictions.removeIf(r -> r.getName().equals(localRestriction.getName()));
+ generalRestrictions.add(localRestriction);
+ }
+ }
+
+ // apply the general restrictions to any already existing privilege that was not posted
+ // with new state
+ if (!generalRestrictions.isEmpty()) {
+ for (Entry<Privilege, LocalPrivilege> entry : privilegeToLocalPrivilegesMap.entrySet()) {
+ Privilege p = entry.getKey();
+ if (!privilegeToParamValuesMap.containsKey(p)) {
+ LocalPrivilege lp = entry.getValue();
+ applyPrivilegeAndRestrictions(privilegeToLocalPrivilegesMap, p,
+ lp.isAllow(), generalRestrictions,
+ lp.isDeny(), generalRestrictions);
+ }
+ }
+ }
+
+ List<Entry<Privilege, Set<String>>> sortedEntries = new ArrayList<>(privilegeToParamValuesMap.entrySet());
// sort the entries to process the most shallow last
Collections.sort(sortedEntries, (e1, e2) -> privilegeLongestDepthMap.get(e1.getKey()).compareTo(privilegeLongestDepthMap.get(e2.getKey())));
- for (Entry<Privilege, String> entry : sortedEntries) {
- String paramName = entry.getValue();
+ for (Entry<Privilege, Set<String>> entry : sortedEntries) {
+ Set<String> paramValues = entry.getValue();
Privilege privilege = entry.getKey();
- String [] paramValues = request.getParameterValues(paramName);
// convert and sort the values to ensure that allow goes after
// deny or none when there is a conflict
- List<PrivilegeValues> privilegeValues = Stream.of(paramValues)
+ List<PrivilegeValues> privilegeValues = paramValues.stream()
.map(PrivilegeValues::valueOfParam)
.sorted((v1, v2) -> Integer.compare(v2.ordinal(), v1.ordinal()))
.collect(Collectors.toList());
+ boolean none = false;
+ boolean allow = false;
+ Set<LocalRestriction> allowRestrictions = Collections.emptySet();
+ boolean deny = false;
+ Set<LocalRestriction> denyRestrictions = Collections.emptySet();
for (PrivilegeValues value : privilegeValues) {
switch (value) {
case DENY:
case DENIED:
- PrivilegesHelper.deny(privilegeToLocalPrivilegesMap, Collections.emptySet(), Collections.singleton(privilege));
+ deny = true;
+ denyRestrictions = postedRestrictionsForPrivilege(request, srMap, privilege, value, generalRestrictions);
break;
case ALLOW:
case GRANTED:
- PrivilegesHelper.allow(privilegeToLocalPrivilegesMap, Collections.emptySet(), Collections.singleton(privilege));
+ allow = true;
+ allowRestrictions = postedRestrictionsForPrivilege(request, srMap, privilege, value, generalRestrictions);
break;
case NONE:
- PrivilegesHelper.none(privilegeToLocalPrivilegesMap, Collections.singleton(privilege));
+ none = true;
break;
default:
break;
}
}
+ if (none) {
+ PrivilegesHelper.none(privilegeToLocalPrivilegesMap, Collections.singleton(privilege));
+ }
+ applyPrivilegeAndRestrictions(privilegeToLocalPrivilegesMap, privilege,
+ allow, allowRestrictions,
+ deny, denyRestrictions);
+ }
+ }
+
+ /**
+ * Apply the privilege and restrictions to the local privileges
+ *
+ * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items
+ * @param p the privilege
+ * @param allow true if the privilege is to be allowed
+ * @param allowRestrictions restrictions (if any) for the allow
+ * @param deny true if the privilege is to be denied
+ * @param denyRestrictions restrictions (if any) for the deny
+ */
+ protected void applyPrivilegeAndRestrictions(
+ @NotNull Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap,
+ @NotNull Privilege p,
+ boolean allow, @NotNull Set<LocalRestriction> allowRestrictions,
+ boolean deny, @NotNull Set<LocalRestriction> denyRestrictions) throws RepositoryException {
+ if (allow) {
+ // clear out the state that we are replacing
+ PrivilegesHelper.unallowRestrictions(privilegeToLocalPrivilegesMap,
+ allowRestrictions.stream().map(LocalRestriction::getName).collect(Collectors.toSet()),
+ Collections.singleton(p));
+ }
+ if (deny) {
+ // clear out the state that we are replacing
+ PrivilegesHelper.undenyRestrictions(privilegeToLocalPrivilegesMap,
+ denyRestrictions.stream().map(LocalRestriction::getName).collect(Collectors.toSet()),
+ Collections.singleton(p));
+ }
+ if (allow && deny) {
+ PrivilegesHelper.allowAndDeny(privilegeToLocalPrivilegesMap,
+ allow, allowRestrictions, deny, denyRestrictions,
+ Collections.singleton(p));
+ } else if (allow) {
+ PrivilegesHelper.allow(privilegeToLocalPrivilegesMap, allowRestrictions, Collections.singleton(p));
+ } else if (deny) {
+ PrivilegesHelper.deny(privilegeToLocalPrivilegesMap, denyRestrictions, Collections.singleton(p));
}
}
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java
index 666082a..e5635ea 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java
@@ -262,6 +262,117 @@
}
@Test
+ public void testAllowAndDenyBoth() throws RepositoryException {
+ Map<Privilege, LocalPrivilege> merged = new HashMap<>();
+
+ // allow jcr:modifyProperties
+ PrivilegesHelper.allowAndDeny(merged,
+ true, Collections.emptySet(),
+ true, Collections.emptySet(),
+ Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
+
+ assertAllowSetHasModifyPropertiesLeafs(merged);
+ assertDenySetIsEmpty(merged);
+ }
+
+ @Test
+ public void testAllowAndDenyBothWithSameRestrictions() throws RepositoryException {
+ Map<Privilege, LocalPrivilege> merged = new HashMap<>();
+
+ // allow jcr:modifyProperties
+ PrivilegesHelper.allowAndDeny(merged,
+ true, Collections.singleton(new LocalRestriction(rd(AccessControlConstants.REP_GLOB), val("/hello"))),
+ true, Collections.singleton(new LocalRestriction(rd(AccessControlConstants.REP_GLOB), val("/hello"))),
+ Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
+
+ assertAllowSetHasModifyPropertiesLeafs(merged);
+ assertDenySetIsEmpty(merged);
+ }
+
+ protected void assertDenySetIsEmpty(Map<Privilege, LocalPrivilege> merged) {
+ Set<Privilege> denySet = merged.values().stream()
+ .filter(lp -> lp.isDeny())
+ .map(lp -> lp.getPrivilege())
+ .collect(Collectors.toSet());
+ assertTrue(denySet.isEmpty());
+ }
+
+ protected void assertAllowSetHasModifyPropertiesLeafs(Map<Privilege, LocalPrivilege> merged)
+ throws RepositoryException {
+ Set<Privilege> allowSet = merged.values().stream()
+ .filter(lp -> lp.isAllow())
+ .map(lp -> lp.getPrivilege())
+ .collect(Collectors.toSet());
+ assertThat(allowSet.size(), equalTo(3));
+ assertThat(allowSet, not(hasItems(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES))));
+ assertThat(allowSet, hasItems(
+ priv(PrivilegeConstants.REP_ADD_PROPERTIES),
+ priv(PrivilegeConstants.REP_ALTER_PROPERTIES),
+ priv(PrivilegeConstants.REP_REMOVE_PROPERTIES)));
+ }
+
+ protected void assertDenySetHasModifyPropertiesLeafs(Map<Privilege, LocalPrivilege> merged)
+ throws RepositoryException {
+ Set<Privilege> denySet = merged.values().stream()
+ .filter(lp -> lp.isDeny())
+ .map(lp -> lp.getPrivilege())
+ .collect(Collectors.toSet());
+ assertThat(denySet.size(), equalTo(3));
+ assertThat(denySet, not(hasItems(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES))));
+ assertThat(denySet, hasItems(
+ priv(PrivilegeConstants.REP_ADD_PROPERTIES),
+ priv(PrivilegeConstants.REP_ALTER_PROPERTIES),
+ priv(PrivilegeConstants.REP_REMOVE_PROPERTIES)));
+ }
+
+
+ @Test
+ public void testAllowAndDenyBothWithDifferentRestrictions() throws RepositoryException {
+ Map<Privilege, LocalPrivilege> merged = new HashMap<>();
+
+ // allow jcr:modifyProperties
+ PrivilegesHelper.allowAndDeny(merged,
+ true, Collections.singleton(new LocalRestriction(rd(AccessControlConstants.REP_GLOB), val("/hello"))),
+ true, Collections.singleton(new LocalRestriction(rd(AccessControlConstants.REP_GLOB), val("/hello2"))),
+ Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
+
+ assertAllowSetHasModifyPropertiesLeafs(merged);
+ assertDenySetHasModifyPropertiesLeafs(merged);
+ }
+
+ @Test
+ public void testAllowAndDenyOnlyAllow() throws RepositoryException {
+ Map<Privilege, LocalPrivilege> merged = new HashMap<>();
+
+ // allow jcr:modifyProperties
+ PrivilegesHelper.allowAndDeny(merged,
+ true, Collections.emptySet(),
+ false, Collections.emptySet(),
+ Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
+
+ assertAllowSetHasModifyPropertiesLeafs(merged);
+ assertDenySetIsEmpty(merged);
+ }
+
+ @Test
+ public void testAllowAndDenyOnlyDeny() throws RepositoryException {
+ Map<Privilege, LocalPrivilege> merged = new HashMap<>();
+
+ // allow jcr:modifyProperties
+ PrivilegesHelper.allowAndDeny(merged,
+ false, Collections.emptySet(),
+ true, Collections.emptySet(),
+ Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
+
+ Set<Privilege> allowSet = merged.values().stream()
+ .filter(lp -> lp.isAllow())
+ .map(lp -> lp.getPrivilege())
+ .collect(Collectors.toSet());
+ assertTrue(allowSet.isEmpty());
+ assertDenySetHasModifyPropertiesLeafs(merged);
+ }
+
+ @Test
public void testAllow() throws RepositoryException {
Map<Privilege, LocalPrivilege> merged = new HashMap<>();
@@ -269,17 +380,7 @@
PrivilegesHelper.allow(merged, Collections.emptySet(),
Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
- Set<Privilege> allowSet = merged.values().stream()
- .filter(lp -> lp.isAllow())
- .map(lp -> lp.getPrivilege())
- .collect(Collectors.toSet());
-
- assertThat(allowSet.size(), equalTo(3));
- assertThat(allowSet, not(hasItems(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES))));
- assertThat(allowSet, hasItems(
- priv(PrivilegeConstants.REP_ADD_PROPERTIES),
- priv(PrivilegeConstants.REP_ALTER_PROPERTIES),
- priv(PrivilegeConstants.REP_REMOVE_PROPERTIES)));
+ assertAllowSetHasModifyPropertiesLeafs(merged);
}
@Test
@@ -290,17 +391,7 @@
PrivilegesHelper.allow(merged, Collections.emptySet(),
Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
- Set<Privilege> allowSet = merged.values().stream()
- .filter(lp -> lp.isAllow())
- .map(lp -> lp.getPrivilege())
- .collect(Collectors.toSet());
-
- assertThat(allowSet.size(), equalTo(3));
- assertThat(allowSet, not(hasItems(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES))));
- assertThat(allowSet, hasItems(
- priv(PrivilegeConstants.REP_ADD_PROPERTIES),
- priv(PrivilegeConstants.REP_ALTER_PROPERTIES),
- priv(PrivilegeConstants.REP_REMOVE_PROPERTIES)));
+ assertAllowSetHasModifyPropertiesLeafs(merged);
// unallow jcr:modifyProperties
PrivilegesHelper.unallow(merged,
@@ -322,17 +413,7 @@
PrivilegesHelper.deny(merged, Collections.emptySet(),
Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
- Set<Privilege> denySet = merged.values().stream()
- .filter(lp -> lp.isDeny())
- .map(lp -> lp.getPrivilege())
- .collect(Collectors.toSet());
-
- assertThat(denySet.size(), equalTo(3));
- assertThat(denySet, not(hasItems(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES))));
- assertThat(denySet, hasItems(
- priv(PrivilegeConstants.REP_ADD_PROPERTIES),
- priv(PrivilegeConstants.REP_ALTER_PROPERTIES),
- priv(PrivilegeConstants.REP_REMOVE_PROPERTIES)));
+ assertDenySetHasModifyPropertiesLeafs(merged);
}
@Test
@@ -343,17 +424,7 @@
PrivilegesHelper.deny(merged, Collections.emptySet(),
Collections.singleton(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES)));
- Set<Privilege> denySet = merged.values().stream()
- .filter(lp -> lp.isDeny())
- .map(lp -> lp.getPrivilege())
- .collect(Collectors.toSet());
-
- assertThat(denySet.size(), equalTo(3));
- assertThat(denySet, not(hasItems(priv(PrivilegeConstants.JCR_MODIFY_PROPERTIES))));
- assertThat(denySet, hasItems(
- priv(PrivilegeConstants.REP_ADD_PROPERTIES),
- priv(PrivilegeConstants.REP_ALTER_PROPERTIES),
- priv(PrivilegeConstants.REP_REMOVE_PROPERTIES)));
+ assertDenySetHasModifyPropertiesLeafs(merged);
// undeny jcr:modifyProperties
PrivilegesHelper.undeny(merged,
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/AccessManagerClientTestSupport.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/AccessManagerClientTestSupport.java
index 68c19d6..5737aa1 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/AccessManagerClientTestSupport.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/AccessManagerClientTestSupport.java
@@ -291,14 +291,14 @@
assertNotNull(privilegeObj);
String key = privilegeState.toString();;
if (expectedForAllow) {
- assertTrue("Expected privilege " + privilegeName + " to have key '" + key,
+ assertTrue("Expected privilege " + privilegeName + " to have key: " + key,
privilegeObj.containsKey(key));
JsonValue jsonValue = privilegeObj.get(key);
if (verifyAce != null) {
verifyAce.verify(jsonValue);
}
} else {
- assertFalse("Did not expect privilege " + privilegeName + " to have key '" + key,
+ assertFalse("Did not expect privilege " + privilegeName + " to have key: " + key,
privilegeObj.containsKey(key));
}
}
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/GetEaceIT.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/GetEaceIT.java
index 0e71730..42eca5e 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/GetEaceIT.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/GetEaceIT.java
@@ -290,7 +290,7 @@
assertNotNull(jsonContent);
JsonObject jsonObject = parseJson(jsonContent);
assertTrue("Expected childPropOne property", jsonObject.containsKey("childPropOne"));
- assertFalse("Did not expected childPropTwo property", jsonObject.containsKey("childPropTwo"));
+ assertFalse("Did not expect childPropTwo property", jsonObject.containsKey("childPropTwo"));
// add ACE to the child to make the other property readable
List<NameValuePair> postParams2 = new AcePostParamsBuilder(testUserId)
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java
index 63266ec..801b159 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java
@@ -40,6 +40,7 @@
import javax.servlet.http.HttpServletResponse;
import org.apache.http.NameValuePair;
+import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
@@ -2365,4 +2366,100 @@
assertPrivilege(privilegesObject, true, PrivilegeValues.DENY, PrivilegeConstants.JCR_WRITE, verifyRestrictions);
}
+ /**
+ * Test to verify adding an ACE does not include an allow aggregate privilege
+ * when there is a deny child privilege with the same restrictions as the parent
+ */
+ @Test
+ public void testNoAggregateAllowWhenDenyChildHasSameRestriction() throws IOException, JsonException {
+ testUserId = createTestUser();
+ testFolderUrl = createTestFolder();
+
+ // update the ACE
+ List<NameValuePair> postParams = new AcePostParamsBuilder(testUserId)
+ //allow the child privileges with varying restrictions
+ .withPrivilege(PrivilegeConstants.REP_READ_NODES, PrivilegeValues.ALLOW)
+ .withPrivilege(PrivilegeConstants.REP_READ_PROPERTIES, PrivilegeValues.ALLOW)
+ .withPrivilegeRestriction(PrivilegeValues.ALLOW, PrivilegeConstants.REP_READ_PROPERTIES,
+ AccessControlConstants.REP_ITEM_NAMES, new String[] {JcrConstants.JCR_CREATED, JcrConstants.JCR_PRIMARYTYPE})
+ //deny a child privilege with different restrictions than the same allow privilege
+ .withPrivilege(PrivilegeConstants.REP_READ_PROPERTIES, PrivilegeValues.DENY)
+ .build();
+ addOrUpdateAce(testFolderUrl, postParams);
+
+ JsonObject user = getAce(testFolderUrl, testUserId);
+ assertNotNull(user);
+ assertEquals(testUserId, user.getString("principal"));
+
+ JsonObject privilegesObject = user.getJsonObject("privileges");
+ assertNotNull(privilegesObject);
+ assertEquals(2, privilegesObject.size());
+
+ VerifyAce verifyRestrictions = jsonValue -> {
+ assertNotNull(jsonValue);
+ assertTrue(jsonValue instanceof JsonObject);
+ JsonObject restrictionsObj = (JsonObject)jsonValue;
+
+ JsonValue customValue = restrictionsObj.get(AccessControlConstants.REP_ITEM_NAMES);
+ assertNotNull(customValue);
+ assertTrue(customValue instanceof JsonArray);
+ assertEquals(2, ((JsonArray)customValue).size());
+ assertEquals(JcrConstants.JCR_CREATED, ((JsonArray)customValue).getString(0));
+ assertEquals(JcrConstants.JCR_PRIMARYTYPE, ((JsonArray)customValue).getString(1));
+ };
+ //allow privilege
+ assertPrivilege(privilegesObject, true, PrivilegeValues.ALLOW, PrivilegeConstants.REP_READ_NODES, verifyTrue);
+ assertPrivilege(privilegesObject, true, PrivilegeValues.ALLOW, PrivilegeConstants.REP_READ_PROPERTIES, verifyRestrictions);
+ //deny privilege
+ assertPrivilege(privilegesObject, true, PrivilegeValues.DENY, PrivilegeConstants.REP_READ_PROPERTIES, verifyTrue);
+ }
+
+ /**
+ * Test to verify adding an ACE does not include a deny aggregate privilege
+ * when there is an allow child privilege with the same restrictions as the parent
+ */
+ @Test
+ public void testNoAggregateDenyWhenAllowChildHasSameRestriction() throws IOException, JsonException {
+ testUserId = createTestUser();
+ testFolderUrl = createTestFolder();
+
+ // update the ACE
+ List<NameValuePair> postParams = new AcePostParamsBuilder(testUserId)
+ //deny the child privileges with varying restrictions
+ .withPrivilege(PrivilegeConstants.REP_READ_NODES, PrivilegeValues.DENY)
+ .withPrivilege(PrivilegeConstants.REP_READ_PROPERTIES, PrivilegeValues.DENY)
+ .withPrivilegeRestriction(PrivilegeValues.DENY, PrivilegeConstants.REP_READ_PROPERTIES,
+ AccessControlConstants.REP_ITEM_NAMES, new String[] {JcrConstants.JCR_CREATED, JcrConstants.JCR_PRIMARYTYPE})
+ //allow a child privilege with different restrictions than the same deny privilege
+ .withPrivilege(PrivilegeConstants.REP_READ_PROPERTIES, PrivilegeValues.ALLOW)
+ .build();
+ addOrUpdateAce(testFolderUrl, postParams);
+
+ JsonObject user = getAce(testFolderUrl, testUserId);
+ assertNotNull(user);
+ assertEquals(testUserId, user.getString("principal"));
+
+ JsonObject privilegesObject = user.getJsonObject("privileges");
+ assertNotNull(privilegesObject);
+ assertEquals(2, privilegesObject.size());
+
+ VerifyAce verifyRestrictions = jsonValue -> {
+ assertNotNull(jsonValue);
+ assertTrue(jsonValue instanceof JsonObject);
+ JsonObject restrictionsObj = (JsonObject)jsonValue;
+
+ JsonValue customValue = restrictionsObj.get(AccessControlConstants.REP_ITEM_NAMES);
+ assertNotNull(customValue);
+ assertTrue(customValue instanceof JsonArray);
+ assertEquals(2, ((JsonArray)customValue).size());
+ assertEquals(JcrConstants.JCR_CREATED, ((JsonArray)customValue).getString(0));
+ assertEquals(JcrConstants.JCR_PRIMARYTYPE, ((JsonArray)customValue).getString(1));
+ };
+ //allow privilege
+ assertPrivilege(privilegesObject, true, PrivilegeValues.ALLOW, PrivilegeConstants.REP_READ_PROPERTIES, verifyTrue);
+ //deny privilege
+ assertPrivilege(privilegesObject, true, PrivilegeValues.DENY, PrivilegeConstants.REP_READ_NODES, verifyTrue);
+ assertPrivilege(privilegesObject, true, PrivilegeValues.DENY, PrivilegeConstants.REP_READ_PROPERTIES, verifyRestrictions);
+ }
+
}