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/12/14 11:22:53 UTC

[jackrabbit-oak] branch trunk updated: OAK-9637 : Additional API to retrieve PrivilegeCollection to avoid manual resolution of privilege aggregation

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


The following commit(s) were added to refs/heads/trunk by this push:
     new fe6d29b  OAK-9637 : Additional API to retrieve PrivilegeCollection to avoid manual resolution of privilege aggregation
     new 759f9c8  Merge branch 'trunk' of https://github.com/apache/jackrabbit-oak into trunk
fe6d29b is described below

commit fe6d29b1e337f81c7ed4c51856b217ce3d44ca01
Author: angela <an...@adobe.com>
AuthorDate: Tue Dec 14 12:22:24 2021 +0100

    OAK-9637 : Additional API to retrieve PrivilegeCollection to avoid manual resolution of privilege aggregation
---
 .../impl/PrincipalBasedAccessControlManager.java   |   5 +
 .../principalbased/impl/PrincipalPolicyImpl.java   |   5 +
 .../principalbased/impl/AbstractEntryTest.java     |   5 +
 .../impl/PrincipalPolicyImplTest.java              |   6 +
 .../accesscontrol/AccessControlManagerImpl.java    |   8 +-
 .../authorization/accesscontrol/ACLTest.java       |  34 +-----
 .../accesscontrol/AbstractAccessControlTest.java   |  10 ++
 .../AccessControlManagerImplTest.java              |  37 ++++++
 .../authorization/accesscontrol/EntryTest.java     | 134 ++++++++-------------
 .../authorization/accesscontrol/UtilTest.java      |  52 ++++----
 .../L5_AccessControlListImplTest.java              |   7 ++
 .../api/security/JackrabbitAccessControlEntry.java |  20 ++-
 .../security/JackrabbitAccessControlManager.java   |  26 ++++
 .../api/security/authorization/package-info.java   |   2 +-
 .../jackrabbit/api/security/package-info.java      |   2 +-
 .../security/authorization/accesscontrol/ACE.java  |  30 ++++-
 .../AbstractAccessControlManager.java              |  37 +++---
 .../accesscontrol/AbstractPrivilegeCollection.java |  68 +++++++++++
 .../authorization/accesscontrol/package-info.java  |   2 +-
 .../authorization/accesscontrol/ACETest.java       | 117 +++++++++++-------
 .../AbstractAccessControlManagerTest.java          |  11 ++
 .../accesscontrol/AbstractAccessControlTest.java   |  29 +++--
 .../AbstractPrivilegeCollectionTest.java           | 130 ++++++++++++++++++++
 23 files changed, 562 insertions(+), 215 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 8204d8d..eaec709 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
@@ -404,6 +404,11 @@ class PrincipalBasedAccessControlManager extends AbstractAccessControlManager im
             }
 
             @Override
+            protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+                return privilegeBitsProvider;
+            }
+            
+            @Override
             public Privilege[] getPrivileges() {
                 Set<String> names =  privilegeBitsProvider.getPrivilegeNames(getPrivilegeBits());
                 return Utils.privilegesFromOakNames(names, mgrProvider.getPrivilegeManager(), getNamePathMapper());
diff --git a/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImpl.java b/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImpl.java
index b4d2413..5ecec6e 100644
--- a/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImpl.java
+++ b/oak-authorization-principalbased/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImpl.java
@@ -275,5 +275,10 @@ class PrincipalPolicyImpl extends AbstractAccessControlList implements Principal
         @NotNull NamePathMapper getNamePathMapper() {
             return PrincipalPolicyImpl.this.getNamePathMapper();
         }
+
+        @Override
+        protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+            return privilegeBitsProvider;
+        }
     }
 }
diff --git a/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/AbstractEntryTest.java b/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/AbstractEntryTest.java
index fa7593a..4699165 100644
--- a/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/AbstractEntryTest.java
+++ b/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/AbstractEntryTest.java
@@ -142,6 +142,11 @@ public class AbstractEntryTest extends AbstractPrincipalBasedTest {
         }
 
         @Override
