blob: cc8dd9f7857312ba4ba4ab1bf9c2ed22729066c7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.jcr.repoinit.impl;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.repoinit.parser.operations.AclLine;
import org.apache.sling.repoinit.parser.operations.RestrictionClause;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.Privilege;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import static org.apache.sling.repoinit.parser.operations.AclLine.ID_DELIMINATOR;
import static org.apache.sling.repoinit.parser.operations.AclLine.PATH_HOME;
import static org.apache.sling.repoinit.parser.operations.AclLine.PATH_REPOSITORY;
import static org.apache.sling.repoinit.parser.operations.AclLine.PROP_PATHS;
import static org.apache.sling.repoinit.parser.operations.AclLine.PROP_PRIVILEGES;
import static org.apache.sling.repoinit.parser.operations.AclLine.SUBTREE_DELIMINATOR;
/** Utilities for ACL management */
public class AclUtil {
private static final Logger LOG = LoggerFactory.getLogger(AclUtil.class);
public static JackrabbitAccessControlManager getJACM(Session s) throws RepositoryException {
final AccessControlManager acm = s.getAccessControlManager();
checkState((acm instanceof JackrabbitAccessControlManager), "AccessControlManager is not a JackrabbitAccessControlManager:" + acm.getClass().getName());
return (JackrabbitAccessControlManager) acm;
}
/**
* Converts RestrictionClauses to structure consumable by
* jackrabbit
* @param list
* @param jacl
* @param s
* @return
* @throws RepositoryException
*/
private static LocalRestrictions createLocalRestrictions(List<RestrictionClause> list, JackrabbitAccessControlList jacl, Session s) throws RepositoryException {
Map<String,Value> restrictions = new HashMap<>();
Map<String,Value[]> mvrestrictions = new HashMap<>();
if(list != null && !list.isEmpty()){
ValueFactory vf = s.getValueFactory();
for(RestrictionClause rc : list){
String restrictionName = rc.getName();
int type = jacl.getRestrictionType(restrictionName);
boolean isMvRestriction = jacl.isMultiValueRestriction(restrictionName);
Value[] values = new Value[rc.getValues().size()];
for(int i=0;i<values.length;i++) {
values[i] = vf.createValue(rc.getValues().get(i),type);
}
if("rep:glob".equals(restrictionName) && values.length == 0) {
// SLING-7280 - special case for rep:glob which supports an empty string
// to mean "no values"
restrictions.put(restrictionName, vf.createValue(""));
} else if (isMvRestriction) {
mvrestrictions.put(restrictionName, values);
} else {
checkState(values.length == 1, "Expected just one value for single valued restriction with name " + restrictionName);
restrictions.put(restrictionName, values[0]);
}
}
}
return new LocalRestrictions(restrictions,mvrestrictions);
}
public static void setAcl(Session session, List<String> principals, List<String> paths, List<String> privileges, boolean isAllow)
throws RepositoryException {
setAcl(session,principals,paths,privileges,isAllow,Arrays.asList(new RestrictionClause[]{}));
}
public static void setAcl(Session session, List<String> principals, List<String> paths, List<String> privileges, boolean isAllow, List<RestrictionClause> restrictionClauses)
throws RepositoryException {
for (String jcrPath : getJcrPaths(session, paths)) {
if (jcrPath != null && !session.nodeExists(jcrPath)) {
throw new PathNotFoundException("Cannot set ACL on non-existent path " + jcrPath);
}
setAcl(session, principals, jcrPath, privileges, isAllow, restrictionClauses);
}
}
private static void setAcl(Session session, List<String> principals, String jcrPath, List<String> privileges, boolean isAllow, List<RestrictionClause> restrictionClauses)
throws RepositoryException {
AccessControlManager acMgr = session.getAccessControlManager();
final String [] privArray = privileges.toArray(new String[privileges.size()]);
final Privilege[] jcrPriv = AccessControlUtils.privilegesFromNames(acMgr, privArray);
JackrabbitAccessControlList acl = getAccessControlList(acMgr, jcrPath, true);
checkState(acl != null, "No JackrabbitAccessControlList available for path " + jcrPath);
LocalRestrictions localRestrictions = createLocalRestrictions(restrictionClauses, acl, session);
AccessControlEntry[] existingAces = acl.getAccessControlEntries();
boolean changed = false;
for (String name : principals) {
Principal principal = AccessControlUtils.getPrincipal(session, name);
if (principal == null) {
// backwards compatibility: fallback to original code treating principal name as authorizable ID (see SLING-8604)
final Authorizable authorizable = UserUtil.getAuthorizable(session, name);
checkState(authorizable != null, "Authorizable not found:" + name);
principal = authorizable.getPrincipal();
}
checkState(principal != null, "Principal not found: " + name);
LocalAccessControlEntry newAce = new LocalAccessControlEntry(principal, jcrPriv, isAllow, localRestrictions);
if (contains(existingAces, newAce)) {
LOG.info("Not adding {} to path {} since an equivalent access control entry already exists", newAce, jcrPath);
continue;
}
acl.addEntry(newAce.principal, newAce.privileges, newAce.isAllow,
newAce.restrictions.getRestrictions(), newAce.restrictions.getMVRestrictions());
changed = true;
}
if ( changed ) {
acMgr.setPolicy(jcrPath, acl);
}
}
public static void setRepositoryAcl(Session session, List<String> principals, List<String> privileges, boolean isAllow, List<RestrictionClause> restrictionClauses)
throws RepositoryException {
setAcl(session, principals, (String)null, privileges, isAllow, restrictionClauses);
}
/**
* Remove resource-based access control setup for the principal with the given name.
*
* @param session
* @param principalName
* @throws RepositoryException
*/
public static void removePolicy(@NotNull Session session, @NotNull final String principalName) throws RepositoryException {
Principal principal = AccessControlUtils.getPrincipal(session, principalName);
if (principal == null) {
LOG.info("Principal {} does not exist.", principalName);
// using PrincipalImpl will prevent 'removePolicy' from failing with AccessControlException
// in case import-behavior is configured to be ABORT.
principal = new PrincipalImpl(principalName);
}
JackrabbitAccessControlManager acMgr = getJACM(session);
for (JackrabbitAccessControlPolicy policy : acMgr.getPolicies(principal)) {
// make sure not to remove the principal-based access control list but instead only drop
// resource-based access control content for the given principal
if (policy instanceof JackrabbitAccessControlList && !(policy instanceof PrincipalAccessControlList)) {
acMgr.removePolicy(policy.getPath(), policy);
}
}
}
/**
* Remove resource-based access control setup defined for the specified paths.
*
* @param session
* @param paths
* @throws RepositoryException
*/
public static void removePolicies(@NotNull Session session, @NotNull List<String> paths) throws RepositoryException {
AccessControlManager acMgr = session.getAccessControlManager();
for (String jcrPath : getJcrPaths(session, paths)) {
if (!isValidPath(session, jcrPath)) {
LOG.info("Cannot remove ACL; no node at {} ", jcrPath);
continue;
}
LOG.info("Removing access control policy at {}", jcrPath);
JackrabbitAccessControlList acl = getAccessControlList(acMgr, jcrPath, false);
if (acl == null) {
LOG.info("No ACL to remove at path {}", jcrPath);
} else {
acMgr.removePolicy(jcrPath, acl);
}
}
}
public static void removeEntries(@NotNull Session session, @NotNull List<String> principals, @NotNull List<String> paths) throws RepositoryException {
Set<String> principalNames = new HashSet<>(principals);
AccessControlManager acMgr = session.getAccessControlManager();
for (String jcrPath : getJcrPaths(session, paths)) {
if (!isValidPath(session, jcrPath)) {
LOG.info("Cannot remove access control entries on non-existent path {}", jcrPath);
} else {
JackrabbitAccessControlList acl = getAccessControlList(acMgr, jcrPath, false);
if (acl != null) {
boolean modified = false;
for (AccessControlEntry ace : acl.getAccessControlEntries()) {
if (principalNames.contains(ace.getPrincipal().getName())) {
acl.removeAccessControlEntry(ace);
modified = true;
}
}
if (modified) {
acMgr.setPolicy(jcrPath, acl);
}
} else {
LOG.info("Cannot remove access control entries for principal(s) {}. No ACL at {}", principalNames, jcrPath);
}
}
}
}
public static void removeEntries(@NotNull Session session, @NotNull List<String> principals, @NotNull List<String> paths, List<String> privileges, boolean isAllow, List<RestrictionClause> restrictionClauses) throws RepositoryException {
Set<String> principalNames = new HashSet<>(principals);
AccessControlManager acMgr = session.getAccessControlManager();
for (String jcrPath : getJcrPaths(session, paths)) {
if (!isValidPath(session, jcrPath)) {
LOG.info("Cannot remove access control entries on non-existent path {}", jcrPath);
} else {
JackrabbitAccessControlList acl = getAccessControlList(acMgr, jcrPath, false);
if (acl != null) {
boolean modified = false;
LocalRestrictions restr = createLocalRestrictions(restrictionClauses, acl, session);
Privilege[] privs = AccessControlUtils.privilegesFromNames(acMgr, privileges.toArray(new String[0]));
for (AccessControlEntry ace : acl.getAccessControlEntries()) {
Principal principal = ace.getPrincipal();
if (!principalNames.contains(principal.getName())) {
continue;
}
LocalAccessControlEntry entry = new LocalAccessControlEntry(ace.getPrincipal(), privs, isAllow, restr);
if (entry.isEqual(ace)) {
acl.removeAccessControlEntry(ace);
modified = true;
}
}
if (modified) {
acMgr.setPolicy(jcrPath, acl);
} else {
LOG.info("No matching access control entry found to remove for principals {} at {}. Expected entry with isAllow={}, privileges={}, restrictions={}", principalNames, jcrPath, isAllow, privileges, restrictionClauses);
}
} else {
LOG.info("Cannot remove access control entries for principal(s) {}. No ACL at {}", principalNames, jcrPath);
}
}
}
}
public static void setPrincipalAcl(Session session, String principalName, Collection<AclLine> lines) throws RepositoryException {
final JackrabbitAccessControlManager acMgr = getJACM(session);
Principal principal = AccessControlUtils.getPrincipal(session, principalName);
if (principal == null) {
// due to transient nature of the repo-init the principal lookup may not succeed if completed through query
// -> save transient changes and retry principal lookup
session.save();
principal = AccessControlUtils.getPrincipal(session, principalName);
checkState(principal != null, "Principal not found: " + principalName);
}
final PrincipalAccessControlList acl = getPrincipalAccessControlList(acMgr, principal, true);
boolean modified = false;
for (AclLine line : lines) {
AclLine.Action action = line.getAction();
List<String> jcrPaths = getJcrPaths(session, line.getProperty(PROP_PATHS));
if (action == AclLine.Action.DENY) {
throw new AccessControlException("PrincipalAccessControlList doesn't support 'deny' entries.");
} else if (action == AclLine.Action.REMOVE) {
throw new RuntimeException(AclLine.Action.REMOVE + " is not supported. Use 'remove principal acl' instead.");
} else if (action == AclLine.Action.REMOVE_ALL) {
if (removePrincipalEntries(acl, principalName, entry -> jcrPaths.contains(entry.getEffectivePath()))) {
modified = true;
}
} else if (action == AclLine.Action.ALLOW) {
final Privilege[] privileges = AccessControlUtils.privilegesFromNames(acMgr, line.getProperty(PROP_PRIVILEGES).toArray(new String[0]));
for (String effectivePath : jcrPaths) {
if (acl == null) {
// no PrincipalAccessControlList available: don't fail if an equivalent path-based entry with the same definition exists
// or if there exists no node at the effective path (unable to evaluate path-based entries).
LOG.info("No PrincipalAccessControlList available for principal {}", principal);
if (!containsEquivalentEntry(session, effectivePath, principal, privileges, true, line.getRestrictions())) {
LOG.warn("No equivalent path-based entry exists for principal {} and effective path {} ", principal.getName(), effectivePath);
return;
}
} else {
final LocalRestrictions restrictions = createLocalRestrictions(line.getRestrictions(), acl, session);
final boolean added = acl.addEntry(effectivePath, privileges, restrictions.getRestrictions(), restrictions.getMVRestrictions());
if (!added) {
LOG.info("Equivalent principal-based entry already exists for principal {} and effective path {} ", principalName, effectivePath);
} else {
modified = true;
}
}
}
} else {
throw new RuntimeException("Unknown action " + action);
}
}
if (modified) {
acMgr.setPolicy(acl.getPath(), acl);
}
}
public static void removePrincipalEntries(Session session, String principalName, Collection<AclLine> lines) throws RepositoryException {
final JackrabbitAccessControlManager acMgr = getJACM(session);
Principal principal = AccessControlUtils.getPrincipal(session, principalName);
if (principal == null) {
// due to transient nature of the repo-init the principal lookup may not succeed if completed through query
// -> save transient changes and retry principal lookup
session.save();
principal = AccessControlUtils.getPrincipal(session, principalName);
checkState(principal != null, "Principal not found: " + principalName);
}
final PrincipalAccessControlList acl = getPrincipalAccessControlList(acMgr, principal, true);
boolean modified = false;
for (AclLine line : lines) {
List<String> jcrPaths = getJcrPaths(session, line.getProperty(PROP_PATHS));
LocalRestrictions restr = createLocalRestrictions(line.getRestrictions(), acl, session);
List<String> privNames = line.getProperty(PROP_PRIVILEGES);
Privilege[] privs = AccessControlUtils.privilegesFromNames(acMgr, privNames.toArray(new String[0]));
Predicate<PrincipalAccessControlList.Entry> predicate = entry -> {
if (!jcrPaths.contains(entry.getEffectivePath())) {
return false;
}
LocalAccessControlEntry lace = new LocalAccessControlEntry(entry.getPrincipal(), privs, line.getAction()== AclLine.Action.ALLOW, restr);
return lace.isEqual(entry);
};
if (removePrincipalEntries(acl, principalName, predicate)) {
modified = true;
} else {
LOG.info("No matching access control entry found to remove for principal {} at {}. Expected entry with isAllow={}, privileges={}, restrictions={}", principalName, jcrPaths, line.getAction(), privNames, line.getRestrictions());
}
}
if (modified) {
acMgr.setPolicy(acl.getPath(), acl);
}
}
/**
* Remove principal-based access control setup for the principal with the given name.
*
* @param session
* @param principalName
* @throws RepositoryException
*/
public static void removePrincipalPolicy(@NotNull Session session, @NotNull String principalName) throws RepositoryException {
Principal principal = AccessControlUtils.getPrincipal(session, principalName);
if (principal == null) {
LOG.info("Cannot remove principal-based ACL. Principal {} does not exist.", principalName);
return;
}
JackrabbitAccessControlManager acMgr = getJACM(session);
PrincipalAccessControlList acl = getPrincipalAccessControlList(acMgr, principal, false);
if (acl == null) {
LOG.info("Cannot remove principal-based ACL for principal {}. No such policy exists.", principalName);
} else {
acMgr.removePolicy(acl.getPath(), acl);
}
}
private static boolean isValidPath(@NotNull Session session, @Nullable String jcrPath) throws RepositoryException {
return jcrPath == null || session.nodeExists(jcrPath);
}
@Nullable
private static JackrabbitAccessControlList getAccessControlList(@NotNull AccessControlManager acMgr,
@Nullable String path, boolean includeApplicable) throws RepositoryException {
if (includeApplicable) {
return AccessControlUtils.getAccessControlList(acMgr, path);
} else {
for (AccessControlPolicy policy : acMgr.getPolicies(path)) {
if (policy instanceof JackrabbitAccessControlList) {
return (JackrabbitAccessControlList) policy;
}
}
return null;
}
}
@Nullable
private static PrincipalAccessControlList getPrincipalAccessControlList(@NotNull JackrabbitAccessControlManager acMgr,
@NotNull Principal principal, boolean includeApplicable) throws RepositoryException {
PrincipalAccessControlList acl = null;
for (JackrabbitAccessControlPolicy policy : acMgr.getPolicies(principal)) {
if (policy instanceof PrincipalAccessControlList) {
acl = (PrincipalAccessControlList) policy;
break;
}
}
if (acl == null && includeApplicable) {
for (JackrabbitAccessControlPolicy policy : acMgr.getApplicablePolicies(principal)) {
if (policy instanceof PrincipalAccessControlList) {
acl = (PrincipalAccessControlList) policy;
break;
}
}
}
return acl;
}
private static boolean removePrincipalEntries(@Nullable PrincipalAccessControlList acl, @NotNull String principalName, @NotNull Predicate<PrincipalAccessControlList.Entry> predicate) throws RepositoryException {
boolean modified = false;
if (acl == null) {
LOG.info("Cannot remove entries. No principal-based ACL for {}", principalName);
} else {
for (AccessControlEntry ace : acl.getAccessControlEntries()) {
if (ace instanceof PrincipalAccessControlList.Entry) {
PrincipalAccessControlList.Entry entry = (PrincipalAccessControlList.Entry) ace;
if (predicate.test(entry)) {
acl.removeAccessControlEntry(ace);
modified = true;
}
}
}
}
return modified;
}
@NotNull
private static List<String> getJcrPaths(@NotNull Session session, @NotNull List<String> paths) throws RepositoryException {
List<String> jcrPaths = new ArrayList<>(paths.size());
for (String path : paths) {
if (PATH_REPOSITORY.equals(path) || path == null || path.isEmpty()) {
jcrPaths.add(null);
} else if (path.startsWith(PATH_HOME)) {
int lastHashIndex = path.lastIndexOf(SUBTREE_DELIMINATOR);
checkState(lastHashIndex > -1, "Invalid format of home path: # deliminator expected.");
String subTreePath = path.substring(lastHashIndex+1);
for (String aPath : getAuthorizablePaths(session, path.substring(PATH_HOME.length(), lastHashIndex))) {
jcrPaths.add(aPath + subTreePath);
}
} else {
jcrPaths.add(path);
}
}
return jcrPaths;
}
@NotNull
private static Iterable<String> getAuthorizablePaths(@NotNull Session session, @NotNull String ids) throws RepositoryException {
List<String> paths = new ArrayList<>();
for (String id : Text.explode(ids, ID_DELIMINATOR)) {
Authorizable a = UserUtil.getAuthorizable(session, id);
if (a == null) {
throw new PathNotFoundException("Cannot resolve path of user/group with id '" + id + "'.");
}
paths.add(a.getPath());
}
return paths;
}
private static boolean containsEquivalentEntry(Session session, String absPath, Principal principal, Privilege[] privileges, boolean isAllow, List<RestrictionClause> restrictionList) throws RepositoryException {
if (absPath != null && !session.nodeExists(absPath)) {
LOG.info("Cannot determine existence of equivalent path-based entry for principal {}. No node at path {} ", principal.getName(), absPath);
return true;
}
for (AccessControlPolicy policy : session.getAccessControlManager().getPolicies(absPath)) {
if (policy instanceof JackrabbitAccessControlList) {
LocalRestrictions lr = createLocalRestrictions(restrictionList, ((JackrabbitAccessControlList) policy), session);
LocalAccessControlEntry newEntry = new LocalAccessControlEntry(principal, privileges, isAllow, lr);
if (contains(((JackrabbitAccessControlList) policy).getAccessControlEntries(), newEntry)) {
LOG.info("Equivalent path-based entry exists for principal {} and effective path {} ", newEntry.principal.getName(), absPath);
return true;
}
}
}
return false;
}
// visible for testing
static boolean contains(AccessControlEntry[] existingAces, LocalAccessControlEntry newAce) throws RepositoryException {
for (int i = 0 ; i < existingAces.length; i++) {
JackrabbitAccessControlEntry existingEntry = (JackrabbitAccessControlEntry) existingAces[i];
LOG.debug("Comparing {} with {}", newAce, toString(existingEntry));
if (newAce.isContainedIn(existingEntry)) {
return true;
}
}
return false;
}
private static String toString(JackrabbitAccessControlEntry entry) throws RepositoryException {
return "[" + entry.getClass().getSimpleName() + "# principal: "
+ "" + entry.getPrincipal() + ", privileges: " + Arrays.toString(entry.getPrivileges()) +
", isAllow: " + entry.isAllow() + ", restrictionNames: " + entry.getRestrictionNames() + "]";
}
private static void checkState(boolean expression, String msg) {
if (!expression) {
throw new IllegalStateException(msg);
}
}
/** Compare value arrays a and b, by set comparison.
* @param a first value array
* @param b second value array
* @return true if both value arrays contain the same elements,
* in whatever order.
*/
static boolean compareValues(Value[] a, Value[] b) {
if (a == null || b == null) {
return false;
}
if(a.length != b.length){
return false;
}
Set<Value> first = new HashSet<>(Arrays.asList(a));
Set<Value> second = new HashSet<>(Arrays.asList(b));
return first.equals(second);
}
/**
* Helper class which allows easy comparison of a local (proposed) access control entry with an existing one
*/
static class LocalAccessControlEntry {
private final Principal principal;
private final Privilege[] privileges;
private final boolean isAllow;
private final LocalRestrictions restrictions;
LocalAccessControlEntry(Principal principal, Privilege[] privileges, boolean isAllow) {
this(principal, privileges, isAllow, null);
}
LocalAccessControlEntry(Principal principal, Privilege[] privileges,
boolean isAllow, LocalRestrictions restrictions) {
this.principal = principal;
this.privileges = privileges;
this.isAllow = isAllow;
this.restrictions = restrictions != null ? restrictions : new LocalRestrictions();
}
public boolean isContainedIn(JackrabbitAccessControlEntry other) throws RepositoryException {
return other.getPrincipal().equals(principal) &&
contains(other.getPrivileges(), privileges) &&
other.isAllow() == isAllow &&
sameRestrictions(other);
}
public boolean isEqual(AccessControlEntry other) {
if (!(other instanceof JackrabbitAccessControlEntry)) {
return false;
}
try {
JackrabbitAccessControlEntry otherAce = (JackrabbitAccessControlEntry) other;
return other.getPrincipal().equals(principal) &&
equalPrivileges(other.getPrivileges(), privileges) &&
otherAce.isAllow() == isAllow &&
sameRestrictions(otherAce);
} catch (RepositoryException e) {
throw new RuntimeException("Cannot verify equivalence of access control entries", e);
}
}
private Set<Privilege> expandPrivileges(Privilege[] privileges){
Set<Privilege> expandedSet = new HashSet<>();
if(privileges != null){
for(Privilege privilege : privileges){
if(privilege.isAggregate()){
expandedSet.addAll(Arrays.asList(privilege.getAggregatePrivileges()));
} else {
expandedSet.add(privilege);
}
}
}
return expandedSet;
}
/**
* compares if restrictions present in jackrabbit access control entry
* is same as specified restrictions in repo init
* @param jace
* @return
* @throws RepositoryException
*/
private boolean sameRestrictions(JackrabbitAccessControlEntry jace) throws RepositoryException {
// total (multivalue and simple) number of restrictions should be same
if(jace.getRestrictionNames().length == (restrictions.size())){
for(String rn : jace.getRestrictionNames()){
Value[] oldValues = jace.getRestrictions(rn);
Value[] newValues = restrictions.getRestrictions().get(rn) != null
? new Value[]{restrictions.getRestrictions().get(rn)}
: restrictions.getMVRestrictions().get(rn);
if((newValues == null || newValues.length == 0) && (oldValues == null || oldValues.length == 0)){
continue;
}
if(!compareValues(newValues, oldValues)) {
return false;
}
}
return true;
}
return false;
}
private boolean contains(Privilege[] first, Privilege[] second) {
// we need to ensure that the privilege order is not taken into account, so we use sets
Set<Privilege> set1 = expandPrivileges(first);
Set<Privilege> set2 = expandPrivileges(second);
return set1.containsAll(set2);
}
private boolean equalPrivileges(Privilege[] first, Privilege[] second) {
Set<Privilege> set1 = expandPrivileges(first);
Set<Privilege> set2 = expandPrivileges(second);
return set1.equals(set2);
}
@Override
public String toString() {
return "[" + getClass().getSimpleName() + "# principal " + principal+ ", privileges: " + Arrays.toString(privileges) + ", isAllow : " + isAllow + "]";
}
}
/**
* Helper class to store both restrictions and multi value restrictions
* in ready to consume structure expected by jackrabbit
*/
private static class LocalRestrictions {
private Map<String,Value> restrictions;
private Map<String,Value[]> mvRestrictions;
public LocalRestrictions(){
restrictions = new HashMap<>();
mvRestrictions = new HashMap<>();
}
public LocalRestrictions(Map<String,Value> restrictions,Map<String,Value[]> mvRestrictions){
this.restrictions = restrictions != null ? restrictions : new HashMap<String,Value>();
this.mvRestrictions = mvRestrictions != null ? mvRestrictions : new HashMap<String,Value[]>();
}
public Map<String,Value> getRestrictions(){
return this.restrictions;
}
public Map<String,Value[]> getMVRestrictions(){
return this.mvRestrictions;
}
public int size(){
return this.restrictions.size() + this.mvRestrictions.size();
}
}
}