You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2021/07/21 07:52:32 UTC

[jackrabbit-oak] 01/02: OAK-9494 : Check if a privilege name is included in a set/array of Privileges obtained from AccessControlManager.getPrivileges

This is an automated email from the ASF dual-hosted git repository.

angela pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit 7d4d1f9314244db91d2d4f4468727e876e3c92f1
Author: angela <an...@adobe.com>
AuthorDate: Wed Jul 21 09:50:33 2021 +0200

    OAK-9494 : Check if a privilege name is included in a set/array of Privileges obtained from AccessControlManager.getPrivileges
---
 .../impl/PrincipalBasedAccessControlManager.java   |   5 +
 oak-benchmarks/run_GetPrivilegesIncludeNames.sh    |  61 +++++++
 .../jackrabbit/oak/benchmark/BenchmarkOptions.java |   5 +
 .../jackrabbit/oak/benchmark/BenchmarkRunner.java  |   6 +
 .../GetPrivilegeCollectionIncludeNamesTest.java    | 133 ++++++++++++++
 .../accesscontrol/AccessControlManagerImpl.java    |  13 +-
 .../security/privilege/PrivilegeManagerImpl.java   |  33 +---
 .../security/JackrabbitAccessControlManager.java   |  49 ++++++
 .../authorization/PrivilegeCollection.java         | 107 +++++++++++
 .../api/security/authorization/package-info.java   |   2 +-
 .../jackrabbit/api/security/package-info.java      |   2 +-
 .../JackrabbitAccessControlManagerDelegator.java   |  23 +++
 .../JackrabbitAccessControlManagerTest.java        |  91 ++++++++++
 .../PrivilegeCollectionDefaultTest.java            |  72 ++++++++
 .../AbstractAccessControlManager.java              |  76 +++++++-
 .../authorization/accesscontrol/package-info.java  |   2 +-
 .../security/privilege/PrivilegeBitsProvider.java  |  42 ++++-
 .../oak/spi/security/privilege/PrivilegeUtil.java  |  50 ++++++
 .../oak/spi/security/privilege/package-info.java   |   2 +-
 .../AbstractAccessControlManagerTest.java          | 195 ++++++++++++---------
 .../privilege/PrivilegeBitsProviderTest.java       |  48 +++++
 .../spi/security/privilege/PrivilegeUtilTest.java  |  54 ++++++
 22 files changed, 931 insertions(+), 140 deletions(-)

diff --git a/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalBasedAccessControlManager.java b/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalBasedAccessControlManager.java
index 8b67389..f98a37f 100644
--- a/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalBasedAccessControlManager.java
+++ b/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalBasedAccessControlManager.java
@@ -109,6 +109,11 @@ class PrincipalBasedAccessControlManager extends AbstractAccessControlManager im
         this.filterProvider = filterProvider;
         filter = filterProvider.getFilter(mgrProvider.getSecurityProvider(), mgrProvider.getRoot(), mgrProvider.getNamePathMapper());
     }
+    
+    @Override
+    protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+        return mgrProvider.getPrivilegeBitsProvider();
+    }
 
     //-------------------------------------< JackrabbitAccessControlManager >---
 