+        protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+            return bitsProvider;
+        }
+        
+        @Override
         public Privilege[] getPrivileges() {
             try {
                 return privilegesFromNames(bitsProvider.getPrivilegeNames(getPrivilegeBits()));
diff --git a/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImplTest.java b/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImplTest.java
index 5b4732a..a821844 100644
--- a/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImplTest.java
+++ b/oak-authorization-principalbased/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/principalbased/impl/PrincipalPolicyImplTest.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree;
@@ -611,6 +612,11 @@ public class PrincipalPolicyImplTest extends AbstractPrincipalBasedTest {
             }
 
             @Override
+            public @NotNull PrivilegeCollection getPrivilegeCollection() throws RepositoryException {
+                return entry.getPrivilegeCollection();
+            }
+
+            @Override
             public Principal getPrincipal() {
                 return entry.getPrincipal();
             }
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 237c23f..f8804c3 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,6 +80,7 @@ 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;
@@ -683,7 +684,7 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
 
         @Override
         boolean checkValidPrincipal(@Nullable Principal principal) throws AccessControlException {
-            // principal asspciated with the policy has been validated before -> make sure only entries for the same
+            // principal associated with the policy has been validated before -> make sure only entries for the same
             // principal are created
             if (principal == null || !this.principal.getName().equals(principal.getName())) {
                 throw new AccessControlException("Principal mismatch.");
@@ -756,6 +757,11 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple
         }
 
         @Override
+        protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+            return AccessControlManagerImpl.this.getPrivilegeBitsProvider();
+        }
+
+        @Override
         public Privilege[] getPrivileges() {
             Set<Privilege> privileges = new HashSet<>();
             for (String name : getPrivilegeBitsProvider().getPrivilegeNames(getPrivilegeBits())) {
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java
index 4bcfc5a..cd1d9de 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/ACLTest.java
@@ -75,6 +75,8 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -306,34 +308,10 @@ public class ACLTest extends AbstractAccessControlTest implements PrivilegeConst
 
     @Test(expected = AccessControlException.class)
     public void testRemoveInvalidEntry() throws Exception {
-        acl.removeAccessControlEntry(new JackrabbitAccessControlEntry() {
-            public boolean isAllow() {
-                return false;
-            }
-
-            @NotNull
-            public String[] getRestrictionNames() {
-                return new String[0];
-            }
-
-            @Nullable
-            public Value getRestriction(@NotNull String restrictionName) {
-                return null;
-            }
-
-            @Nullable
-            public Value[] getRestrictions(@NotNull String restrictionName) {
-                return null;
-            }
-
-            public Principal getPrincipal() {
-                return testPrincipal;
-            }
-
-            public Privilege[] getPrivileges() {
-                return testPrivileges;
-            }
-        });
+        JackrabbitAccessControlEntry ace = mockAccessControlEntry(testPrincipal, testPrivileges);
+        acl.removeAccessControlEntry(ace);
+        verify(ace, never()).getPrincipal();
+        verify(ace, never()).getPrivileges();
     }
 
     @Test(expected = AccessControlException.class)
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AbstractAccessControlTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AbstractAccessControlTest.java
index 51fdcab..55a2b76 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AbstractAccessControlTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AbstractAccessControlTest.java
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.oak.security.authorization.accesscontrol;
 
 import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
 import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
@@ -47,6 +48,8 @@ import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public abstract class AbstractAccessControlTest extends AbstractSecurityTest {
 
@@ -168,6 +171,13 @@ public abstract class AbstractAccessControlTest extends AbstractSecurityTest {
         };
     }
     
+    static JackrabbitAccessControlEntry mockAccessControlEntry(@NotNull Principal principal, @NotNull Privilege[] privs) {
+        JackrabbitAccessControlEntry ace = mock(JackrabbitAccessControlEntry.class);
+        when(ace.getPrincipal()).thenReturn(principal);
+        when(ace.getPrivileges()).thenReturn(privs);
+        return ace;
+    }
+    
     static void assertPolicies(@Nullable AccessControlPolicy[] policies, long expectedSize) {
         assertNotNull(policies);
         assertEquals(expectedSize, policies.length);
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java
index 01e54cf..ac796c6 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImplTest.java
@@ -26,6 +26,7 @@ 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.PrivilegeCollection;
 import org.apache.jackrabbit.api.security.principal.GroupPrincipal;
 import org.apache.jackrabbit.oak.api.ContentSession;
 import org.apache.jackrabbit.oak.api.QueryEngine;
@@ -52,6 +53,7 @@ import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restrict
 import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
 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.security.privilege.PrivilegeConstants;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -89,11 +91,16 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Sets.newHashSet;
 import static java.util.Collections.singletonMap;
 import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.NT_OAK_UNSTRUCTURED;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_ALL;
 import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_LOCK_MANAGEMENT;
 import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_READ;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_VERSION_MANAGEMENT;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_NODES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_PROPERTIES;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -1976,6 +1983,11 @@ public class AccessControlManagerImplTest extends AbstractAccessControlTest impl
         Privilege[] privs = privilegesFromNames(PrivilegeConstants.JCR_LIFECYCLE_MANAGEMENT);
         acl.getEntries().add(new ACE(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_LIFECYCLE_MANAGEMENT), false, Collections.emptySet(), getNamePathMapper()) {
             @Override
+            protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+                return AccessControlManagerImplTest.this.getBitsProvider();
+            }
+
+            @Override
             public Privilege[] getPrivileges() {
                 return privs;
             }
@@ -2071,6 +2083,31 @@ public class AccessControlManagerImplTest extends AbstractAccessControlTest impl
         assertEquals(0, repoLevelPolicies.length);
     }
 
+    //------------------------------------< privilegeCollectionFromNames() >---
+    @Test
+    public void testPrivilegeCollectionFromNames() throws Exception {
+        PrivilegeCollection pc = acMgr.privilegeCollectionFromNames(Privilege.JCR_READ, Privilege.JCR_WRITE, Privilege.JCR_VERSION_MANAGEMENT);
+        
+        assertEquals(pc, acMgr.privilegeCollectionFromNames(Privilege.JCR_READ, Privilege.JCR_WRITE, Privilege.JCR_VERSION_MANAGEMENT));
+        assertEquals(pc, acMgr.privilegeCollectionFromNames(JCR_READ, PrivilegeConstants.JCR_WRITE, JCR_VERSION_MANAGEMENT));
+        assertEquals(pc, acMgr.privilegeCollectionFromNames(REP_READ_NODES, REP_READ_PROPERTIES, PrivilegeConstants.JCR_WRITE, JCR_VERSION_MANAGEMENT));
+        
+        assertNotEquals(pc, acMgr.privilegeCollectionFromNames());
+        assertNotEquals(pc, acMgr.privilegeCollectionFromNames(JCR_READ));
+        assertNotEquals(pc, acMgr.privilegeCollectionFromNames(JCR_ALL));
+        assertNotEquals(pc, acMgr.privilegeCollectionFromNames(JCR_READ, PrivilegeConstants.JCR_WRITE));
+        assertNotEquals(pc, acMgr.privilegeCollectionFromNames(JCR_READ, JCR_VERSION_MANAGEMENT));
+        
+        assertTrue(pc.includes());
+        assertTrue(pc.includes(Privilege.JCR_READ, Privilege.JCR_WRITE));
+        assertTrue(pc.includes(PrivilegeConstants.JCR_WRITE, Privilege.JCR_VERSION_MANAGEMENT));
+        assertTrue(pc.includes(REP_READ_NODES, PrivilegeConstants.JCR_ADD_CHILD_NODES));
+        
+        assertFalse(pc.includes(PrivilegeConstants.JCR_ALL));
+        assertFalse(pc.includes(PrivilegeConstants.REP_WRITE));
+        assertFalse(pc.includes(PrivilegeConstants.REP_USER_MANAGEMENT, PrivilegeConstants.REP_ALTER_PROPERTIES));
+    }
+
     private final static class TestACL extends AbstractAccessControlList {
 
         private final List<JackrabbitAccessControlEntry> entries = new ArrayList<>();
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/EntryTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/EntryTest.java
index 1542262..cd672a3 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/EntryTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/EntryTest.java
@@ -19,14 +19,16 @@ package org.apache.jackrabbit.oak.security.authorization.accesscontrol;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.namepath.impl.GlobalNameMapper;
+import org.apache.jackrabbit.oak.namepath.impl.NamePathMapperImpl;
 import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.ACE;
 import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restriction;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -48,6 +50,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_READ;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_WRITE;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_ALTER_PROPERTIES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_NODES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_PROPERTIES;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -56,6 +63,8 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 
 public class EntryTest extends AbstractAccessControlTest {
 
@@ -69,6 +78,7 @@ public class EntryTest extends AbstractAccessControlTest {
     @Before
     public void before() throws Exception {
         super.before();
+        namePathMapper = new NamePathMapperImpl(new GlobalNameMapper(root));
 
         acMgr = getAccessControlManager(root);
         testPrincipal = () -> "TestPrincipal";
@@ -97,7 +107,7 @@ public class EntryTest extends AbstractAccessControlTest {
     }
 
     private ACE createEntry(Set<Restriction> restrictions) throws Exception {
-        return createEntry(testPrincipal, true, restrictions, PrivilegeConstants.JCR_READ);
+        return createEntry(testPrincipal, true, restrictions, JCR_READ);
     }
 
     private Restriction createRestriction(Value value) throws Exception {
@@ -110,16 +120,16 @@ public class EntryTest extends AbstractAccessControlTest {
 
     @Test
     public void testIsAllow() throws RepositoryException {
-        ACE ace = createEntry(new String[]{PrivilegeConstants.JCR_READ}, true);
+        ACE ace = createEntry(new String[]{JCR_READ}, true);
         assertTrue(ace.isAllow());
 
-        ace = createEntry(new String[]{PrivilegeConstants.JCR_READ}, false);
+        ace = createEntry(new String[]{JCR_READ}, false);
         assertFalse(ace.isAllow());
     }
 
     @Test
     public void testGetPrincipal() throws RepositoryException {
-        ACE tmpl = createEntry(new String[]{PrivilegeConstants.JCR_READ}, true);
+        ACE tmpl = createEntry(new String[]{JCR_READ}, true);
         assertNotNull(tmpl.getPrincipal());
         assertEquals(testPrincipal.getName(), tmpl.getPrincipal().getName());
         assertSame(testPrincipal, tmpl.getPrincipal());
@@ -135,12 +145,12 @@ public class EntryTest extends AbstractAccessControlTest {
 
     @Test
     public void testGetPrivileges() throws RepositoryException {
-        ACE entry = createEntry(new String[]{PrivilegeConstants.JCR_READ}, true);
+        ACE entry = createEntry(new String[]{JCR_READ}, true);
 
         Privilege[] privs = entry.getPrivileges();
         assertNotNull(privs);
         assertEquals(1, privs.length);
-        assertEquals(privs[0], acMgr.privilegeFromName(PrivilegeConstants.JCR_READ));
+        assertEquals(privs[0], acMgr.privilegeFromName(JCR_READ));
 
         entry = createEntry(new String[]{PrivilegeConstants.REP_WRITE}, true);
         privs = entry.getPrivileges();
@@ -162,11 +172,11 @@ public class EntryTest extends AbstractAccessControlTest {
 
     @Test
     public void testGetPrivilegeBits() throws RepositoryException {
-        ACE entry = createEntry(new String[]{PrivilegeConstants.JCR_READ}, true);
+        ACE entry = createEntry(new String[]{JCR_READ}, true);
 
         PrivilegeBits bits = entry.getPrivilegeBits();
         assertNotNull(bits);
-        assertEquals(bits, getBitsProvider().getBits(PrivilegeConstants.JCR_READ));
+        assertEquals(bits, getBitsProvider().getBits(JCR_READ));
 
         entry = createEntry(new String[]{PrivilegeConstants.REP_WRITE}, true);
         bits = entry.getPrivilegeBits();
@@ -184,20 +194,10 @@ public class EntryTest extends AbstractAccessControlTest {
         assertEquals(expected, bits);
     }
 
-    @Test(expected = AccessControlException.class)
-    public void testNullPrivileges() throws Exception {
-        new EmptyACE(null);
-    }
-
-    @Test(expected = AccessControlException.class)
-    public void testEmptyPrivileges() throws Exception {
-        new EmptyACE(PrivilegeBits.EMPTY);
-    }
-
     @Test
     public void testRedundantPrivileges() throws Exception {
-        ACE ace = createEntry(PrivilegeConstants.JCR_READ, PrivilegeConstants.JCR_READ);
-        assertEquals(getBitsProvider().getBits(PrivilegeConstants.JCR_READ), ace.getPrivilegeBits());
+        ACE ace = createEntry(JCR_READ, JCR_READ);
+        assertEquals(getBitsProvider().getBits(JCR_READ), ace.getPrivilegeBits());
     }
 
     /**
@@ -226,17 +226,17 @@ public class EntryTest extends AbstractAccessControlTest {
                 return new Privilege[0];
             }
         };
-        Privilege[] privs = new Privilege[]{invalidPriv, acMgr.privilegeFromName(PrivilegeConstants.JCR_READ)};
+        Privilege[] privs = new Privilege[]{invalidPriv, acMgr.privilegeFromName(JCR_READ)};
         ACE entry = createEntry(testPrincipal, privs, true);
-        assertEquals(getBitsProvider().getBits(PrivilegeConstants.JCR_READ), entry.getPrivilegeBits());
+        assertEquals(getBitsProvider().getBits(JCR_READ), entry.getPrivilegeBits());
     }
 
     @Test
     public void testAggregatePrivileges() throws Exception {
         ACE ace = createEntry(PrivilegeConstants.REP_READ_NODES, PrivilegeConstants.REP_READ_PROPERTIES);
 
-        assertEquals(getBitsProvider().getBits(PrivilegeConstants.JCR_READ), ace.getPrivilegeBits());
-        assertArrayEquals(privilegesFromNames(PrivilegeConstants.JCR_READ), ace.getPrivileges());
+        assertEquals(getBitsProvider().getBits(JCR_READ), ace.getPrivilegeBits());
+        assertArrayEquals(privilegesFromNames(JCR_READ), ace.getPrivileges());
     }
 
     @Test
@@ -386,6 +386,23 @@ public class EntryTest extends AbstractAccessControlTest {
 
         assertTrue(ace.getRestrictions().isEmpty());
     }
+    
+    @Test
+    public void testGetPrivilegeCollection() throws Exception {
+        PrivilegeCollection pc = createEntry(Privilege.JCR_READ, Privilege.JCR_WRITE).getPrivilegeCollection();
+        Set<Privilege> expected = ImmutableSet.copyOf(AccessControlUtils.privilegesFromNames(acMgr, Privilege.JCR_READ, Privilege.JCR_WRITE));
+        assertEquals(expected, ImmutableSet.copyOf(pc.getPrivileges()));
+        
+        assertEquals(pc, createEntry(JCR_READ, JCR_WRITE).getPrivilegeCollection());
+        assertEquals(pc, createEntry(JCR_READ, PrivilegeConstants.JCR_ADD_CHILD_NODES, PrivilegeConstants.JCR_MODIFY_PROPERTIES, 
+                PrivilegeConstants.JCR_WRITE).getPrivilegeCollection());
+        
+        assertTrue(pc.includes(JCR_READ));
+        assertTrue(pc.includes(JCR_READ, JCR_WRITE));
+        assertTrue(pc.includes(JCR_READ, REP_READ_PROPERTIES, REP_READ_NODES, REP_ALTER_PROPERTIES));
+        
+        assertFalse(pc.includes(Privilege.JCR_ALL));
+    }
 
     @Test
     public void testEquals() throws RepositoryException {
@@ -421,9 +438,9 @@ public class EntryTest extends AbstractAccessControlTest {
 
     @Test
     public void testEquals2() throws RepositoryException {
-        ACE ace = createEntry(PrivilegeConstants.JCR_ADD_CHILD_NODES, PrivilegeConstants.JCR_READ);
+        ACE ace = createEntry(PrivilegeConstants.JCR_ADD_CHILD_NODES, JCR_READ);
         // priv array contains duplicates
-        ACE ace2 = createEntry(PrivilegeConstants.JCR_ADD_CHILD_NODES, PrivilegeConstants.JCR_ADD_CHILD_NODES, PrivilegeConstants.JCR_READ);
+        ACE ace2 = createEntry(PrivilegeConstants.JCR_ADD_CHILD_NODES, PrivilegeConstants.JCR_ADD_CHILD_NODES, JCR_READ);
 
         assertEquals(ace, ace2);
     }
@@ -441,7 +458,7 @@ public class EntryTest extends AbstractAccessControlTest {
         otherAces.add(createEntry(princ, privs, true));
 
         // ACE template with different privileges
-        otherAces.add(createEntry(new String[]{PrivilegeConstants.JCR_READ}, true));
+        otherAces.add(createEntry(new String[]{JCR_READ}, true));
 
         // ACE template with different 'allow' flag
         otherAces.add(createEntry(new String[]{PrivilegeConstants.JCR_ALL}, false));
@@ -450,12 +467,15 @@ public class EntryTest extends AbstractAccessControlTest {
         otherAces.add(createEntry(new String[]{PrivilegeConstants.REP_WRITE}, false));
 
         // other ace impl
-        JackrabbitAccessControlEntry pe = new Jace(testPrincipal, privs);
+        JackrabbitAccessControlEntry pe = mockAccessControlEntry(ace.getPrincipal(), ace.getPrivileges());
         otherAces.add(pe);
 
         for (JackrabbitAccessControlEntry otherAce : otherAces) {
             assertNotEquals(ace, otherAce);
         }
+
+        verify(pe, never()).getPrincipal();
+        verify(pe, never()).getPrivileges();
     }
 
     @Test
@@ -502,7 +522,7 @@ public class EntryTest extends AbstractAccessControlTest {
         otherAces.add(createEntry(princ, privs, true));
 
         // ACE template with different privileges
-        otherAces.add(createEntry(new String[]{PrivilegeConstants.JCR_READ}, true));
+        otherAces.add(createEntry(new String[]{JCR_READ}, true));
 
         // ACE template with different 'allow' flag
         otherAces.add(createEntry(new String[]{PrivilegeConstants.JCR_ALL}, false));
@@ -511,62 +531,14 @@ public class EntryTest extends AbstractAccessControlTest {
         otherAces.add(createEntry(new String[]{PrivilegeConstants.REP_WRITE}, false));
 
         // other ace impl
-        JackrabbitAccessControlEntry pe = new Jace(testPrincipal, privs);
+        JackrabbitAccessControlEntry pe = mockAccessControlEntry(ace.getPrincipal(), ace.getPrivileges());
         otherAces.add(pe);
 
         for (JackrabbitAccessControlEntry otherAce : otherAces) {
             assertNotEquals(ace.hashCode(), otherAce.hashCode());
         }
 
-    }
-
-    private class EmptyACE extends ACE {
-
-        EmptyACE(PrivilegeBits privilegeBits) throws AccessControlException {
-            super(testPrincipal, privilegeBits, true, null, namePathMapper);
-        }
-
-        @Override
-        public Privilege[] getPrivileges() {
-            return new Privilege[0];
-        }
-    }
-
-    private static final class Jace implements JackrabbitAccessControlEntry {
-
-        private final Principal principal;
-        private final Privilege[] privs;
-
-        private Jace(@NotNull Principal principal, @NotNull Privilege[] privs) {
-            this.principal = principal;
-            this.privs = privs;
-        }
-
-        public boolean isAllow() {
-            return true;
-        }
-
-        @NotNull
-        public String[] getRestrictionNames() {
-            return new String[0];
-        }
-
-        @Nullable
-        public Value getRestriction(@NotNull String restrictionName) {
-            return null;
-        }
-
-        @Nullable
-        public Value[] getRestrictions(@NotNull String restrictionName) {
-            return null;
-        }
-
-        public Principal getPrincipal() {
-            return principal;
-        }
-
-        public Privilege[] getPrivileges() {
-            return privs;
-        }
+        verify(pe, never()).getPrincipal();
+        verify(pe, never()).getPrivileges();
     }
 }
\ No newline at end of file
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/UtilTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/UtilTest.java
index c0c179b..a7166f9 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/UtilTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/UtilTest.java
@@ -16,10 +16,6 @@
  */
 package org.apache.jackrabbit.oak.security.authorization.accesscontrol;
 
-import javax.jcr.RepositoryException;
-import javax.jcr.security.AccessControlException;
-import javax.jcr.security.Privilege;
-
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
@@ -40,14 +36,21 @@ import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
 import org.junit.Before;
 import org.junit.Test;
 
+import javax.jcr.security.AccessControlException;
+
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 public class UtilTest extends AbstractSecurityTest {
 
@@ -116,8 +119,8 @@ public class UtilTest extends AbstractSecurityTest {
     }
 
     @Test
-    public void testGenerateName() throws AccessControlException {
-        ACE ace = new TestAce(true);
+    public void testGenerateName() {
+        ACE ace = mockACE(true);
         String name = Util.generateAceName(ace, 0);
 
         assertTrue(name.startsWith(ALLOW));
@@ -128,11 +131,14 @@ public class UtilTest extends AbstractSecurityTest {
         assertTrue(name.startsWith(ALLOW));
         assertEquals(ALLOW + 1, name);
         assertEquals(name, Util.generateAceName(ace, 1));
+
+        verify(ace, times(4)).isAllow();
+        verifyNoMoreInteractions(ace);
     }
 
     @Test
-    public void testGenerateName2() throws AccessControlException {
-        ACE ace = new TestAce(false);
+    public void testGenerateName2() {
+        ACE ace = mockACE(false);
         String name = Util.generateAceName(ace, 0);
 
         assertTrue(name.startsWith(DENY));
@@ -143,34 +149,28 @@ public class UtilTest extends AbstractSecurityTest {
         assertTrue(name.startsWith(DENY));
         assertEquals(DENY + 2, name);
         assertEquals(name, Util.generateAceName(ace, 2));
+
+        verify(ace, times(4)).isAllow();
+        verifyNoMoreInteractions(ace);
     }
 
     @Test
-    public void testGenerateNameDifferentAllow() throws Exception {
-        ACE allow = new TestAce(false);
-        ACE deny = new TestAce(true);
+    public void testGenerateNameDifferentAllow() {
+        ACE allow = mockACE(false);
+        ACE deny = mockACE(true);
 
         assertNotEquals(Util.generateAceName(allow, 0), Util.generateAceName(deny, 0));
         assertNotEquals(Util.generateAceName(allow, 1), Util.generateAceName(deny, 1));
         assertNotEquals(Util.generateAceName(allow, 20), Util.generateAceName(deny, 20));
         assertNotEquals(Util.generateAceName(allow, 0), Util.generateAceName(deny, 1));
         assertNotEquals(Util.generateAceName(allow, 1), Util.generateAceName(deny, 20));
-
+        
+        verify(allow, times(5)).isAllow();
+        verify(deny, times(5)).isAllow();
+        verifyNoMoreInteractions(allow, deny);
     }
 
-    private final class TestAce extends ACE {
-
-        TestAce(boolean isAllow) throws AccessControlException {
-            super(EveryonePrincipal.getInstance(), bitsProvider.getBits(PrivilegeConstants.JCR_READ), isAllow, null, NamePathMapper.DEFAULT);
-        }
-
-        @Override
-        public Privilege[] getPrivileges() {
-            try {
-                return privilegesFromNames(bitsProvider.getPrivilegeNames(getPrivilegeBits()));
-            } catch (RepositoryException e) {
-                throw new RuntimeException(e.getMessage());
-            }
-        }
+    private ACE mockACE(boolean isAllow) {
+        return mock(ACE.class, withSettings().useConstructor(EveryonePrincipal.getInstance(), bitsProvider.getBits(PrivilegeConstants.JCR_READ), isAllow, null, NamePathMapper.DEFAULT).defaultAnswer(CALLS_REAL_METHODS));
     }
 }
diff --git a/oak-exercise/src/test/java/org/apache/jackrabbit/oak/exercise/security/authorization/accesscontrol/L5_AccessControlListImplTest.java b/oak-exercise/src/test/java/org/apache/jackrabbit/oak/exercise/security/authorization/accesscontrol/L5_AccessControlListImplTest.java
index 1a76875..114b14e 100644
--- a/oak-exercise/src/test/java/org/apache/jackrabbit/oak/exercise/security/authorization/accesscontrol/L5_AccessControlListImplTest.java
+++ b/oak-exercise/src/test/java/org/apache/jackrabbit/oak/exercise/security/authorization/accesscontrol/L5_AccessControlListImplTest.java
@@ -20,6 +20,7 @@ import java.security.Principal;
 import java.util.Collections;
 import java.util.List;
 import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
 import javax.jcr.security.AccessControlEntry;
 import javax.jcr.security.AccessControlException;
@@ -31,6 +32,7 @@ import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.JackrabbitWorkspace;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
 import org.apache.jackrabbit.oak.exercise.ExerciseUtility;
@@ -266,6 +268,11 @@ public class L5_AccessControlListImplTest extends AbstractJCRTest {
                     return null;
                 }
 
+                @Override
+                public @NotNull PrivilegeCollection getPrivilegeCollection() throws RepositoryException {
+                    throw new UnsupportedRepositoryOperationException();
+                }
+
                 public Principal getPrincipal() {
                     return testPrincipal;
                 }
diff --git a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
index 829915c..754e5e4 100644
--- a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
+++ b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
@@ -16,15 +16,16 @@
  */
 package org.apache.jackrabbit.api.security;
 
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
+
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.ValueFormatException;
 import javax.jcr.security.AccessControlEntry;
 
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.osgi.annotation.versioning.ProviderType;
-
 /**
  * <code>JackrabbitAccessControlEntry</code> is a Jackrabbit specific extension
  * of the <code>AccessControlEntry</code> interface. It represents an single
@@ -83,4 +84,15 @@ public interface JackrabbitAccessControlEntry extends AccessControlEntry {
      */
     @Nullable
     Value[] getRestrictions(@NotNull String restrictionName) throws RepositoryException;
+
+    /**
+     * Returns a {@link PrivilegeCollection} representing the privileges associated with this entry.
+     *
+     * @return A {@link PrivilegeCollection} wrapping around the privileges defined for this instance of {@code JackrabbitAccessControlEntry}.
+     * @throws RepositoryException If an error occurs.
+     * @since Oak 1.42.0
+     * @see #getPrivileges() 
+     */
+    @NotNull
+    PrivilegeCollection getPrivilegeCollection() throws RepositoryException;
 }
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 135ee74..50f434c 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
@@ -26,6 +26,8 @@ import javax.jcr.security.AccessControlPolicy;
 import javax.jcr.security.Privilege;
 
 import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
@@ -220,4 +222,28 @@ public interface JackrabbitAccessControlManager extends AccessControlManager {
     default PrivilegeCollection getPrivilegeCollection(@Nullable String absPath, @NotNull Set<Principal> principals) throws RepositoryException {
         return new PrivilegeCollection.Default(getPrivileges(absPath, principals), this);
     }
+
+    /**
+     * <p>Returns the {@link PrivilegeCollection} for the specified <code>privilegeNames</code>.
+     * Since the privilege names are JCR names, they may be passed in either
+     * qualified or expanded form (see specification for details on JCR names).</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 privilegeNames the names of existing privilege.
+     * @return the <code>PrivilegeCollection</code> representing the specified <code>privilegeNames</code>.
+     * @throws AccessControlException if no privilege with any of the specified names exists.
+     * @throws RepositoryException If another error occurs.
+     * @since Oak 1.42.0
+     */
+    @NotNull
+    default PrivilegeCollection privilegeCollectionFromNames(@NotNull String... privilegeNames) throws RepositoryException {
+        List<Privilege> privileges = new ArrayList<>();
+        for (String privilegeName : privilegeNames) {
+            privileges.add(privilegeFromName(privilegeName));
+        }
+        return new PrivilegeCollection.Default(privileges.toArray(new Privilege[0]), this);
+    }
 }
\ No newline at end of file
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 132dc73..d4e3e40 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.5.0")
+@org.osgi.annotation.versioning.Version("2.6.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 4e7c077..b4db9fd 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.5.0")
+@org.osgi.annotation.versioning.Version("2.6.0")
 package org.apache.jackrabbit.api.security;
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACE.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACE.java
index 468731c..e070afd 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACE.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACE.java
@@ -20,17 +20,21 @@ import com.google.common.base.Objects;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableSet;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.plugins.value.jcr.PartialValueFactory;
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restriction;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.ValueFormatException;
 import javax.jcr.security.AccessControlException;
+import javax.jcr.security.Privilege;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.List;
@@ -41,6 +45,7 @@ import java.util.Set;
  * It asserts that the basic contract is fulfilled but does perform any additional
  * validation on the principal, the privileges or the specified restrictions.
  */
+@ProviderType
 public abstract class ACE implements JackrabbitAccessControlEntry {
 
     private final Principal principal;
@@ -60,7 +65,7 @@ public abstract class ACE implements JackrabbitAccessControlEntry {
      * @param isAllow {@code true} if the entry is granting privileges.
      * @param restrictions A optional set of restrictions.
      * @param namePathMapper The name-path mapper
-     * @throws AccessControlException If the given {@code principal} or {@code privilgeBits} are {@code null} or if {@code privilgeBits} are {@link PrivilegeBits#isEmpty() empty}.
+     * @throws AccessControlException If the given {@code principal} or {@code privilegeBits} are {@code null} or if {@code privilegeBits} are {@link PrivilegeBits#isEmpty() empty}.
      */
     public ACE(@Nullable Principal principal, @Nullable PrivilegeBits privilegeBits,
                boolean isAllow, @Nullable Set<Restriction> restrictions, 
@@ -87,6 +92,9 @@ public abstract class ACE implements JackrabbitAccessControlEntry {
     public Set<Restriction> getRestrictions() {
         return restrictions;
     }
+    
+    @NotNull
+    protected abstract PrivilegeBitsProvider getPrivilegeBitsProvider();
 
     //-------------------------------------------------< AccessControlEntry >---
     @NotNull
@@ -141,6 +149,26 @@ public abstract class ACE implements JackrabbitAccessControlEntry {
         return null;
     }
 
+    @Override
+    public @NotNull PrivilegeCollection getPrivilegeCollection() {
+        return new AbstractPrivilegeCollection(privilegeBits) {
+            @Override
+            public Privilege[] getPrivileges() {
+                return ACE.this.getPrivileges();
+            }
+
+            @Override
+            @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+                return ACE.this.getPrivilegeBitsProvider();
+            }
+
+            @Override
+            @NotNull NamePathMapper getNamePathMapper() {
+                return namePathMapper;
+            }
+        };
+    }
+
     //-------------------------------------------------------------< Object >---
 
     @Override
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 4b51d36..773e98d 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
@@ -28,12 +28,12 @@ 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.osgi.annotation.versioning.ProviderType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,6 +53,7 @@ import java.util.Set;
  * This implementation covers both editing access control content by path and
  * by {@code Principal} resulting both in the same content structure.
  */
+@ProviderType
 public abstract class AbstractAccessControlManager implements JackrabbitAccessControlManager, AccessControlConstants {
 
     private static final Logger log = LoggerFactory.getLogger(AbstractAccessControlManager.class);
@@ -127,7 +128,7 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
 
     @Override
     public @NotNull PrivilegeCollection getPrivilegeCollection(@Nullable String absPath) throws RepositoryException {
-        return getPrivilegeCollection(absPath, getPermissionProvider(), Permissions.NO_PERMISSION);
+        return getPrivilegeCollection(getPrivilegeNames(absPath, getPermissionProvider(), Permissions.NO_PERMISSION));
     }
 
     @Override
@@ -136,10 +137,15 @@ public abstract class AbstractAccessControlManager implements JackrabbitAccessCo
             return getPrivilegeCollection(absPath);
         } else {
             PermissionProvider provider = config.getPermissionProvider(root, workspaceName, principals);
-            return getPrivilegeCollection(absPath, provider, Permissions.READ_ACCESS_CONTROL);
+            return getPrivilegeCollection(getPrivilegeNames(absPath, provider, Permissions.READ_ACCESS_CONTROL));
         }
     }
 
+    @Override
+    public @NotNull PrivilegeCollection privilegeCollectionFromNames(@NotNull String... privilegeNames) throws RepositoryException {
+        return getPrivilegeCollection(PrivilegeUtil.getOakNames(privilegeNames, namePathMapper));
+    }
+
     //----------------------------------------------------------< protected >---
     @NotNull
     protected AuthorizationConfiguration getConfig() {
@@ -295,28 +301,23 @@ 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() {
+    private PrivilegeCollection getPrivilegeCollection(@NotNull Set<String> pNames) {
+        return new AbstractPrivilegeCollection(getPrivilegeBitsProvider().getBits(pNames)) {
             @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);
+            @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+                return AbstractAccessControlManager.this.getPrivilegeBitsProvider();
+            }
+
+            @Override
+            @NotNull NamePathMapper getNamePathMapper() {
+                return AbstractAccessControlManager.this.getNamePathMapper();
             }
         };
     }
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractPrivilegeCollection.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractPrivilegeCollection.java
new file mode 100644
index 0000000..a25c3dd
--- /dev/null
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractPrivilegeCollection.java
@@ -0,0 +1,68 @@
+/*
+ * 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.spi.security.authorization.accesscontrol;
+
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+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.PrivilegeUtil;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.RepositoryException;
+import java.util.Objects;
+
+abstract class AbstractPrivilegeCollection implements PrivilegeCollection {
+    
+    private final PrivilegeBits privilegeBits;
+
+    AbstractPrivilegeCollection(@NotNull PrivilegeBits privilegeBits) {
+        this.privilegeBits = privilegeBits;
+    }
+    
+    abstract @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider();
+    abstract @NotNull NamePathMapper getNamePathMapper();
+
+    @Override
+    public boolean includes(@NotNull String... privilegeNames) throws RepositoryException {
+        if (privilegeNames.length == 0) {
+            return true;
+        }
+        if (privilegeBits.isEmpty()) {
+            return false;
+        }
+        PrivilegeBits toTest = getPrivilegeBitsProvider().getBits(PrivilegeUtil.getOakNames(privilegeNames, getNamePathMapper()), true);
+        return privilegeBits.includes(toTest);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(privilegeBits);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof AbstractPrivilegeCollection) {
+            AbstractPrivilegeCollection other = (AbstractPrivilegeCollection) obj;
+            return privilegeBits.equals(other.privilegeBits);
+        }
+        return false;
+    }
+}
\ No newline at end of file
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 8bdef7e..a4f2ad2 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.8.0")
+@Version("1.9.0")
 package org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACETest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACETest.java
index 5132242..67a4c07 100644
--- a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACETest.java
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/ACETest.java
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
@@ -29,7 +30,10 @@ import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restrict
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionImpl;
 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.PrivilegeConstants;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -40,9 +44,14 @@ import javax.jcr.ValueFactory;
 import javax.jcr.ValueFormatException;
 import javax.jcr.security.AccessControlException;
 import javax.jcr.security.Privilege;
+import java.security.Principal;
 import java.util.Collections;
 import java.util.Set;
 
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_READ;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_WRITE;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_NODES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_PROPERTIES;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -52,7 +61,12 @@ import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 /**
  * Tests for {@link ACE}
@@ -73,9 +87,15 @@ public class ACETest extends AbstractAccessControlTest {
                 valueFactory.createValue("nt:file", PropertyType.NAME)
         };
     }
+    
+    private ACE mockACE(@NotNull Principal principal, @NotNull PrivilegeBits bits, boolean isAllow, @Nullable Set<Restriction> rest) {
+        ACE ace = mock(ACE.class, withSettings().useConstructor(principal, bits, isAllow, rest, getNamePathMapper()).defaultAnswer(CALLS_REAL_METHODS));
+        when(ace.getPrivilegeBitsProvider()).thenReturn(getBitsProvider());
+        return ace;
+    }
 
     private ACE createEntry(Restriction... restrictions) throws Exception {
-        return createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true, restrictions);
+        return createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true, restrictions);
     }
 
     private Restriction createRestriction(String name, String value) {
@@ -92,16 +112,16 @@ public class ACETest extends AbstractAccessControlTest {
 
     @Test
     public void testIsAllow() throws RepositoryException {
-        ACE ace = createEntry(true, PrivilegeConstants.JCR_READ);
+        ACE ace = createEntry(true, JCR_READ);
         assertTrue(ace.isAllow());
 
-        ace = createEntry(false, PrivilegeConstants.JCR_READ);
+        ace = createEntry(false, JCR_READ);
         assertFalse(ace.isAllow());
     }
 
     @Test
     public void testGetPrincipal() throws RepositoryException {
-        ACE tmpl = createEntry(true, PrivilegeConstants.JCR_READ);
+        ACE tmpl = createEntry(true, JCR_READ);
         assertNotNull(tmpl.getPrincipal());
         assertEquals(testPrincipal.getName(), tmpl.getPrincipal().getName());
         assertSame(testPrincipal, tmpl.getPrincipal());
@@ -109,7 +129,7 @@ public class ACETest extends AbstractAccessControlTest {
 
     @Test(expected = AccessControlException.class)
     public void testNullPrincipal() throws Exception {
-        createEntry(null, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
+        createEntry(null, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
     }
 
     @Test(expected = AccessControlException.class)
@@ -124,11 +144,11 @@ public class ACETest extends AbstractAccessControlTest {
 
     @Test
     public void testGetPrivilegeBits() throws RepositoryException {
-        ACE entry = createEntry(true, PrivilegeConstants.JCR_READ);
+        ACE entry = createEntry(true, JCR_READ);
 
         PrivilegeBits bits = entry.getPrivilegeBits();
         assertNotNull(bits);
-        assertEquals(PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), bits);
+        assertEquals(PrivilegeBits.BUILT_IN.get(JCR_READ), bits);
 
         entry = createEntry(true, PrivilegeConstants.REP_WRITE);
         bits = entry.getPrivilegeBits();
@@ -148,46 +168,59 @@ public class ACETest extends AbstractAccessControlTest {
 
     @Test(expected = AccessControlException.class)
     public void testNullPrivileges() throws Exception {
-        new ACE(testPrincipal, null, true, Collections.emptySet(), getNamePathMapper()) {
-            @Override
-            public Privilege[] getPrivileges() {
-                return new Privilege[0];
-            }
-        };
+        createEmptyEntry(null);
     }
 
     @Test(expected = AccessControlException.class)
     public void testEmptyPrivileges() throws Exception {
-        new ACE(testPrincipal, PrivilegeBits.EMPTY, true, null, getNamePathMapper()) {
+        createEmptyEntry(PrivilegeBits.EMPTY);
+    }
+
+    @NotNull 
+    private ACE createEmptyEntry(@Nullable PrivilegeBits bits) throws AccessControlException {
+        return new ACE(testPrincipal, bits, true, Collections.emptySet(), getNamePathMapper()) {
             @Override
-            public Privilege[] getPrivileges() {
-                return new Privilege[0];
+            protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+                return getBitsProvider();
             }
-        };
-    }
 
-    @Test
-    public void testNullRestrictions() throws Exception {
-        ACE ace = new ACE(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true, null, getNamePathMapper()) {
             @Override
             public Privilege[] getPrivileges() {
                 return new Privilege[0];
             }
         };
+    }
+    
+    @Test
+    public void testGetPrivilegeCollection() throws RepositoryException {
+        ACE entry = mockACE(testPrincipal,PrivilegeBits.BUILT_IN.get(JCR_READ), true, null);
+
+        PrivilegeCollection pc = entry.getPrivilegeCollection();
+        assertTrue(pc instanceof AbstractPrivilegeCollection);
+        
+        assertArrayEquals(entry.getPrivileges(), pc.getPrivileges());
+        verify(entry, times(2)).getPrivileges();
+        
+        assertTrue(pc.includes(JCR_READ));
+        assertTrue(pc.includes(REP_READ_NODES));
+        assertTrue(pc.includes(REP_READ_PROPERTIES));
+        assertFalse(pc.includes(JCR_READ, JCR_WRITE));
+        
+        assertEquals(pc, entry.getPrivilegeCollection());
+    }
+
+    @Test
+    public void testNullRestrictions() {
+        ACE ace = mockACE(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true, null);
         assertTrue(ace.getRestrictions().isEmpty());
     }
 
     @Test
-    public void testRestrictions() throws Exception {
+    public void testRestrictions() {
         Restriction r = new RestrictionImpl(PropertyStates.createProperty("r", "v"), false);
         Restriction r2 = new RestrictionImpl(PropertyStates.createProperty("r2", ImmutableList.of("v"), Type.STRINGS), false);
         Set<Restriction> restrictions = Sets.newHashSet(r, r2);
-        ACE ace = new ACE(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true, restrictions, getNamePathMapper()) {
-            @Override
-            public Privilege[] getPrivileges() {
-                return new Privilege[0];
-            }
-        };
+        ACE ace = mockACE(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true, restrictions);
         assertFalse(ace.getRestrictions().isEmpty());
         assertNotSame(restrictions, ace.getRestrictions());
         assertTrue(Iterables.elementsEqual(restrictions, ace.getRestrictions()));
@@ -333,52 +366,52 @@ public class ACETest extends AbstractAccessControlTest {
 
     @Test
     public void testGetRestrictionsNone() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
 
         assertTrue(ace.getRestrictions().isEmpty());
     }
 
     @Test
     public void testEqualsSameACE() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
 
         assertEquals(ace, ace);
     }
 
     @Test
     public void testEqualsACE() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
-        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
+        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
 
         assertEquals(ace, ace2);
     }
 
     @Test
     public void testEqualsOtherEntryImpl() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
 
         assertNotEquals(ace, mock(JackrabbitAccessControlEntry.class));
     }
 
     @Test
     public void testEqualsDifferentAllow() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
-        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), false);
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
+        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), false);
 
         assertNotEquals(ace, ace2);
     }
 
     @Test
     public void testEqualsDifferentPrincipal() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
-        ACE ace2 = createEntry(EveryonePrincipal.getInstance(), PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
+        ACE ace2 = createEntry(EveryonePrincipal.getInstance(), PrivilegeBits.BUILT_IN.get(JCR_READ), true);
 
         assertNotEquals(ace, ace2);
     }
 
     @Test
     public void testEqualsDifferentPrivs() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true);
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true);
         ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_ADD_CHILD_NODES), true);
 
         assertNotEquals(ace, ace2);
@@ -386,16 +419,16 @@ public class ACETest extends AbstractAccessControlTest {
 
     @Test
     public void testEqualsDifferentRestrictions() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true, createRestriction("name2", "val"));
-        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true, createRestriction("name", "val"));
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true, createRestriction("name2", "val"));
+        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true, createRestriction("name", "val"));
 
         assertNotEquals(ace, ace2);
     }
 
     @Test
     public void testEqualsDifferentRestrictionValue() throws Exception {
-        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true, createRestriction("name", "val"));
-        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(PrivilegeConstants.JCR_READ), true, createRestriction("name", "val2"));
+        ACE ace = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true, createRestriction("name", "val"));
+        ACE ace2 = createEntry(testPrincipal, PrivilegeBits.BUILT_IN.get(JCR_READ), true, createRestriction("name", "val2"));
 
         assertNotEquals(ace, ace2);
     }
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 bd0515d..012bbfa 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
@@ -514,4 +514,15 @@ public class AbstractAccessControlManagerTest extends AbstractAccessControlTest
         verify(acMgr).getPrivilegeCollection(testPath, getEveryonePrincipalSet());
         verify(acMgr, never()).getPrivilegeCollection(testPath);
     }
+    
+    @Test
+    public void testPrivilegeCollectionFromNames() throws Exception {
+        PrivilegeCollection pc = acMgr.privilegeCollectionFromNames();
+        assertTrue(pc instanceof AbstractPrivilegeCollection);
+        assertArrayEquals(new Privilege[0], pc.getPrivileges());
+
+        pc = acMgr.privilegeCollectionFromNames(JCR_READ);
+        assertTrue(pc instanceof AbstractPrivilegeCollection);
+        assertArrayEquals(new Privilege[] {acMgr.privilegeFromName(JCR_READ)}, pc.getPrivileges());
+    }
 }
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlTest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlTest.java
index d4abfaa..c5de49b 100644
--- a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlTest.java
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractAccessControlTest.java
@@ -16,12 +16,6 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol;
 
-import java.security.Principal;
-import java.util.Set;
-import javax.jcr.RepositoryException;
-import javax.jcr.security.AccessControlException;
-import javax.jcr.security.Privilege;
-
 import com.google.common.collect.ImmutableSet;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
@@ -29,22 +23,30 @@ import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restrict
 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.mockito.Mockito;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.Privilege;
+import java.security.Principal;
+import java.util.Set;
+
+import static org.mockito.Mockito.mock;
 
 public abstract class AbstractAccessControlTest {
 
-    final Root root = Mockito.mock(Root.class);
+    final Root root = mock(Root.class);
 
     Principal testPrincipal = new PrincipalImpl("testPrincipal");
 
-    PrivilegeBitsProvider getBitsProvider() {
+    protected PrivilegeBitsProvider getBitsProvider() {
         return new PrivilegeBitsProvider(root);
     }
 
-    NamePathMapper getNamePathMapper() {
+    protected NamePathMapper getNamePathMapper() {
         return NamePathMapper.DEFAULT;
     }
-
+    
     ACE createEntry(boolean isAllow, String... privilegeName)
             throws RepositoryException {
         if (privilegeName.length == 1) {
@@ -73,5 +75,10 @@ public abstract class AbstractAccessControlTest {
         public Privilege[] getPrivileges() {
             throw new UnsupportedOperationException();
         }
+
+        @Override
+        protected @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+            return getBitsProvider();
+        }
     }
 }
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractPrivilegeCollectionTest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractPrivilegeCollectionTest.java
new file mode 100644
index 0000000..050b2f7
--- /dev/null
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/accesscontrol/AbstractPrivilegeCollectionTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.spi.security.authorization.accesscontrol;
+
+import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.security.Privilege;
+
+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_MODIFY_ACCESS_CONTROL;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_MODIFY_PROPERTIES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_READ;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_READ_ACCESS_CONTROL;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_WRITE;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_NODES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_READ_PROPERTIES;
+import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.REP_WRITE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+public class AbstractPrivilegeCollectionTest extends AbstractAccessControlTest {
+    
+    private @NotNull AbstractPrivilegeCollection createPrivilegeCollection(@NotNull String... privilegeNames) {
+        return new AbstractPrivilegeCollection(getBitsProvider().getBits(privilegeNames)) {
+            @Override
+            @NotNull PrivilegeBitsProvider getPrivilegeBitsProvider() {
+                return AbstractPrivilegeCollectionTest.this.getBitsProvider();
+            }
+
+            @Override
+            @NotNull NamePathMapper getNamePathMapper() {
+                return AbstractPrivilegeCollectionTest.this.getNamePathMapper();
+            }
+
+            @Override
+            public Privilege[] getPrivileges() throws RepositoryException {
+                throw new RepositoryException("not implemented");
+            }
+        };
+    }
+    
+    @Test
+    public void testIncludes() throws Exception {
+        AbstractPrivilegeCollection apc = createPrivilegeCollection(JCR_WRITE, JCR_READ);
+        
+        assertTrue(apc.includes());
+        assertTrue(apc.includes(JCR_WRITE, JCR_READ));
+        assertTrue(apc.includes(JCR_WRITE));
+        assertTrue(apc.includes(JCR_READ));
+        assertTrue(apc.includes(REP_READ_NODES, REP_READ_NODES));
+
+        assertFalse(apc.includes(JCR_MODIFY_ACCESS_CONTROL));
+        assertFalse(apc.includes(REP_WRITE));
+    }
+    
+    @Test
+    public void testEquals() {
+        PrivilegeCollection apc = createPrivilegeCollection(JCR_READ_ACCESS_CONTROL, JCR_READ);
+
+        assertEquals(apc, createPrivilegeCollection(JCR_READ, JCR_READ_ACCESS_CONTROL));
+        assertEquals(apc, createPrivilegeCollection(REP_READ_PROPERTIES, REP_READ_NODES, JCR_READ_ACCESS_CONTROL));
+        assertEquals(apc, createPrivilegeCollection(REP_READ_PROPERTIES, REP_READ_NODES, JCR_READ, JCR_READ_ACCESS_CONTROL));
+        assertEquals(apc, apc);
+        
+        assertNotEquals(apc, createPrivilegeCollection(JCR_READ_ACCESS_CONTROL));
+        assertNotEquals(apc, createPrivilegeCollection(JCR_READ));
+        assertNotEquals(apc, createPrivilegeCollection(JCR_READ_ACCESS_CONTROL, JCR_READ, JCR_MODIFY_PROPERTIES));
+        // different impl
+        assertNotEquals(apc, new PrivilegeCollection() {
+            @Override
+            public Privilege[] getPrivileges() {
+                return new Privilege[0];
+            }
+
+            @Override
+            public boolean includes(@NotNull String... privilegeNames) {
+                return false;
+            }
+        });
+    }
+    
+    @Test
+    public void testHashCode() {
+        AbstractPrivilegeCollection apc = createPrivilegeCollection(JCR_ADD_CHILD_NODES, JCR_MODIFY_PROPERTIES);
+        
+        assertEquals(getBitsProvider().getBits(JCR_ADD_CHILD_NODES, JCR_MODIFY_PROPERTIES).hashCode(), apc.hashCode());
+        assertEquals(apc.hashCode(), createPrivilegeCollection(JCR_ADD_CHILD_NODES, JCR_MODIFY_PROPERTIES).hashCode());
+        
+        assertNotEquals(apc.hashCode(), createPrivilegeCollection(JCR_ADD_CHILD_NODES).hashCode());
+        assertNotEquals(apc.hashCode(), createPrivilegeCollection(JCR_MODIFY_PROPERTIES).hashCode());
+        assertNotEquals(apc.hashCode(), createPrivilegeCollection(JCR_READ).hashCode());
+    }
+    
+    @Test
+    public void testEmpty() throws RepositoryException {
+        AbstractPrivilegeCollection apc = createPrivilegeCollection();
+        
+        assertTrue(apc.includes());
+        assertFalse(apc.includes(REP_READ_NODES));
+
+        assertEquals(PrivilegeBits.EMPTY.hashCode(), apc.hashCode());
+        
+        assertEquals(apc, createPrivilegeCollection());
+        assertNotEquals(apc, createPrivilegeCollection(JCR_READ));
+
+    }
+
+}
\ No newline at end of file