diff --git a/oak-benchmarks/run_GetPrivilegesIncludeNames.sh b/oak-benchmarks/run_GetPrivilegesIncludeNames.sh
new file mode 100755
index 0000000..b863bcf
--- /dev/null
+++ b/oak-benchmarks/run_GetPrivilegesIncludeNames.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# 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.
+#
+TITLE=GetPrivilegeCollectionIncludeNamesTest_ACCESSCONTORL_MANAGER_HAS_PRIVILEGES
+BENCH="GetPrivilegeCollectionIncludeNamesTest"
+ITEMS_TO_READ="10000"
+ACE_CNT="1 5 10"
+GROUP_CNT="1 10 100"
+RUNTIME=5
+FIXS="Oak-Segment-Tar"
+THREADS="1,10,20" #,50" #"1,2,4,8,10,15,20,50"
+PROFILE=true
+EVAL_TYPE=ACCESSCONTORL_MANAGER_HAS_PRIVILEGES #ACCESSCONTORL_MANAGER_GET_PRIVILEGE_COLLECTION, PRIVILEGE_MANAGER_INCLUDES, JCR_PRIVILEGE_NAME_AGGREGATION, ACCESSCONTORL_MANAGER_HAS_PRIVILEGES
+REPORT=false
+
+LOG=$TITLE"_$(date +'%Y%m%d_%H%M%S').csv"
+echo "Benchmarks: $BENCH" > $LOG
+echo "Fixture: $FIXS" >> $LOG
+echo "Runtime: $RUNTIME" >> $LOG
+echo "Concurrency: $THREADS" >> $LOG
+echo "Profiling: $PROFILE" >> $LOG
+echo "EvalType: $EVAL_TYPE" >> $LOG
+
+echo "Items to Read: $ITEMS_TO_READ" >> $LOG
+echo "Number of ACEs per Group Principal: $ACE_CNT" >> $LOG
+echo "Number of Groups: $GROUP_CNT" >> $LOG
+
+echo "--------------------------------------" >> $LOG
+
+for bm in $BENCH
+    do
+    for aceCnt in $ACE_CNT
+        do
+        for groupCnt in $GROUP_CNT
+        do  
+            echo "EvalType = $EVAL_TYPE with $aceCnt initial ACEs per principal and $groupCnt principals per subject" | tee -a $LOG
+            echo "-----------------------------------------------------------" | tee -a $LOG
+            rm -rf target/Jackrabbit-* target/Oak-Tar-*
+            cmd="java -Xmx2048m -Dprofile=$PROFILE -Druntime=$RUNTIME -Dwarmup=1 -jar target/oak-benchmarks-*-SNAPSHOT.jar benchmark --itemsToRead $ITEMS_TO_READ --numberOfInitialAce $aceCnt --numberOfGroups $groupCnt --csvFile $LOG --concurrency $THREADS --report $REPORT --evalType $EVAL_TYPE $bm $FIXS"
+            echo $cmd
+            $cmd
+        done
+    done
+done
+echo "-----------------------------------------"
+echo "Benchmark completed. see $LOG for details:"
+cat $LOG
diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkOptions.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkOptions.java
index e5f96fc..8657bf1 100644
--- a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkOptions.java
+++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkOptions.java
@@ -94,6 +94,7 @@ public class BenchmarkOptions {
     private final OptionSpec<String> nonOption;
     private final OptionSpec<?> help;
     private final OptionSpec<Boolean> useAggregationFilter;
+    private final OptionSpec<String> evalType;
     private final OptionSpec<String> elasticHost;
     private final OptionSpec<String> elasticScheme;
     private final OptionSpec<Integer> elasticPort;
@@ -359,6 +360,8 @@ public class BenchmarkOptions {
     public OptionSpec<Boolean> getUseAggregationFilter() {
         return useAggregationFilter;
     }
+    
+    public OptionSpec<String> getEvalutionType() { return evalType; }
 
 
     public BenchmarkOptions(OptionParser parser) {
@@ -443,6 +446,8 @@ public class BenchmarkOptions {
         useAggregationFilter = parser.accepts("useAggregationFilter", "Run principal-based tests with 'AggregationFilter'")
                 .withOptionalArg().ofType(Boolean.class)
                 .defaultsTo(Boolean.FALSE);
+        evalType = parser.accepts("evalType", "Allows to switch between different evaluation types within a single benchmark")
+                .withOptionalArg().ofType(String.class);
         batchSize = parser.accepts("batchSize", "Batch size before persisting operations.")
                 .withOptionalArg().ofType(Integer.class).defaultsTo(AddMembersTest.DEFAULT_BATCH_SIZE);
         importBehavior = parser.accepts("importBehavior", "Protected Item Import Behavior")
diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
index 2878e32..5b0584e 100644
--- a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
+++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
@@ -36,6 +36,7 @@ import org.apache.jackrabbit.oak.benchmark.authentication.external.SyncAllUsersT
 import org.apache.jackrabbit.oak.benchmark.authentication.external.SyncExternalUsersTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.AceCreationTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.CanReadNonExisting;
+import org.apache.jackrabbit.oak.benchmark.authorization.GetPrivilegeCollectionIncludeNamesTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.SaveHasItemGetItemTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.HasPermissionHasItemGetItemTest;
 import org.apache.jackrabbit.oak.benchmark.authorization.HasPrivilegesHasItemGetItemTest;
@@ -290,6 +291,11 @@ public class BenchmarkRunner {
                                 benchmarkOptions.getNumberOfInitialAce().value(options),
                                 benchmarkOptions.getNumberOfGroups().value(options),
                                 benchmarkOptions.getReport().value(options)),
+                        new GetPrivilegeCollectionIncludeNamesTest(benchmarkOptions.getItemsToRead().value(options),
+                                benchmarkOptions.getNumberOfInitialAce().value(options),
+                                benchmarkOptions.getNumberOfGroups().value(options),
+                                benchmarkOptions.getReport().value(options), 
+                                benchmarkOptions.getEvalutionType().value(options)),
                         new ConcurrentReadDeepTreeTest(
                                 benchmarkOptions.getRunAsAdmin().value(options),
                                 benchmarkOptions.getItemsToRead().value(options),
diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/GetPrivilegeCollectionIncludeNamesTest.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/GetPrivilegeCollectionIncludeNamesTest.java
new file mode 100644
index 0000000..ebbfb43
--- /dev/null
+++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/authorization/GetPrivilegeCollectionIncludeNamesTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.jackrabbit.oak.benchmark.authorization;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import joptsimple.internal.Strings;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public class GetPrivilegeCollectionIncludeNamesTest extends AbstractHasItemGetItemTest {
+    
+    private enum EvaluationType {
+        ACCESSCONTORL_MANAGER_GET_PRIVILEGE_COLLECTION,
+        JCR_PRIVILEGE_NAME_AGGREGATION,
+        ACCESSCONTORL_MANAGER_HAS_PRIVILEGES
+    } 
+    
+    private static final List<String> ALL_PRIVILEGE_NAMES = ImmutableList.copyOf(PrivilegeBits.BUILT_IN.keySet());
+    
+    private final EvaluationType evalType;
+    
+    public GetPrivilegeCollectionIncludeNamesTest(int itemsToRead, int numberOfACEs, int numberOfGroups, boolean doReport, String evalType) {
+        super(itemsToRead, numberOfACEs, numberOfGroups, doReport);
+        this.evalType = (Strings.isNullOrEmpty(evalType)) ? EvaluationType.ACCESSCONTORL_MANAGER_GET_PRIVILEGE_COLLECTION : EvaluationType.valueOf(evalType);
+    }
+
+    @Override
+    @NotNull String additionalMethodName() {
+        return evalType.name();
+    }
+
+    @Override
+    void additionalOperations(@NotNull String path, @NotNull Session s, @NotNull AccessControlManager acMgr) {
+        try {
+            List<String> privNames = ImmutableList.of(getRandom(ALL_PRIVILEGE_NAMES), getRandom(ALL_PRIVILEGE_NAMES), getRandom(ALL_PRIVILEGE_NAMES), getRandom(ALL_PRIVILEGE_NAMES));
+            String accessControlledPath = getAccessControlledPath(path);
+            if (EvaluationType.ACCESSCONTORL_MANAGER_GET_PRIVILEGE_COLLECTION == evalType) {
+                PrivilegeCollection pc = ((JackrabbitAccessControlManager) acMgr).getPrivilegeCollection(accessControlledPath);
+                for (String toTest : privNames) {
+                    boolean includes = pc.includes(toTest);
+                    if (doReport) {
+                        System.out.println("PrivilegeCollection.includes('"+toTest+"') : "+includes);
+                    }
+                }
+            } else if (EvaluationType.JCR_PRIVILEGE_NAME_AGGREGATION == evalType) {
+                // evaluation using regular JCR Privilege API 
+                Privilege[] privileges = acMgr.getPrivileges(accessControlledPath);
+                Set<String> privilegeNames = Sets.newHashSet(AccessControlUtils.namesFromPrivileges(privileges));
+                Stream.of(privileges).filter(Privilege::isAggregate).forEach(privilege -> Collections.addAll(privilegeNames, AccessControlUtils.namesFromPrivileges(privilege.getAggregatePrivileges())));
+                for (String toTest : privNames) {
+                    boolean includes = privilegeNames.contains(toTest);
+                    if (doReport) {
+                        System.out.println("Privileges '"+Arrays.toString(privileges)+"' include '"+toTest+"' : "+includes);
+                    }
+                }
+            } else {
+                // evaluation using separate AccessControlManager.hasPrivileges
+                for (String toTest : privNames) {
+                    boolean hasPrivilege = acMgr.hasPrivileges(accessControlledPath, AccessControlUtils.privilegesFromNames(acMgr, toTest)); 
+                    if (doReport) {
+                        System.out.println("AccessControlManager.hasPrivileges('"+accessControlledPath+"','"+toTest+"') : "+hasPrivilege);
+                    }
+                }
+            }
+        } catch (RepositoryException e) {
+            if (doReport) {
+                e.printStackTrace(System.out);
+            }
+        }
+    }
+
+    @Override
+    protected void randomRead(Session testSession, List<String> allPaths, int cnt) throws RepositoryException {
+        boolean logout = false;
+        if (testSession == null) {
+            testSession = getTestSession();
+            logout = true;
+        }
+        try {
+            int nodeCnt = 0;
+            int propertyCnt = 0;
+            int addCnt = 0;
+            int noAccess = 0;
+            int size = allPaths.size();
+
+            AccessControlManager acMgr = testSession.getAccessControlManager();
+
+            long start = System.currentTimeMillis();
+            for (int i = 0; i < cnt; i++) {
+                double rand = size * Math.random();
+                int index = (int) Math.floor(rand);
+                String path = allPaths.get(index);
+                additionalOperations(path, testSession, acMgr);
+            }
+            long end = System.currentTimeMillis();
+            if (doReport) {
+                System.out.println("Session " + testSession.getUserID() + " reading " + cnt + " (Nodes: "+ nodeCnt +"; Properties: "+propertyCnt+"; no access: "+noAccess+"; "+ additionalMethodName()+": "+addCnt+") completed in " + (end - start));
+            }
+        } finally {
+            if (logout) {
+                logout(testSession);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java
index f1e0a6c..0523345 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java
@@ -80,7 +80,6 @@ import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restrict
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
-import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
 import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
 import org.apache.jackrabbit.util.ISO9075;
 import org.apache.jackrabbit.util.Text;
@@ -100,7 +99,6 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
 
     private static final Logger log = LoggerFactory.getLogger(AccessControlManagerImpl.class);
 
-    private final PrivilegeBitsProvider bitsProvider;
     private final ReadOnlyNodeTypeManager ntMgr;
 
     private final PrincipalManager principalManager;
@@ -113,7 +111,6 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
                                     @NotNull SecurityProvider securityProvider) {
         super(root, namePathMapper, securityProvider);
 
-        bitsProvider = new PrivilegeBitsProvider(root);
         ntMgr = ReadOnlyNodeTypeManager.getInstance(root, namePathMapper);
 
         principalManager = securityProvider.getConfiguration(PrincipalConfiguration.class).getPrincipalManager(root, namePathMapper);
@@ -318,7 +315,7 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
 
             Tree aceNode = TreeUtil.addChild(aclTree, nodeName, ntName);
             aceNode.setProperty(REP_PRINCIPAL_NAME, ace.getPrincipal().getName());
-            aceNode.setProperty(REP_PRIVILEGES, bitsProvider.getPrivilegeNames(ace.getPrivilegeBits()), Type.NAMES);
+            aceNode.setProperty(REP_PRIVILEGES, getPrivilegeBitsProvider().getPrivilegeNames(ace.getPrivilegeBits()), Type.NAMES);
             Set<Restriction> restrictions = ace.getRestrictions();
             restrictionProvider.writeRestrictions(oakPath, aceNode, restrictions);
         }
@@ -533,7 +530,7 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
         boolean isAllow = NT_REP_GRANT_ACE.equals(TreeUtil.getPrimaryTypeName(aceTree));
         Set<Restriction> restrictions = restrictionProvider.readRestrictions(oakPath, aceTree);
         Iterable<String> privNames = checkNotNull(TreeUtil.getStrings(aceTree, REP_PRIVILEGES));
-        return new Entry(getPrincipal(aceTree, principalMap), bitsProvider.getBits(privNames), isAllow, restrictions, getNamePathMapper());
+        return new Entry(getPrincipal(aceTree, principalMap), getPrivilegeBitsProvider().getBits(privNames), isAllow, restrictions, getNamePathMapper());
     }
 
     @NotNull
@@ -648,7 +645,7 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
         @Override
         @NotNull
         PrivilegeBits getPrivilegeBits(@NotNull Privilege[] privileges) {
-            return bitsProvider.getBits(privileges, getNamePathMapper());
+            return getPrivilegeBitsProvider().getBits(privileges, getNamePathMapper());
         }
 
         @Override
@@ -718,7 +715,7 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
         @Override
         @NotNull
         PrivilegeBits getPrivilegeBits(@NotNull Privilege[] privileges) {
-            return bitsProvider.getBits(privileges, getNamePathMapper());
+            return getPrivilegeBitsProvider().getBits(privileges, getNamePathMapper());
         }
 
         @Override
@@ -776,7 +773,7 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
         @Override
         public Privilege[] getPrivileges() {
             Set<Privilege> privileges = new HashSet<>();
-            for (String name : bitsProvider.getPrivilegeNames(getPrivilegeBits())) {
+            for (String name : getPrivilegeBitsProvider().getPrivilegeNames(getPrivilegeBits())) {
                 try {
                     privileges.add(getPrivilegeManager().getPrivilege(getNamePathMapper().getJcrName(name)));
                 } catch (RepositoryException e) {
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java
index 6a6ac1f..a40df3e 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java
@@ -17,7 +17,6 @@
 package org.apache.jackrabbit.oak.security.privilege;
 
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -32,6 +31,7 @@ import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeDefinition;
 import org.apache.jackrabbit.oak.spi.security.privilege.ImmutablePrivilegeDefinition;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -67,7 +67,7 @@ class PrivilegeManagerImpl implements PrivilegeManager {
     @NotNull
     @Override
     public Privilege getPrivilege(@NotNull String privilegeName) throws RepositoryException {
-        PrivilegeDefinition def = getPrivilegeDefinition(getOakName(privilegeName));
+        PrivilegeDefinition def = getPrivilegeDefinition(PrivilegeUtil.getOakName(privilegeName, namePathMapper));
         if (def == null) {
             throw new AccessControlException("No such privilege " + privilegeName);
         } else {
@@ -85,7 +85,7 @@ class PrivilegeManagerImpl implements PrivilegeManager {
         if (Strings.isNullOrEmpty(privilegeName)) {
             throw new RepositoryException("Invalid privilege name " + privilegeName);
         }
-        PrivilegeDefinition definition = new ImmutablePrivilegeDefinition(getOakName(privilegeName), isAbstract, getOakNames(declaredAggregateNames));
+        PrivilegeDefinition definition = new ImmutablePrivilegeDefinition(PrivilegeUtil.getOakName(privilegeName, namePathMapper), isAbstract, PrivilegeUtil.getOakNames(declaredAggregateNames, namePathMapper));
         PrivilegeDefinitionWriter writer = new PrivilegeDefinitionWriter(getWriteRoot());
         writer.writeDefinition(definition);
 
@@ -101,33 +101,6 @@ class PrivilegeManagerImpl implements PrivilegeManager {
     }
 
     @NotNull
-    private Set<String> getOakNames(@Nullable String[] jcrNames) throws RepositoryException {
-        Set<String> oakNames;
-        if (jcrNames == null || jcrNames.length == 0) {
-            oakNames = Collections.emptySet();
-        } else {
-            oakNames = new HashSet<>(jcrNames.length);
-            for (String jcrName : jcrNames) {
-                String oakName = getOakName(jcrName);
-                oakNames.add(oakName);
-            }
-        }
-        return oakNames;
-    }
-
-    @NotNull
-    private String getOakName(@Nullable String jcrName) throws RepositoryException {
-        if (jcrName == null) {
-            throw new AccessControlException("Invalid privilege name 'null'");
-        }
-        String oakName = namePathMapper.getOakNameOrNull(jcrName);
-        if (oakName == null) {
-        	throw new AccessControlException("Cannot resolve privilege name " + jcrName);
-        }
-        return oakName;
-    }
-
-    @NotNull
     private Privilege getPrivilege(@NotNull PrivilegeDefinition definition) {
         return new PrivilegeImpl(definition);
     }
diff --git a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlManager.java b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlManager.java
index 1bf9bd2..135ee74 100644
--- a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlManager.java
+++ b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlManager.java
@@ -28,6 +28,7 @@ import javax.jcr.security.Privilege;
 import java.security.Principal;
 import java.util.Set;
 
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.osgi.annotation.versioning.ProviderType;
@@ -171,4 +172,52 @@ public interface JackrabbitAccessControlManager extends AccessControlManager {
     @NotNull
     public Privilege[] getPrivileges(@Nullable String absPath, @NotNull Set<Principal> principals)
             throws PathNotFoundException, AccessDeniedException, RepositoryException;
+
+    /**
+     * <p>Returns the {@link PrivilegeCollection} for editing session at the given absolute path, which
+     * must be an existing node. This is equivalent to {@link #getPrivileges(String)} and 
+     * {@link #hasPrivileges(String, Privilege[])} but allows for easy resolution of aggregated privileges 
+     * (like e.g. jcr:all) and repeated evaluation if the editing session has privileges granted 
+     * at the given target node.</p>
+     * 
+     * Note: For backwards compatibility this method comes with a default implementation that computes the {@link PrivilegeCollection}
+     * using regular JCR/Jackrabbit API, which might not be efficient. Implementations of {@link JackrabbitAccessControlManager} 
+     * are therefore expected to overwrite the default.
+     * 
+     * @param absPath An absolute path to an existing JCR node.
+     * @return A {@link PrivilegeCollection} wrapping around the privileges granted for the editing session at absPath.
+     * @throws PathNotFoundException if no node at <code>absPath</code> exists or the session does not have sufficient 
+     * access to retrieve a node at that location.
+     * @throws RepositoryException If another error occurs.
+     * @since Oak 1.42.0
+     */
+    @NotNull
+    default PrivilegeCollection getPrivilegeCollection(@Nullable String absPath) throws RepositoryException {
+        return new PrivilegeCollection.Default(getPrivileges(absPath), this);
+    }
+
+    /**
+     * <p>Returns the {@link PrivilegeCollection} for the given set of principals at the given absolute path, which
+     * must be an existing node. This is equivalent to {@link #getPrivileges(String,Set)} and 
+     * {@link #hasPrivileges(String, Set, Privilege[])} but allows for easy resolution of aggregated privileges 
+     * (like e.g. jcr:all) and repeated evaluation if the editing session has privileges granted 
+     * at the given target node.</p>
+     *
+     * Note: For backwards compatibility this method comes with a default implementation that computes the {@link PrivilegeCollection}
+     * using regular JCR/Jackrabbit API, which might not be efficient. Implementations of {@link JackrabbitAccessControlManager}
+     * are therefore expected to overwrite the default.
+     * 
+     * @param absPath An absolute path to an existing JCR node.
+     * @param principals A set of principals for which the {@link PrivilegeCollection} should be created.
+     * @return A {@link PrivilegeCollection} wrapping around the privileges granted for the editing session at absPath.
+     * @throws PathNotFoundException if no node at <code>absPath</code> exists or the session does not have sufficient 
+     * access to retrieve a node at that location.
+     * @throws AccessDeniedException if the session lacks <code>READ_ACCESS_CONTROL</code> privilege for the <code>absPath</code> node.
+     * @throws RepositoryException If another error occurs.
+     * @since Oak 1.42.0
+     */
+    @NotNull
+    default PrivilegeCollection getPrivilegeCollection(@Nullable String absPath, @NotNull Set<Principal> principals) throws RepositoryException {
+        return new PrivilegeCollection.Default(getPrivileges(absPath, principals), this);
+    }
 }
\ No newline at end of file
diff --git a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/authorization/PrivilegeCollection.java b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/authorization/PrivilegeCollection.java
new file mode 100644
index 0000000..9f2ea4b
--- /dev/null
+++ b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/authorization/PrivilegeCollection.java
@@ -0,0 +1,107 @@
+/*
+ * 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.jackrabbit.api.security.authorization;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public interface PrivilegeCollection {
+
+    /**
+     * Return the underlying privilege array.
+     * 
+     * @return the privilege array for which this instance has been created.
+     * @throws RepositoryException If an error occurs.
+     */
+    Privilege[] getPrivileges() throws RepositoryException;
+
+    /**
+     * Tests whether the given JCR {@code privilegeNames} are contained in the privileges for which this instance of 
+     * {@code PrivilegeEvaluation} has been created such as e.g. through 
+     * {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getPrivilegeCollection(String)} or  
+     * {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getPrivilegeCollection(String, Set)}. 
+     * The inclusion can either be direct or through privilege aggregation.
+     * 
+     * @param privilegeNames The JCR names of privileges to be tested. They can be passed in expanded form 
+     * (like e.g. {@link Privilege#JCR_READ)} or in qualified form (i.e. 'jcr:read' if 'jcr' was the prefixed defined for 
+     * the 'http://www.jcp.org/jcr/1.0' namespace.
+     * @return {@code true} if the underlying {@code privileges} include all specified privilege names either directly 
+     * or by means of aggregation; {@code false} if one or multiple privileges are not included. If {@code jcr:all} privilege 
+     * is part of this collection or if no privilege names are specified this method will return {@code true}. 
+     * If no privileges are granted {@code false} is returned.
+     * @throws AccessControlException If any of the given privilege names is invalid i.e. no such privilege exists.
+     * @throws RepositoryException If another error occurs.
+     * @since Oak 1.42.0
+     */
+    boolean includes(@NotNull String... privilegeNames) throws RepositoryException;
+
+    /**
+     * Default implementation of the {@link PrivilegeCollection} interface.
+     * 
+     * Note that this implementation uses JCR API to resolve privilege aggregation, which is expected to be inefficient.
+     * Implementations of {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getPrivilegeCollection(String)} 
+     * and {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlManager#getPrivilegeCollection(String, Set)} 
+     * therefore should provide improved implementations of the {@code PrivilegeCollection} interface.
+     * 
+     * @since Oak 1.42.0
+     */
+    class Default implements PrivilegeCollection {
+        
+        private final Privilege[] privileges;
+        private final AccessControlManager accessControlManager;
+        
+        public Default(@NotNull Privilege[] privileges, @NotNull AccessControlManager accessControlManager) {
+            this.privileges = privileges;
+            this.accessControlManager = accessControlManager;
+        }
+
+        @Override
+        public Privilege[] getPrivileges() {
+            return privileges;
+        }
+
+        @Override
+        public boolean includes(@NotNull String... privilegeNames) throws RepositoryException {
+            if (privilegeNames.length == 0) {
+                return true;
+            }
+            if (privileges.length == 0) {
+                return false;
+            }
+            Set<Privilege> toTest = new HashSet<>(privilegeNames.length);
+            for (String pName : privilegeNames) {
+                toTest.add(accessControlManager.privilegeFromName(pName));
+            }
+            Set<Privilege> privilegeSet = new HashSet<>(Arrays.asList(privileges));
+            if (privilegeSet.containsAll(toTest)) {
+                return true;
+            } else {
+                Stream.of(privileges).filter(Privilege::isAggregate).forEach(privilege -> Collections.addAll(privilegeSet, privilege.getAggregatePrivileges()));
+                return privilegeSet.containsAll(toTest);
+            }
+        }
+    }
+}
diff --git a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/authorization/package-info.java b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/authorization/package-info.java
index d71b3f6..132dc73 100644
--- a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/authorization/package-info.java
+++ b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/authorization/package-info.java
@@ -18,5 +18,5 @@
 /**
  * Jackrabbit extensions for authorization.
  */
-@org.osgi.annotation.versioning.Version("2.4.2")
+@org.osgi.annotation.versioning.Version("2.5.0")
 package org.apache.jackrabbit.api.security.authorization;
diff --git a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/package-info.java b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/package-info.java
index 8fef895..4e7c077 100644
--- a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/package-info.java
+++ b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/package-info.java
@@ -18,5 +18,5 @@
 /**
  * Jackrabbit extensions for access control.
  */
-@org.osgi.annotation.versioning.Version("2.4.1")
+@org.osgi.annotation.versioning.Version("2.5.0")
 package org.apache.jackrabbit.api.security;
diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/JackrabbitAccessControlManagerDelegator.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/JackrabbitAccessControlManagerDelegator.java
index f6840e4..f842c5e 100644
--- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/JackrabbitAccessControlManagerDelegator.java
+++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/JackrabbitAccessControlManagerDelegator.java
@@ -29,6 +29,7 @@ import javax.jcr.security.Privilege;
 
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -114,6 +115,28 @@ public class JackrabbitAccessControlManagerDelegator implements JackrabbitAccess
             }
         });
     }
+    
+    @Override
+    public @NotNull PrivilegeCollection getPrivilegeCollection(@Nullable String absPath) throws RepositoryException {
+        return delegate.perform(new SessionOperation<PrivilegeCollection>("getPrivilegeCollection") {
+            @NotNull
+            @Override
+            public PrivilegeCollection perform() throws RepositoryException {
+                return jackrabbitACManager.getPrivilegeCollection(absPath);
+            }
+        });
+    }
+
+    @Override
+    public @NotNull PrivilegeCollection getPrivilegeCollection(@Nullable String absPath, @NotNull Set<Principal> principals) throws RepositoryException {
+        return delegate.perform(new SessionOperation<PrivilegeCollection>("getPrivilegeCollection") {
+            @NotNull
+            @Override
+            public PrivilegeCollection perform() throws RepositoryException {
+                return jackrabbitACManager.getPrivilegeCollection(absPath, principals);
+            }
+        });
+    }
 
     @Override
     public Privilege[] getSupportedPrivileges(String absPath) throws RepositoryException {
diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/JackrabbitAccessControlManagerTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/JackrabbitAccessControlManagerTest.java
new file mode 100644
index 0000000..a7b1a66
--- /dev/null
+++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/JackrabbitAccessControlManagerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.jackrabbit.oak.jcr.security.authorization;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.security.Privilege;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class JackrabbitAccessControlManagerTest extends AbstractEvaluationTest {
+    
+    private JackrabbitAccessControlManager jrAcMgr;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!(acMgr instanceof JackrabbitAccessControlManager)) {
+            throw new NotExecutableException("JackrabbitAccessControlManager expected");
+        }
+        jrAcMgr = (JackrabbitAccessControlManager) acMgr;
+    }
+    
+    public void testGetPrivilegeCollection() throws Exception {
+        PrivilegeCollection pc = jrAcMgr.getPrivilegeCollection(path);
+        assertTrue(pc.includes(Privilege.JCR_ALL));
+        assertTrue(pc.includes(Privilege.JCR_READ, PrivilegeConstants.REP_READ_PROPERTIES));
+        assertArrayEquals(privilegesFromName(Privilege.JCR_ALL), pc.getPrivileges());
+    }
+    
+    public void testGetPrivilegeCollectionInvalidPath() throws Exception {
+        try {
+            jrAcMgr.getPrivilegeCollection("/invalid");
+            fail();
+        } catch (PathNotFoundException e) {
+            // success
+        }
+    }
+
+    public void testGetPrivilegeCollectionEmptyPrincipalSet() throws Exception {
+        PrivilegeCollection pc = jrAcMgr.getPrivilegeCollection(path, Collections.emptySet());
+        assertFalse(pc.includes(Privilege.JCR_ALL));
+        assertFalse(pc.includes(Privilege.JCR_READ, PrivilegeConstants.REP_READ_PROPERTIES));
+        assertArrayEquals(new Privilege[0], pc.getPrivileges());
+    }
+
+    public void testGetPrivilegeCollectionPrincipalSet() throws Exception {
+        PrivilegeCollection pc = jrAcMgr.getPrivilegeCollection(path, Collections.singleton(EveryonePrincipal.getInstance()));
+        assertFalse(pc.includes(Privilege.JCR_ALL));
+        assertTrue(pc.includes(Privilege.JCR_READ, PrivilegeConstants.REP_READ_PROPERTIES));
+        assertArrayEquals(privilegesFromName(Privilege.JCR_READ), pc.getPrivileges());
+    }
+    
+    public void testGetPrivilegeCollectionTestSession() throws Exception {
+        PrivilegeCollection pc = ((JackrabbitAccessControlManager) testAcMgr).getPrivilegeCollection(path);
+        assertFalse(pc.includes(Privilege.JCR_ALL));
+        assertTrue(pc.includes(Privilege.JCR_READ, PrivilegeConstants.REP_READ_PROPERTIES));
+        assertArrayEquals(privilegesFromName(Privilege.JCR_READ), pc.getPrivileges());
+    }
+
+    public void testGetPrivilegeCollectionTestSessionPrincipalSet() throws Exception {
+        try {
+            ((JackrabbitAccessControlManager) testAcMgr).getPrivilegeCollection(path, Collections.singleton(EveryonePrincipal.getInstance()));
+            fail("AccessDeniedException expected");
+        } catch (AccessDeniedException e) {
+            // success
+        }
+    }
+}
\ No newline at end of file
diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/PrivilegeCollectionDefaultTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/PrivilegeCollectionDefaultTest.java
new file mode 100644
index 0000000..85e44c6
--- /dev/null
+++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/PrivilegeCollectionDefaultTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.jackrabbit.oak.jcr.security.authorization;
+
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+
+import javax.jcr.security.Privilege;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class PrivilegeCollectionDefaultTest extends AbstractEvaluationTest {
+    
+    public void testEmptyCollection() throws Exception {
+        PrivilegeCollection pc = new PrivilegeCollection.Default(new Privilege[0], acMgr);
+        assertArrayEquals(new Privilege[0], pc.getPrivileges());
+        assertTrue(pc.includes());
+        assertFalse(pc.includes(Privilege.JCR_ALL));
+        assertFalse(pc.includes(PrivilegeConstants.JCR_READ));
+        assertFalse(pc.includes(Privilege.JCR_MODIFY_ACCESS_CONTROL));
+    }
+    
+    public void testJcrAllCollection() throws Exception {
+        Privilege[] jcrAll = privilegesFromName(Privilege.JCR_ALL);
+        PrivilegeCollection pc = new PrivilegeCollection.Default(jcrAll, acMgr);
+        assertArrayEquals(jcrAll, pc.getPrivileges());
+        assertTrue(pc.includes());
+        assertTrue(pc.includes(Privilege.JCR_ALL));
+        assertTrue(pc.includes(PrivilegeConstants.REP_READ_NODES));
+        assertTrue(pc.includes(Privilege.JCR_LIFECYCLE_MANAGEMENT));
+        assertTrue(pc.includes(Privilege.JCR_WRITE));
+    }
+    
+    public void testNonAggregated() throws Exception {
+        Privilege[] privs = privilegesFromNames(new String[] {Privilege.JCR_NODE_TYPE_MANAGEMENT, Privilege.JCR_REMOVE_NODE, Privilege.JCR_READ_ACCESS_CONTROL});
+        PrivilegeCollection pc = new PrivilegeCollection.Default(privs, acMgr);
+        assertArrayEquals(privs, pc.getPrivileges());
+        assertTrue(pc.includes());
+        assertFalse(pc.includes(Privilege.JCR_ALL));
+        assertFalse(pc.includes(PrivilegeConstants.REP_READ_PROPERTIES));
+        assertFalse(pc.includes(Privilege.JCR_REMOVE_CHILD_NODES));
+        assertTrue(pc.includes(Privilege.JCR_NODE_TYPE_MANAGEMENT, Privilege.JCR_REMOVE_NODE, Privilege.JCR_READ_ACCESS_CONTROL));
+        assertTrue(pc.includes(Privilege.JCR_REMOVE_NODE));
+    }
+
+    public void testAggregated() throws Exception {
+        Privilege[] privs = privilegesFromNames(new String[] {Privilege.JCR_READ, Privilege.JCR_MODIFY_PROPERTIES, PrivilegeConstants.REP_WRITE});
+        PrivilegeCollection pc = new PrivilegeCollection.Default(privs, acMgr);
+        assertArrayEquals(privs, pc.getPrivileges());
+        assertTrue(pc.includes());
+        assertFalse(pc.includes(Privilege.JCR_ALL));
+        assertFalse(pc.includes(PrivilegeConstants.JCR_WORKSPACE_MANAGEMENT));
+        assertTrue(pc.includes(Privilege.JCR_READ, Privilege.JCR_MODIFY_PROPERTIES, PrivilegeConstants.REP_WRITE));
+        assertTrue(pc.includes(PrivilegeConstants.REP_READ_PROPERTIES, PrivilegeConstants.REP_READ_NODES));
+        assertTrue(pc.includes(PrivilegeConstants.REP_ADD_PROPERTIES, PrivilegeConstants.REP_REMOVE_PROPERTIES));
+        assertTrue(pc.includes(Privilege.JCR_REMOVE_CHILD_NODES, Privilege.JCR_MODIFY_PROPERTIES, Privilege.JCR_NODE_TYPE_MANAGEMENT));
+    }
+}
\ No newline at end of file
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManager.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManager.java
index 118f57d..0ff23a6 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManager.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManager.java
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol;
 
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
@@ -27,7 +28,10 @@ import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfigu
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionAware;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionProvider;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConfiguration;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -58,6 +62,7 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
     private final PrivilegeManager privilegeManager;
 
     private PermissionProvider permissionProvider;
+    private PrivilegeBitsProvider privilegeBitsProvider;
     private boolean doRefresh = false;
 
     protected AbstractAccessControlManager(@NotNull Root root,
@@ -118,6 +123,21 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
         }
     }
 
+    @Override
+    public @NotNull PrivilegeCollection getPrivilegeCollection(@Nullable String absPath) throws RepositoryException {
+        return getPrivilegeCollection(absPath, getPermissionProvider(), Permissions.NO_PERMISSION);
+    }
+
+    @Override
+    public @NotNull PrivilegeCollection getPrivilegeCollection(@Nullable String absPath, @NotNull Set<Principal> principals) throws RepositoryException {
+        if (getPrincipals().equals(principals)) {
+            return getPrivilegeCollection(absPath);
+        } else {
+            PermissionProvider provider = config.getPermissionProvider(root, workspaceName, principals);
+            return getPrivilegeCollection(absPath, provider, Permissions.READ_ACCESS_CONTROL);
+        }
+    }
+
     //----------------------------------------------------------< protected >---
     @NotNull
     protected AuthorizationConfiguration getConfig() {
@@ -143,6 +163,14 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
     protected PrivilegeManager getPrivilegeManager() {
         return privilegeManager;
     }
+    
+    @NotNull
+    protected PrivilegeBitsProvider getPrivilegeBitsProvider() {
+        if (privilegeBitsProvider == null) {
+            privilegeBitsProvider = new PrivilegeBitsProvider(root);
+        }
+        return privilegeBitsProvider;
+    }
 
     @Nullable
     protected String getOakPath(@Nullable String jcrPath) throws RepositoryException {
@@ -210,9 +238,7 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
     }
 
     @NotNull
-    private Privilege[] getPrivileges(@Nullable String absPath,
-                                      @NotNull PermissionProvider provider,
-                                      long permissions) throws RepositoryException {
+    private Set<String> getPrivilegeNames(@Nullable String absPath, @NotNull PermissionProvider provider, long permissions) throws RepositoryException {
         Tree tree;
         if (absPath == null) {
             tree = null;
@@ -222,17 +248,28 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
         } else {
             tree = getTree(getOakPath(absPath), permissions, false);
         }
-        Set<String> pNames = provider.getPrivileges(tree);
-        if (pNames.isEmpty()) {
+        return provider.getPrivileges(tree);
+    }
+
+    @NotNull
+    private Privilege[] getPrivileges(@NotNull Set<String> privilegeNames) throws RepositoryException {
+        if (privilegeNames.isEmpty()) {
             return new Privilege[0];
         } else {
-            Set<Privilege> privileges = new HashSet<>(pNames.size());
-            for (String name : pNames) {
+            Set<Privilege> privileges = new HashSet<>(privilegeNames.size());
+            for (String name : privilegeNames) {
                 privileges.add(privilegeManager.getPrivilege(namePathMapper.getJcrName(name)));
             }
             return privileges.toArray(new Privilege[0]);
         }
     }
+    
+    @NotNull
+    private Privilege[] getPrivileges(@Nullable String absPath,
+                                      @NotNull PermissionProvider provider,
+                                      long permissions) throws RepositoryException {
+        return getPrivileges(getPrivilegeNames(absPath, provider, permissions));
+    }
 
     private boolean hasPrivileges(@Nullable String absPath, @Nullable Privilege[] privileges,
                                   @NotNull PermissionProvider provider, long permissions,
@@ -258,4 +295,29 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
             return provider.hasPrivileges(tree, privilegeNames.toArray(new String[0]));
         }
     }
+
+    @NotNull
+    private PrivilegeCollection getPrivilegeCollection(@Nullable String absPath, @NotNull PermissionProvider provider, long permissions) throws RepositoryException {
+        Set<String> pNames = getPrivilegeNames(absPath, provider, permissions);
+        return new PrivilegeCollection() {
+            @Override
+            public Privilege[] getPrivileges() throws RepositoryException {
+                return AbstractAccessControlManager.this.getPrivileges(pNames);
+            }
+
+            @Override
+            public boolean includes(@NotNull String... privilegeNames) throws RepositoryException {
+                if (privilegeNames.length == 0) {
+                    return true;
+                }
+                if (pNames.isEmpty()) {
+                    return false;
+                }
+                PrivilegeBitsProvider pbp = getPrivilegeBitsProvider();
+                PrivilegeBits toTest = pbp.getBits(PrivilegeUtil.getOakNames(privilegeNames, getNamePathMapper()), true);
+                PrivilegeBits bits = pbp.getBits(pNames);
+                return bits.includes(toTest);
+            }
+        };
+    }
 }
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java
index 35003f7..8bdef7e 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/package-info.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.7.0")
+@Version("1.8.0")
 package org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProvider.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProvider.java
index 587999b..7219c68 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProvider.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProvider.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import javax.jcr.security.AccessControlException;
 import javax.jcr.security.Privilege;
 
 import com.google.common.base.Function;
@@ -84,7 +85,7 @@ public final class PrivilegeBitsProvider implements PrivilegeConstants {
     }
 
     /**
-     * Returns the bits for the given privilege names.
+     * Returns the bits for the given privilege names. Note, that any invalid privilege names will be ignored.
      * 
      * @param privilegeNames the names
      * @return the privilege bits representing the given privilege names.
@@ -95,8 +96,40 @@ public final class PrivilegeBitsProvider implements PrivilegeConstants {
             return PrivilegeBits.EMPTY;
         }
 
-        Tree privilegesTree = null;
         PrivilegeBits bits = PrivilegeBits.getInstance();
+        collectBits(privilegeNames, bits);
+        return bits.unmodifiable();
+    }
+
+    /**
+     * Returns the bits for the given privilege names with the option to verify that all privilege names point to a valid,
+     * registered privilege.
+     * 
+     * @param privilegeNames An iterable of privilege names.
+     * @param validateNames If set to {@code true} this method will throw an AccessControlException if an invalid privilege 
+     * name is found (i.e. one that doesn't represent a registered privilege). If set to {@code false} invalid privilege 
+     * names will be ignored i.e. making this method equivalent to {@link #getBits(String...)}.
+     * @return the privilege bits representing the given privilege names.
+     * @throws AccessControlException If {@code validateNames} is {@code true} and the any of the specified privilege names is invalid.
+     */
+    @NotNull
+    public PrivilegeBits getBits(@NotNull Iterable<String> privilegeNames, boolean validateNames) throws AccessControlException {
+        if (!validateNames) {
+            return getBits(privilegeNames);
+        }
+        if (Iterables.isEmpty(privilegeNames)) {
+            return PrivilegeBits.EMPTY;
+        }
+        PrivilegeBits bits = PrivilegeBits.getInstance();
+        if (!collectBits(privilegeNames, bits)) {
+            throw new AccessControlException("Invalid privilege name contained in " + privilegeNames);
+        }
+        return bits.unmodifiable();
+    }
+    
+    private boolean collectBits(@NotNull Iterable<String> privilegeNames, @NotNull PrivilegeBits bits) {
+        Tree privilegesTree = null;
+        boolean allNamesValid = true;
         for (String privilegeName : privilegeNames) {
             PrivilegeBits builtIn = PrivilegeBits.BUILT_IN.get(privilegeName);
             if (builtIn != null) {
@@ -113,11 +146,12 @@ public final class PrivilegeBitsProvider implements PrivilegeConstants {
                     nameToBits.put(privilegeName, bitsFromDefTree);
                     bits.add(bitsFromDefTree);
                 } else {
-                    log.debug("Ignoring privilege name {}", privilegeName);
+                    log.debug("Invalid privilege name {}", privilegeName);
+                    allNamesValid = false;
                 }
             }
         }
-        return bits.unmodifiable();
+        return allNamesValid;
     }
 
     /**
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtil.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtil.java
index e53e406..294010f 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtil.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtil.java
@@ -20,8 +20,15 @@ import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.jcr.security.AccessControlException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Privilege management related utility methods.
@@ -60,4 +67,47 @@ public final class PrivilegeUtil implements PrivilegeConstants {
         }
         return new ImmutablePrivilegeDefinition(name, isAbstract, declAggrNames);
     }
+
+    /**
+     * Convert the given JCR privilege names to Oak names.
+     * 
+     * @param jcrNames The JCR names of privileges
+     * @param namePathMapper The {@link NamePathMapper} to use for the conversion.
+     * @return A set of Oak names
+     * @throws AccessControlException If the given JCR names cannot be converted.
+     */
+    @NotNull
+    public static Set<String> getOakNames(@Nullable String[] jcrNames, @NotNull NamePathMapper namePathMapper) throws AccessControlException {
+        Set<String> oakNames;
+        if (jcrNames == null || jcrNames.length == 0) {
+            oakNames = Collections.emptySet();
+        } else {
+            oakNames = new HashSet<>(jcrNames.length);
+            for (String jcrName : jcrNames) {
+                String oakName = getOakName(jcrName, namePathMapper);
+                oakNames.add(oakName);
+            }
+        }
+        return oakNames;
+    }
+
+    /**
+     * Convert the given JCR privilege name to an Oak name.
+     * 
+     * @param jcrName The JCR name of a privilege.
+     * @param namePathMapper The {@link NamePathMapper} to use for the conversion.
+     * @return the Oak name of the given privilege.
+     * @throws AccessControlException If the specified name is null or cannot be resolved to an Oak name.
+     */
+    @NotNull
+    public static String getOakName(@Nullable String jcrName, @NotNull NamePathMapper namePathMapper) throws AccessControlException {
+        if (jcrName == null) {
+            throw new AccessControlException("Invalid privilege name 'null'");
+        }
+        String oakName = namePathMapper.getOakNameOrNull(jcrName);
+        if (oakName == null) {
+            throw new AccessControlException("Cannot resolve privilege name " + jcrName);
+        }
+        return oakName;
+    }
 }
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/package-info.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/package-info.java
index ee2fb29..5c1e647 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/package-info.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/privilege/package-info.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.4.2")
+@Version("1.5.0")
 package org.apache.jackrabbit.oak.spi.security.privilege;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManagerTest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManagerTest.java
index e1d45a5..c352516 100644
--- a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManagerTest.java
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlManagerTest.java
@@ -16,23 +16,9 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol;
 
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import javax.jcr.AccessDeniedException;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.security.AccessControlException;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
-
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
 import org.apache.jackrabbit.oak.api.ContentSession;
 import org.apache.jackrabbit.oak.api.Root;
@@ -46,13 +32,31 @@ import org.apache.jackrabbit.oak.spi.security.authorization.permission.EmptyPerm
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.OpenPermissionProvider;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
 import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConfiguration;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
 
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.Privilege;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_ADD_CHILD_NODES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_ALL;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_READ;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_BITS;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -62,10 +66,13 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 public class AbstractAccessControlManagerTest extends AbstractAccessControlTest {
 
@@ -90,43 +97,52 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
 
     @Before
     public void before() throws Exception {
-        testPrivileges = new Privilege[] {mockPrivilege("priv1"), mockPrivilege("priv2")};
-        allPrivileges = new Privilege[] {mockPrivilege(PrivilegeConstants.JCR_ALL)};
+        testPrivileges = new Privilege[] {mockPrivilege(JCR_READ), mockPrivilege(JCR_ADD_CHILD_NODES)};
+        allPrivileges = new Privilege[] {mockPrivilege(JCR_ALL)};
 
-        cs = Mockito.mock(ContentSession.class);
+        cs = mock(ContentSession.class);
         when(cs.getWorkspaceName()).thenReturn(WSP_NAME);
         when(cs.getAuthInfo()).thenReturn(new AuthInfoImpl(null, ImmutableMap.of(), testPrincipals));
 
         when(root.getContentSession()).thenReturn(cs);
 
-        Tree nonExistingTree = Mockito.mock(Tree.class);
+        Tree nonExistingTree = mock(Tree.class);
         when(nonExistingTree.exists()).thenReturn(false);
         when(root.getTree(nonExistingPath)).thenReturn(nonExistingTree);
 
-        Tree existingTree = Mockito.mock(Tree.class);
+        Tree existingTree = mock(Tree.class);
         when(existingTree.exists()).thenReturn(true);
         when(root.getTree(testPath)).thenReturn(existingTree);
 
-        Tree rootTree = Mockito.mock(Tree.class);
+        Tree rootTree = mock(Tree.class);
         when(rootTree.exists()).thenReturn(true);
         when(root.getTree("/")).thenReturn(rootTree);
-
-        privilegeManager = Mockito.mock(PrivilegeManager.class);
+        
+        Tree jcrAllTree = mock(Tree.class);
+        PrivilegeBits pb = PrivilegeBits.getInstance(PrivilegeBits.BUILT_IN.get(JCR_READ), PrivilegeBits.BUILT_IN.get(JCR_ADD_CHILD_NODES));
+        when(jcrAllTree.getProperty(REP_BITS)).thenReturn(pb.asPropertyState(REP_BITS));
+        Tree privilegesTree = mock(Tree.class);
+        when(privilegesTree.exists()).thenReturn(true);
+        when(privilegesTree.hasChild(JCR_ALL)).thenReturn(true);
+        when(privilegesTree.getChild(JCR_ALL)).thenReturn(jcrAllTree);
+        when(root.getTree(PrivilegeConstants.PRIVILEGES_PATH)).thenReturn(privilegesTree);
+
+        privilegeManager = mock(PrivilegeManager.class);
         when(privilegeManager.getRegisteredPrivileges()).thenReturn(testPrivileges);
-        when(privilegeManager.getPrivilege("priv1")).thenReturn(testPrivileges[0]);
-        when(privilegeManager.getPrivilege("priv2")).thenReturn(testPrivileges[1]);
-        when(privilegeManager.getPrivilege(PrivilegeConstants.JCR_ALL)).thenReturn(allPrivileges[0]);
+        when(privilegeManager.getPrivilege(JCR_READ)).thenReturn(testPrivileges[0]);
+        when(privilegeManager.getPrivilege(JCR_ADD_CHILD_NODES)).thenReturn(testPrivileges[1]);
+        when(privilegeManager.getPrivilege(JCR_ALL)).thenReturn(allPrivileges[0]);
 
-        PrivilegeConfiguration privilegeConfiguration = Mockito.mock(PrivilegeConfiguration.class);
+        PrivilegeConfiguration privilegeConfiguration = mock(PrivilegeConfiguration.class);
         when(privilegeConfiguration.getPrivilegeManager(root, getNamePathMapper())).thenReturn(privilegeManager);
 
-        authorizationConfiguration = Mockito.mock(AuthorizationConfiguration.class);
+        authorizationConfiguration = mock(AuthorizationConfiguration.class);
         when(authorizationConfiguration.getPermissionProvider(root, WSP_NAME, getEveryonePrincipalSet())).thenReturn(EmptyPermissionProvider.getInstance());
         when(authorizationConfiguration.getPermissionProvider(root, WSP_NAME, testPrincipals)).thenReturn(OpenPermissionProvider.getInstance());
         when(authorizationConfiguration.getPermissionProvider(root, WSP_NAME, ImmutableSet.of())).thenReturn(EmptyPermissionProvider.getInstance());
         when(authorizationConfiguration.getContext()).thenReturn(Context.DEFAULT);
 
-        securityProvider = Mockito.mock(SecurityProvider.class);
+        securityProvider = mock(SecurityProvider.class);
         when(securityProvider.getConfiguration(PrivilegeConfiguration.class)).thenReturn(privilegeConfiguration);
         when(securityProvider.getConfiguration(AuthorizationConfiguration.class)).thenReturn(authorizationConfiguration);
 
@@ -134,7 +150,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
     }
 
     private AbstractAccessControlManager createAccessControlManager(@NotNull Root root, @NotNull NamePathMapper namePathMapper) {
-        return new TestAcMgr(root, namePathMapper, securityProvider);
+        return mock(AbstractAccessControlManager.class, withSettings().useConstructor(root, namePathMapper, securityProvider).defaultAnswer(InvocationOnMock::callRealMethod));
     }
 
     private static List<String> getInvalidPaths() {
@@ -148,13 +164,13 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
     }
 
     private static Privilege mockPrivilege(@NotNull String name) {
-        Privilege p = Mockito.mock(Privilege.class);
+        Privilege p = mock(Privilege.class);
         when(p.getName()).thenReturn(name);
         return p;
     }
 
     private static Set<Principal> getEveryonePrincipalSet() {
-        return ImmutableSet.<Principal>of(EveryonePrincipal.getInstance());
+        return ImmutableSet.of(EveryonePrincipal.getInstance());
     }
 
     //--------------------------------------------------- protected methods >---
@@ -212,7 +228,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
     }
 
     @Test(expected = PathNotFoundException.class)
-    public void testGetTreeNonExstingPath() throws Exception {
+    public void testGetTreeNonExistingPath() throws Exception {
         acMgr.getTree(nonExistingPath, Permissions.NO_PERMISSION, false);
     }
 
@@ -247,6 +263,13 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
         mgr.getTree(testPath, Permissions.ALL, true);
     }
 
+    @Test
+    public void testGetPrivilegeBitsProvider() {
+        PrivilegeBitsProvider pbp = acMgr.getPrivilegeBitsProvider();
+        assertNotNull(pbp);
+        assertSame(pbp, acMgr.getPrivilegeBitsProvider());
+    }
+    
     //---------------------------------------------< getSupportedPrivileges >---
     @Test
     public void testGetSupportedPrivileges() throws Exception {
@@ -279,7 +302,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
     //--------------------------------------------------< privilegeFromName >---
     @Test
     public void testPrivilegeFromName() throws Exception {
-        List<Privilege> allPrivileges = Arrays.asList(privilegeManager.getRegisteredPrivileges());
+        Privilege[] allPrivileges = privilegeManager.getRegisteredPrivileges();
         for (Privilege privilege : allPrivileges) {
             Privilege p = acMgr.privilegeFromName(privilege.getName());
             assertEquals(privilege, p);
@@ -309,7 +332,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
 
     @Test(expected = PathNotFoundException.class)
     public void testHasPrivilegesNonExistingNodePathEmptyPrincipalSet() throws Exception {
-        acMgr.hasPrivileges(nonExistingPath, ImmutableSet.<Principal>of(), testPrivileges);
+        acMgr.hasPrivileges(nonExistingPath, ImmutableSet.of(), testPrivileges);
     }
 
     @Test
@@ -338,7 +361,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
     public void testHasPrivilegesInvalidPathsEveryoneSet() {
         for (String path : getInvalidPaths()) {
             try {
-                acMgr.hasPrivileges(path, ImmutableSet.<Principal>of(EveryonePrincipal.getInstance()), testPrivileges);
+                acMgr.hasPrivileges(path, ImmutableSet.of(EveryonePrincipal.getInstance()), testPrivileges);
                 fail("AccessControlManager#hasPrivileges for node that doesn't exist should fail.");
             } catch (RepositoryException e) {
                 // success
@@ -358,7 +381,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
 
     @Test
     public void testHasRepoPrivilegesEmptyPrincipalSet() throws Exception {
-        assertFalse(acMgr.hasPrivileges(null, ImmutableSet.<Principal>of(), testPrivileges));
+        assertFalse(acMgr.hasPrivileges(null, ImmutableSet.of(), testPrivileges));
     }
 
     //------------------------------------------------------< getPrivileges >---
@@ -369,7 +392,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
 
     @Test(expected = PathNotFoundException.class)
     public void testGetPrivilegesNonExistingNodePathEmptyPrincipalSet() throws Exception {
-        acMgr.getPrivileges(nonExistingPath, ImmutableSet.<Principal>of());
+        acMgr.getPrivileges(nonExistingPath, ImmutableSet.of());
     }
 
     @Test
@@ -394,7 +417,7 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
 
         for (String path : getInvalidPaths()) {
             try {
-                acMgr.getPrivileges(path, ImmutableSet.<Principal>of());
+                acMgr.getPrivileges(path, ImmutableSet.of());
                 fail("AccessControlManager#getPrivileges  for node that doesn't exist should fail.");
             } catch (RepositoryException e) {
                 // success
@@ -408,13 +431,13 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
     }
 
     @Test
-    public void testGetPrivilegesEveronePrincipalSet() throws Exception {
+    public void testGetPrivilegesEveryonePrincipalSet() throws Exception {
         assertArrayEquals(new Privilege[0], acMgr.getPrivileges(testPath, getEveryonePrincipalSet()));
     }
 
     @Test
     public void testGetPrivilegesEmptyPrincipalSet() throws Exception {
-        assertArrayEquals(new Privilege[0], acMgr.getPrivileges(testPath, ImmutableSet.<Principal>of()));
+        assertArrayEquals(new Privilege[0], acMgr.getPrivileges(testPath, ImmutableSet.of()));
     }
 
     @Test
@@ -440,56 +463,54 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
 
     @Test
     public void testGetRepoPrivilegesEmptyPrincipalSet() throws Exception {
-        assertArrayEquals(new Privilege[0], acMgr.getPrivileges(null, ImmutableSet.<Principal>of()));
+        assertArrayEquals(new Privilege[0], acMgr.getPrivileges(null, ImmutableSet.of()));
     }
 
-    private static class TestAcMgr extends AbstractAccessControlManager {
-
-        protected TestAcMgr(@NotNull Root root, @NotNull NamePathMapper namePathMapper, @NotNull SecurityProvider securityProvider) {
-            super(root, namePathMapper, securityProvider);
-        }
-
-        @NotNull
-        @Override
-        public JackrabbitAccessControlPolicy[] getApplicablePolicies(@NotNull Principal principal) {
-            throw new UnsupportedOperationException();
-        }
-
-        @NotNull
-        @Override
-        public JackrabbitAccessControlPolicy[] getPolicies(@NotNull Principal principal) {
-            throw new UnsupportedOperationException();
-        }
-
-        @NotNull
-        @Override
-        public AccessControlPolicy[] getEffectivePolicies(@NotNull Set<Principal> set) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public AccessControlPolicy[] getPolicies(String absPath)  {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public AccessControlPolicy[] getEffectivePolicies(String absPath) {
-            throw new UnsupportedOperationException();
+    //------------------------------------------------------< getPrivilegeCollection >---
+    @Test
+    public void testGetPrivilegeCollection() throws Exception {
+        PrivilegeCollection pe = acMgr.getPrivilegeCollection(testPath);
+        assertArrayEquals(allPrivileges, pe.getPrivileges());
+        
+        assertTrue(pe.includes(Arrays.stream(testPrivileges).map(Privilege::getName).distinct().toArray(String[]::new)));
+        assertTrue(pe.includes());
+        assertFalse(pe.includes(PrivilegeConstants.JCR_WORKSPACE_MANAGEMENT));
+        try {
+            pe.includes("invalid");
+            fail();
+        } catch (AccessControlException e) {
+            // success
         }
+    }
 
-        @Override
-        public AccessControlPolicyIterator getApplicablePolicies(String absPath) {
-            throw new UnsupportedOperationException();
-        }
+    @Test
+    public void testGetPrivilegeCollectionSamePrincipalSet() throws Exception {
+        PrivilegeCollection pc = acMgr.getPrivilegeCollection(testPath, testPrincipals);
+        assertArrayEquals(allPrivileges, pc.getPrivileges());
+        
+        verify(acMgr).getPrivilegeCollection(testPath);
+        verify(acMgr).getPrivilegeCollection(testPath, testPrincipals);
+    }
 
-        @Override
-        public void setPolicy(String absPath, AccessControlPolicy policy) {
-            throw new UnsupportedOperationException();
-        }
+    @Test
+    public void testGetPrivilegeCollectionEmptyPrincipalSet() throws Exception {
+        PrivilegeCollection pc = acMgr.getPrivilegeCollection(testPath, Collections.emptySet());
+        assertArrayEquals(new Privilege[0], pc.getPrivileges());
+        assertFalse(pc.includes(JCR_READ));
+        assertTrue(pc.includes());
 
-        @Override
-        public void removePolicy(String absPath, AccessControlPolicy policy) {
-            throw new UnsupportedOperationException();
-        }
+        verify(acMgr, never()).getPrivilegeCollection(testPath);
+        verify(acMgr).getPrivilegeCollection(testPath, Collections.emptySet());
+    }
+    
+    @Test
+    public void testGetPrivilegeCollectionPrincipalSet() throws Exception {
+        PrivilegeCollection pc = acMgr.getPrivilegeCollection(testPath, getEveryonePrincipalSet());
+        assertArrayEquals(new Privilege[0], pc.getPrivileges());
+        assertFalse(pc.includes(Arrays.stream(testPrivileges).map(Privilege::getName).distinct().toArray(String[]::new)));
+        assertTrue(pc.includes());
+        
+        verify(acMgr).getPrivilegeCollection(testPath, getEveryonePrincipalSet());
+        verify(acMgr, never()).getPrivilegeCollection(testPath);
     }
 }
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProviderTest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProviderTest.java
index 8f6b3c4..a0595f6 100644
--- a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProviderTest.java
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeBitsProviderTest.java
@@ -31,7 +31,9 @@ import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import javax.jcr.security.AccessControlException;
 import javax.jcr.security.Privilege;
+import java.util.Collections;
 import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
@@ -41,6 +43,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -229,6 +232,51 @@ public class PrivilegeBitsProviderTest implements PrivilegeConstants {
     }
 
     @Test
+    public void testBitsValidateEmpty() throws Exception {
+        PrivilegeBits bits = bitsProvider.getBits(Collections.emptySet(), true);
+        assertSame(PrivilegeBits.EMPTY, bits);
+
+        bits = bitsProvider.getBits(Collections.emptySet(), false);
+        assertSame(PrivilegeBits.EMPTY, bits);
+
+        verify(root, never()).getTree(PRIVILEGES_PATH);
+        verify(privTree, never()).getChild(anyString());
+    }
+    
+    @Test
+    public void testGetBitsValidateTwiceBuitInKnown() throws Exception {
+        Iterable<String> names = ImmutableList.of(KNOWN_PRIV_NAME, JCR_ADD_CHILD_NODES);
+        PrivilegeBits bits1 = bitsProvider.getBits(names, true);
+        PrivilegeBits bits2 = bitsProvider.getBits(names, false);
+
+        assertNotSame(bits1, bits2);
+        assertEquals(bits1, bits2);
+
+        verify(root, times(1)).getTree(PRIVILEGES_PATH);
+        verify(privTree, times(1)).getChild(KNOWN_PRIV_NAME);
+    }
+    
+    @Test(expected = AccessControlException.class)
+    public void testBitsValidateNonExistingTree() throws Exception {
+        Iterable<String> names = ImmutableList.of(KNOWN_PRIV_NAME, JCR_ADD_CHILD_NODES);
+
+        when(privTree.exists()).thenReturn(true);
+        when(privTree.hasChild(KNOWN_PRIV_NAME)).thenReturn(false);
+        // privilegesTree has no child for KNOWN_PRIV_NAME -> must fail
+        bitsProvider.getBits(names, true);
+    }
+
+    public void testBitsValidateFalseNonExistingTree() throws Exception {
+        Iterable<String> names = ImmutableList.of(KNOWN_PRIV_NAME, JCR_ADD_CHILD_NODES);
+
+        when(privTree.exists()).thenReturn(true);
+        when(privTree.hasChild(KNOWN_PRIV_NAME)).thenReturn(false);
+        
+        // validation is disabled => same as getBits(Iterable)
+        assertEquals(bitsProvider.getBits(names), bitsProvider.getBits(names, false));
+    }
+
+    @Test
     public void testGetBitsFromEmptyPrivileges() {
         assertSame(PrivilegeBits.EMPTY, bitsProvider.getBits(new Privilege[0], NamePathMapper.DEFAULT));
     }
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtilTest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtilTest.java
index 9af8854..764be10 100644
--- a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtilTest.java
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/privilege/PrivilegeUtilTest.java
@@ -21,17 +21,28 @@ import com.google.common.collect.Iterables;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.Privilege;
+
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_LIFECYCLE_MANAGEMENT;
 import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_READ;
 import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.PRIVILEGES_PATH;
 import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_AGGREGATES;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 public class PrivilegeUtilTest {
@@ -65,6 +76,49 @@ public class PrivilegeUtilTest {
         PrivilegeDefinition def = PrivilegeUtil.readDefinition(defTree);
         assertEquals("name", def.getName());
         assertTrue(Iterables.elementsEqual(aggregateNames, PrivilegeUtil.readDefinition(defTree).getDeclaredAggregateNames()));
+    }
+    
+    @Test
+    public void testGetPrivilegeOakNameDefaultMapper() throws Exception {
+        assertEquals(JCR_READ, PrivilegeUtil.getOakName(JCR_READ, NamePathMapper.DEFAULT));
+        assertEquals(Privilege.JCR_ADD_CHILD_NODES, PrivilegeUtil.getOakName(Privilege.JCR_ADD_CHILD_NODES, NamePathMapper.DEFAULT));
+        assertEquals("anystring", PrivilegeUtil.getOakName("anystring", NamePathMapper.DEFAULT));
+    }
+
+    @Test
+    public void testGetPrivilegeOakName() throws Exception {
+        NamePathMapper mapper = mock(NamePathMapper.class);
+        when(mapper.getOakNameOrNull(Privilege.JCR_LIFECYCLE_MANAGEMENT)).thenReturn(JCR_LIFECYCLE_MANAGEMENT);
+        assertEquals(JCR_LIFECYCLE_MANAGEMENT, PrivilegeUtil.getOakName(Privilege.JCR_LIFECYCLE_MANAGEMENT, mapper));
+        verify(mapper).getOakNameOrNull(Privilege.JCR_LIFECYCLE_MANAGEMENT);
+        verifyNoMoreInteractions(mapper);
+    }
+    
+    @Test(expected = AccessControlException.class)
+    public void testGetPrivilegeOakNameFromNull() throws RepositoryException {
+        PrivilegeUtil.getOakName(null, NamePathMapper.DEFAULT);
+    }
 
+    @Test(expected = AccessControlException.class)
+    public void testGetPrivilegeOakNameResolvesToNull() throws RepositoryException {
+        PrivilegeUtil.getOakName(Privilege.JCR_READ, new NamePathMapper.Default() {
+            public @Nullable String getOakNameOrNull(@NotNull String jcrName) {
+                return null;
+            }
+        });
+    }
+    
+    @Test
+    public void testGetPrivilegeOakNamesFromNull() throws Exception {
+        NamePathMapper mapper = mock(NamePathMapper.class);
+        assertTrue(PrivilegeUtil.getOakNames(null, mapper).isEmpty());
+        verifyNoInteractions(mapper);
+    }
+    
+    @Test
+    public void testGetPrivilegeOakNamesFromEmptyArray() throws Exception {
+        NamePathMapper mapper = mock(NamePathMapper.class);
+        assertTrue(PrivilegeUtil.getOakNames(new String[0], mapper).isEmpty());
+        verifyNoInteractions(mapper);
     }
 }
\ No newline at end of file