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 dj...@apache.org on 2016/08/17 14:21:08 UTC

svn commit: r1756639 [3/5] - in /jackrabbit/oak/branches/1.2: ./ oak-commons/ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/ oak-core/src/main/java/org/apache/jackrabbit...

Modified: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/MembershipProviderTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/MembershipProviderTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/MembershipProviderTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/MembershipProviderTest.java Wed Aug 17 14:21:07 2016
@@ -16,17 +16,25 @@
  */
 package org.apache.jackrabbit.oak.security.user;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
-
+import javax.annotation.Nonnull;
 import javax.jcr.RepositoryException;
 
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Maps;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
@@ -39,56 +47,53 @@ import org.junit.Test;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 /**
  * Tests large group and user graphs.
  * <ul>
- *     <li>{@link #NUM_USERS} users</li>
- *     <li>{@link #NUM_GROUPS} groups</li>
- *     <li>1 group with all users</li>
- *     <li>1 user with all groups</li>
+ * <li>{@link #NUM_USERS} users</li>
+ * <li>{@link #NUM_GROUPS} groups</li>
+ * <li>1 group with all users</li>
+ * <li>1 user with all groups</li>
  * </ul>
  *
  * @since OAK 1.0
  */
-public class MembershipProviderTest extends AbstractSecurityTest {
+public class MembershipProviderTest extends AbstractSecurityTest implements UserConstants {
 
     private static final int NUM_USERS = 1000;
     private static final int NUM_GROUPS = 1000;
     private static final int SIZE_TH = 10;
 
     private UserManagerImpl userMgr;
+    private MembershipProvider mp;
     private Set<String> testUsers = new HashSet<String>();
     private Set<String> testGroups = new HashSet<String>();
 
     @Before
     public void before() throws Exception {
         super.before();
-        testUsers.clear();
-        testGroups.clear();
         userMgr = new UserManagerImpl(root, namePathMapper, getSecurityProvider());
-        // set the membership size threshold low for testing
-        userMgr.getMembershipProvider().setMembershipSizeThreshold(SIZE_TH);
+        mp = userMgr.getMembershipProvider();
+        // set the threshold low for testing
+        mp.setMembershipSizeThreshold(SIZE_TH);
     }
 
     @After
     public void after() throws Exception {
         try {
-            for (String authId: testGroups) {
-                Authorizable auth = userMgr.getAuthorizable(authId);
-                if (auth != null) {
-                    auth.remove();
-                }
-            }
-            for (String authId: testUsers) {
-                Authorizable auth = userMgr.getAuthorizable(authId);
+            for (String path : Iterables.concat(testUsers, testGroups)) {
+                Authorizable auth = userMgr.getAuthorizableByPath(path);
                 if (auth != null) {
                     auth.remove();
                 }
             }
             root.commit();
         } finally {
+            testUsers.clear();
+            testGroups.clear();
             super.after();
         }
     }
@@ -96,24 +101,24 @@ public class MembershipProviderTest exte
     @Test
     public void testManyMembers() throws Exception {
         Set<String> members = new HashSet<String>();
-        Group grp  = createGroup();
-        for (int i=0; i<NUM_USERS; i++) {
+        Group grp = createGroup();
+        for (int i = 0; i < NUM_USERS; i++) {
             User usr = createUser();
             grp.addMember(usr);
             members.add(usr.getID());
         }
         root.commit();
-        checkMembers(grp, members);
+        assertMembers(grp, members);
 
         // also check storage structure
         Tree tree = root.getTree(grp.getPath());
         assertEquals(
                 "rep:members property must have correct number of references",
                 SIZE_TH,
-                tree.getProperty(UserConstants.REP_MEMBERS).count()
+                tree.getProperty(REP_MEMBERS).count()
         );
 
-        Tree membersList = tree.getChild(UserConstants.REP_MEMBERS_LIST);
+        Tree membersList = tree.getChild(REP_MEMBERS_LIST);
         assertTrue(
                 "rep:memberList must exist",
                 membersList.exists()
@@ -121,7 +126,7 @@ public class MembershipProviderTest exte
 
         assertEquals(
                 "rep:memberList must have correct primary type.",
-                UserConstants.NT_REP_MEMBER_REFERENCES_LIST,
+                NT_REP_MEMBER_REFERENCES_LIST,
                 membersList.getProperty(JcrConstants.JCR_PRIMARYTYPE).getValue(Type.STRING)
         );
 
@@ -136,7 +141,7 @@ public class MembershipProviderTest exte
     public void testManyMemberships() throws Exception {
         Set<String> memberships = new HashSet<String>();
         User usr = createUser();
-        for (int i=0; i<NUM_GROUPS; i++) {
+        for (int i = 0; i < NUM_GROUPS; i++) {
             Group grp = createGroup();
             grp.addMember(usr);
             memberships.add(grp.getID());
@@ -155,17 +160,17 @@ public class MembershipProviderTest exte
     public void testNestedMembers() throws Exception {
         Set<String> members = new HashSet<String>();
         Set<String> declaredMembers = new HashSet<String>();
-        Group grp  = createGroup();
-        for (int i=0; i<10; i++) {
+        Group grp = createGroup();
+        for (int i = 0; i < 10; i++) {
             Group g1 = createGroup();
             grp.addMember(g1);
             members.add(g1.getID());
             declaredMembers.add(g1.getID());
-            for (int j=0; j<10; j++) {
+            for (int j = 0; j < 10; j++) {
                 Group g2 = createGroup();
                 g1.addMember(g2);
                 members.add(g2.getID());
-                for (int k=0; k<10; k++) {
+                for (int k = 0; k < 10; k++) {
                     User usr = createUser();
                     g2.addMember(usr);
                     members.add(usr.getID());
@@ -174,7 +179,7 @@ public class MembershipProviderTest exte
         }
         root.commit();
 
-        checkMembers(grp, members);
+        assertMembers(grp, members);
 
         Iterator<Authorizable> iter = grp.getDeclaredMembers();
         while (iter.hasNext()) {
@@ -187,14 +192,14 @@ public class MembershipProviderTest exte
     @Test
     public void testNestedMemberships() throws Exception {
         Set<String> memberships = new HashSet<String>();
-        User user  = createUser();
+        User user = createUser();
         Group grp = createGroup();
         memberships.add(grp.getID());
-        for (int i=0; i<10; i++) {
+        for (int i = 0; i < 10; i++) {
             Group g1 = createGroup();
             grp.addMember(g1);
             memberships.add(g1.getID());
-            for (int j=0; j<10; j++) {
+            for (int j = 0; j < 10; j++) {
                 Group g2 = createGroup();
                 g1.addMember(g2);
                 memberships.add(g2.getID());
@@ -215,8 +220,8 @@ public class MembershipProviderTest exte
     public void testRemoveMembers() throws Exception {
         Set<String> members = new HashSet<String>();
         String[] users = new String[NUM_USERS];
-        Group grp  = createGroup();
-        for (int i=0; i<NUM_USERS; i++) {
+        Group grp = createGroup();
+        for (int i = 0; i < NUM_USERS; i++) {
             User usr = createUser();
             grp.addMember(usr);
             members.add(usr.getID());
@@ -226,24 +231,24 @@ public class MembershipProviderTest exte
 
         // remove the first TH users, this should remove all references from rep:members in the group node and remove
         // the rep:members property
-        for (int i=0; i<SIZE_TH; i++) {
+        for (int i = 0; i < SIZE_TH; i++) {
             Authorizable auth = userMgr.getAuthorizable(users[i]);
             members.remove(users[i]);
             grp.removeMember(auth);
         }
         root.commit();
 
-        checkMembers(grp, members);
+        assertMembers(grp, members);
 
         // also check storage structure
         Tree tree = root.getTree(grp.getPath());
         assertNull(
                 "rep:members property not exist",
-                tree.getProperty(UserConstants.REP_MEMBERS)
+                tree.getProperty(REP_MEMBERS)
         );
 
         // now add TH/2 again.
-        for (int i=0; i<SIZE_TH/2; i++) {
+        for (int i = 0; i < SIZE_TH / 2; i++) {
             Authorizable auth = userMgr.getAuthorizable(users[i]);
             members.add(users[i]);
             grp.addMember(auth);
@@ -253,47 +258,45 @@ public class MembershipProviderTest exte
         assertEquals(
                 "rep:members property must have correct number of references",
                 SIZE_TH / 2,
-                tree.getProperty(UserConstants.REP_MEMBERS).count()
+                tree.getProperty(REP_MEMBERS).count()
         );
-        checkMembers(grp, members);
+        assertMembers(grp, members);
 
         // now remove the users 20-30, this should remove the 2nd overflow node
-        for (int i=2*SIZE_TH; i< (3*SIZE_TH); i++) {
+        for (int i = 2 * SIZE_TH; i < (3 * SIZE_TH); i++) {
             Authorizable auth = userMgr.getAuthorizable(users[i]);
             members.remove(users[i]);
             grp.removeMember(auth);
         }
         root.commit();
 
-        checkMembers(grp, members);
+        assertMembers(grp, members);
 
-        Tree membersList = tree.getChild(UserConstants.REP_MEMBERS_LIST);
+        Tree membersList = tree.getChild(REP_MEMBERS_LIST);
         assertFalse(
                 "the first overflow node must not exist",
                 membersList.getChild("1").exists()
         );
 
         // now add 10 users and check if the "1" node exists again
-        for (int i=2*SIZE_TH; i< (3*SIZE_TH); i++) {
+        for (int i = 2 * SIZE_TH; i < (3 * SIZE_TH); i++) {
             Authorizable auth = userMgr.getAuthorizable(users[i]);
             members.add(users[i]);
             grp.addMember(auth);
         }
         root.commit();
-        checkMembers(grp, members);
+        assertMembers(grp, members);
 
-        membersList = tree.getChild(UserConstants.REP_MEMBERS_LIST);
-        assertTrue(
-                "the first overflow node must not exist",
-                membersList.getChild("1").exists()
+        membersList = tree.getChild(REP_MEMBERS_LIST);
+        assertTrue("the first overflow node must not exist", membersList.getChild("1").exists()
         );
     }
 
     @Test
     public void testAddMembersAgain() throws Exception {
         Set<String> members = new HashSet<String>();
-        Group grp  = createGroup();
-        for (int i=0; i<NUM_USERS; i++) {
+        Group grp = createGroup();
+        for (int i = 0; i < NUM_USERS; i++) {
             User usr = createUser();
             grp.addMember(usr);
             members.add(usr.getID());
@@ -308,45 +311,271 @@ public class MembershipProviderTest exte
     @Test
     public void testAddMembersAgainOnMembershipProvider() throws Exception {
         Set<String> memberPaths = new HashSet<String>();
-        Group grp  = createGroup();
-        for (int i=0; i<NUM_USERS; i++) {
+        Group grp = createGroup();
+        for (int i = 0; i < NUM_USERS; i++) {
             User usr = createUser();
             grp.addMember(usr);
             memberPaths.add(usr.getPath());
         }
         root.commit();
 
-
-        MembershipProvider mp = userMgr.getMembershipProvider();
         Tree groupTree = root.getTree(grp.getPath());
         for (String path : memberPaths) {
             Tree memberTree = root.getTree(path);
             assertFalse(mp.addMember(groupTree, memberTree));
-            assertFalse(mp.addMember(groupTree, TreeUtil.getString(memberTree, JcrConstants.JCR_UUID)));
+            assertFalse(mp.addMember(groupTree, memberTree));
+
+            String memberId = TreeUtil.getString(memberTree, REP_AUTHORIZABLE_ID);
+            Map<String, String> m = new HashMap<String, String>(1);
+            m.put(TreeUtil.getString(memberTree, JcrConstants.JCR_UUID), memberId);
+
+            Set<String> failed = mp.addMembers(groupTree, m);
+            assertFalse(failed.isEmpty());
+            assertTrue(failed.contains(memberId));
+        }
+    }
+
+    @Test
+    public void testIsDeclaredMemberTransient() throws Exception {
+        Group g = createGroup();
+        List<Authorizable> members = createMembers(g, NUM_USERS/2);
+
+        Tree groupTree = getTree(g);
+
+        // test declared members with transient modifications
+        for (Authorizable a : members) {
+            assertTrue(mp.isDeclaredMember(groupTree, getTree(a)));
+        }
+    }
+
+    @Test
+    public void testIsDeclaredMember() throws Exception {
+        Group g = createGroup();
+        List<Authorizable> members = createMembers(g, NUM_USERS/2);
+        root.commit();
+
+        Tree groupTree = getTree(g);
+
+        // test declared members after commit
+        for (Authorizable a : members) {
+            assertTrue(mp.isDeclaredMember(groupTree, getTree(a)));
+        }
+    }
+
+    @Test
+    public void testIsDeclaredMemberFew() throws Exception {
+        Group g = createGroup();
+        Group m1 = createGroup();
+        User m2 = createUser();
+
+        g.addMembers(m1.getID(), m2.getID());
+
+        Tree groupTree = getTree(g);
+        assertFalse(groupTree.hasChild(REP_MEMBERS_LIST));
+
+        // test declared members with transient modifications
+        assertTrue(mp.isDeclaredMember(groupTree, getTree(m1)));
+        assertTrue(mp.isDeclaredMember(groupTree, getTree(m2)));
+
+        // ... and after commit
+        root.commit();
+        assertTrue(mp.isDeclaredMember(groupTree, getTree(m1)));
+        assertTrue(mp.isDeclaredMember(groupTree, getTree(m2)));
+    }
+
+    @Test
+    public void testIsMemberTransient() throws Exception {
+        Group g = createGroup();
+        Group g2 = createGroup();
+        g.addMember(g2);
+
+        List<Authorizable> members = createMembers(g2, 50);
+
+        Tree groupTree = getTree(g);
+
+        // test members with transient modifications
+        for (Authorizable a : members) {
+            Tree tree = getTree(a);
+            assertFalse(mp.isDeclaredMember(groupTree, tree));
+            assertTrue(mp.isMember(groupTree, tree));
+        }
+    }
+
+    @Test
+    public void testIsMember() throws Exception {
+        Group g = createGroup();
+        Group g2 = createGroup();
+        g.addMember(g2);
+
+        List<Authorizable> members = createMembers(g2, NUM_USERS/2);
+        root.commit();
+
+        // test members after commit
+        Tree groupTree = getTree(g);
+        for (Authorizable a : members) {
+            Tree tree = getTree(a);
+            assertFalse(mp.isDeclaredMember(groupTree, tree));
+            assertTrue(mp.isMember(groupTree, tree));
         }
     }
 
+    @Test
+    public void testIsMemberFew() throws Exception {
+        Group g = createGroup();
+        Group g2 = createGroup();
+        g.addMember(g2);
+
+        User m1 = createUser();
+        Group m2 = createGroup();
+        g2.addMember(m1);
+        g2.addMember(m2);
+
+        Tree groupTree = getTree(g);
+
+        // test members with transient modifications
+        assertFalse(mp.isDeclaredMember(groupTree, getTree(m1)));
+        assertFalse(mp.isDeclaredMember(groupTree, getTree(m2)));
+        assertTrue(mp.isMember(groupTree, getTree(m1)));
+        assertTrue(mp.isMember(groupTree, getTree(m2)));
+
+        // ... and after commit
+        root.commit();
+        assertFalse(mp.isDeclaredMember(groupTree, getTree(m1)));
+        assertFalse(mp.isDeclaredMember(groupTree, getTree(m2)));
+        assertTrue(mp.isMember(groupTree, getTree(m1)));
+        assertTrue(mp.isMember(groupTree, getTree(m2)));
+    }
+
+    @Test
+    public void testTransientInMembersList() throws Exception {
+        Group g = createGroup();
+        createMembers(g, NUM_USERS/2);
+        root.commit();
+
+        // add another transient member that will end up in the members-ref-list
+        User u = createUser();
+        g.addMember(u);
+
+        Tree groupTree = getTree(g);
+        Tree memberTree = getTree(u);
+
+        assertTrue(mp.isDeclaredMember(groupTree, memberTree));
+        assertTrue(mp.isMember(groupTree, memberTree));
+
+        assertFalse(Iterators.contains(mp.getMembership(memberTree, false), groupTree.getPath()));
+        assertFalse(Iterators.contains(mp.getMembership(memberTree, true), groupTree.getPath()));
+        root.commit();
+        assertTrue(Iterators.contains(mp.getMembership(memberTree, false), groupTree.getPath()));
+        assertTrue(Iterators.contains(mp.getMembership(memberTree, true), groupTree.getPath()));
+    }
+
+    @Test
+    public void testNoMember() throws Exception {
+        Group g = createGroup();
+        Group notMember = createGroup();
+        User notMember2 = createUser();
+
+        assertFalse(g.isDeclaredMember(notMember));
+        assertFalse(g.isDeclaredMember(notMember2));
+
+        assertFalse(g.isMember(notMember));
+        assertFalse(g.isMember(notMember2));
+
+        root.commit();
+
+        assertFalse(g.isDeclaredMember(notMember));
+        assertFalse(g.isDeclaredMember(notMember2));
+
+        assertFalse(g.isMember(notMember));
+        assertFalse(g.isMember(notMember2));
+    }
+
+    @Test
+    public void testAddMembersExceedThreshold() throws Exception {
+        Tree groupTree = root.getTree(createGroup().getPath());
+
+        // 1. add array of 21 memberIDs exceeding the threshold
+        Map<String, String> memberIds = createIdMap(0, 21);
+        mp.addMembers(groupTree, memberIds);
+
+        PropertyState repMembers = groupTree.getProperty(REP_MEMBERS);
+        assertNotNull(repMembers);
+        assertEquals(SIZE_TH, repMembers.count());
+
+        // the members-list must how have two ref-members nodes one with 10 and
+        // one with a single ref-value
+        assertMemberList(groupTree, 2, 1);
+
+        // 2. add more members without reaching threshold => still 2 ref-nodes
+        memberIds = createIdMap(21, 29);
+        mp.addMembers(groupTree, memberIds);
+        assertMemberList(groupTree, 2, 9);
+
+        // 3. fill up second ref-members node => a new one must be created
+        memberIds = createIdMap(29, 35);
+        mp.addMembers(groupTree, memberIds);
+        assertMemberList(groupTree, 3, 5);
+
+        // 4. remove members from the initial set => ref nodes as before, rep:members prop on group modified
+        memberIds.clear();
+        memberIds.put(MembershipProvider.getContentID("member1"), "member1");
+        memberIds.put(MembershipProvider.getContentID("member2"), "member2");
+        mp.removeMembers(groupTree, Maps.newHashMap(memberIds));
+
+        assertMemberList(groupTree, 3, 5);
+        assertEquals(8, groupTree.getProperty(REP_MEMBERS).count());
+
+        // 5. add members again => best-tree is the ref-member-node
+        memberIds = createIdMap(35, 39);
+        mp.addMembers(groupTree, memberIds);
+
+        assertEquals(8, groupTree.getProperty(REP_MEMBERS).count());
+        assertMemberList(groupTree, 3, 9);
+
+        // 6. adding more members will fill up rep:members again and create new ref-node
+        memberIds = createIdMap(39, 45);
+        mp.addMembers(groupTree, memberIds);
+
+        assertEquals(SIZE_TH, groupTree.getProperty(REP_MEMBERS).count());
+        assertEquals(4, groupTree.getChild(REP_MEMBERS_LIST).getChildrenCount(10));
+    }
+
     private User createUser() throws RepositoryException {
         String userId = "testUser" + testUsers.size();
         User usr = userMgr.createUser(userId, "pw");
-        testUsers.add(userId);
-        if ((testUsers.size() + testGroups.size()) % 100 == 0) {
-            System.out.println("created " + testGroups.size() + " groups, " + testUsers.size() + " users.");
-        }
+        testUsers.add(usr.getPath());
         return usr;
     }
 
     private Group createGroup() throws RepositoryException {
         String groupName = "testGroup" + testGroups.size();
         Group grp = userMgr.createGroup(groupName);
-        testGroups.add(groupName);
-        if ((testUsers.size() + testGroups.size()) % 100 == 0) {
-            System.out.println("created " + testGroups.size() + " groups, " + testUsers.size() + " users.");
-        }
+        testGroups.add(grp.getPath());
         return grp;
     }
 
-    private void checkMembers(Group grp, Set<String> ms) throws RepositoryException {
+    private List<Authorizable> createMembers(@Nonnull Group g, int cnt) throws Exception {
+        List<Authorizable> members = new ArrayList();
+        for (int i = 0; i <= cnt; i++) {
+            User u = createUser();
+            Group gr = createGroup();
+            g.addMembers(u.getID(), gr.getID());
+            members.add(u);
+            members.add(gr);
+        }
+        return members;
+    }
+
+    private static Map<String, String> createIdMap(int start, int end) {
+        Map<String, String> memberIds = Maps.newLinkedHashMap();
+        for (int i = start; i < end; i++) {
+            String memberId = "member" + i;
+            memberIds.put(MembershipProvider.getContentID(memberId), memberId);
+        }
+        return memberIds;
+    }
+
+    private static void assertMembers(Group grp, Set<String> ms) throws RepositoryException {
         Set<String> members = new HashSet<String>(ms);
         Iterator<Authorizable> iter = grp.getMembers();
         while (iter.hasNext()) {
@@ -355,4 +584,19 @@ public class MembershipProviderTest exte
         }
         assertEquals("Group must have all members", 0, members.size());
     }
+
+    private static void assertMemberList(@Nonnull Tree groupTree, int cntRefTrees, int cnt) {
+        Tree list = groupTree.getChild(REP_MEMBERS_LIST);
+        assertTrue(list.exists());
+        assertEquals(cntRefTrees, list.getChildrenCount(5));
+        for (Tree c : list.getChildren()) {
+            PropertyState repMembers = c.getProperty(REP_MEMBERS);
+            assertNotNull(repMembers);
+            assertTrue(SIZE_TH == repMembers.count() || cnt == repMembers.count());
+        }
+    }
+
+    private Tree getTree(@Nonnull Authorizable a) throws Exception {
+        return root.getTree(a.getPath());
+    }
 }
\ No newline at end of file

Copied: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RemoveMembersByIdBestEffortTest.java (from r1694049, jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RemoveMembersByIdBestEffortTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RemoveMembersByIdBestEffortTest.java?p2=jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RemoveMembersByIdBestEffortTest.java&p1=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RemoveMembersByIdBestEffortTest.java&r1=1694049&r2=1756639&rev=1756639&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RemoveMembersByIdBestEffortTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RemoveMembersByIdBestEffortTest.java Wed Aug 17 14:21:07 2016
@@ -62,4 +62,20 @@ public class RemoveMembersByIdBestEffort
         root.refresh();
         assertFalse(testGroup.isMember(memberGroup));
     }
+
+    @Test
+    public void testMemberListExistingMembers() throws Exception {
+        MembershipProvider mp = ((UserManagerImpl) getUserManager(root)).getMembershipProvider();
+        try {
+            mp.setMembershipSizeThreshold(5);
+            for (int i = 0; i < 10; i++) {
+                testGroup.addMembers("member" + i);
+            }
+
+            Set<String> failed = testGroup.removeMembers("member8");
+            assertTrue(failed.isEmpty());
+        } finally {
+            mp.setMembershipSizeThreshold(100); // back to default
+        }
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserInitializerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserInitializerTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserInitializerTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserInitializerTest.java Wed Aug 17 14:21:07 2016
@@ -130,6 +130,13 @@ public class UserInitializerTest extends
         assertArrayEquals(
                 new String[]{UserConstants.NT_REP_AUTHORIZABLE},
                 Iterables.toArray(declaringNtNames, String.class));
+
+        Tree repMembers = oakIndex.getChild("repMembers");
+        assertIndexDefinition(repMembers, UserConstants.REP_MEMBERS, false);
+        declaringNtNames = TreeUtil.getStrings(repMembers, IndexConstants.DECLARING_NODE_TYPES);
+        assertArrayEquals(
+                new String[]{UserConstants.NT_REP_MEMBER_REFERENCES},
+                Iterables.toArray(declaringNtNames, String.class));
     }
 
     /**

Modified: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java Wed Aug 17 14:21:07 2016
@@ -48,7 +48,6 @@ import org.junit.Before;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
@@ -320,9 +319,41 @@ public class UserValidatorTest extends A
 
             group1.addMember(group2);
             group2.addMember(group3);
-            
-            assertFalse(group3.addMember(group1));
-            
+
+            // manually create the cyclic membership
+            Tree group3Tree = root.getTree(group3.getPath());
+            Set<String> values = Collections.singleton(root.getTree(group1.getPath()).getProperty(JcrConstants.JCR_UUID).getValue(Type.STRING));
+            PropertyState prop = PropertyStates.createProperty(REP_MEMBERS, values, Type.WEAKREFERENCES);
+            group3Tree.setProperty(prop);
+            root.commit();
+            fail("Cyclic group membership must be detected");
+        } catch (CommitFailedException e) {
+            // success
+        } finally {
+            if (group1 != null) group1.remove();
+            if (group2 != null) group2.remove();
+            if (group3 != null) group3.remove();
+            root.commit();
+        }
+    }
+
+    @Test
+    public void testDetectCyclicMembershipWithIntermediateCommit() throws Exception {
+        Group group1 = null;
+        Group group2 = null;
+        Group group3 = null;
+
+        UserManager userMgr = getUserManager(root);
+        try {
+            group1 = userMgr.createGroup("group1");
+            group2 = userMgr.createGroup("group2");
+            group3 = userMgr.createGroup("group3");
+
+            group1.addMember(group2);
+            group2.addMember(group3);
+            root.commit();
+
+            // manually create the cyclic membership
             Tree group3Tree = root.getTree(group3.getPath());
             Set<String> values = Collections.singleton(root.getTree(group1.getPath()).getProperty(JcrConstants.JCR_UUID).getValue(Type.STRING));
             PropertyState prop = PropertyStates.createProperty(REP_MEMBERS, values, Type.WEAKREFERENCES);

Modified: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/autosave/AutoSaveEnabledManagerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/autosave/AutoSaveEnabledManagerTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/autosave/AutoSaveEnabledManagerTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/autosave/AutoSaveEnabledManagerTest.java Wed Aug 17 14:21:07 2016
@@ -248,4 +248,23 @@ public class AutoSaveEnabledManagerTest
         assertFalse(root.hasPendingChanges());
         assertFalse(u.declaredMemberOf().hasNext());
     }
+
+    @Test
+    public void testAddMembers() throws Exception {
+        User u = mgr.createUser("u", "u");
+        Group g = mgr.createGroup("g");
+
+        assertTrue(g.addMembers(u.getID()).isEmpty());
+        assertFalse(root.hasPendingChanges());
+    }
+
+    @Test
+    public void testRemoveMembers() throws Exception {
+        User u = mgr.createUser("u", "u");
+        Group g = mgr.createGroup("g");
+        g.addMember(u);
+
+        assertTrue(g.removeMembers(u.getID()).isEmpty());
+        assertFalse(root.hasPendingChanges());
+    }
 }
\ No newline at end of file

Copied: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java (from r1735564, jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java?p2=jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java&p1=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java&r1=1735564&r2=1756639&rev=1756639&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java Wed Aug 17 14:21:07 2016
@@ -16,13 +16,14 @@
  */
 package org.apache.jackrabbit.oak.spi.security.user.action;
 
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
 import org.junit.Test;
 
-import java.util.List;
+import java.util.Set;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -30,7 +31,7 @@ public class GroupActionBestEffortTest e
 
     @Test
     public void testMembersAddedNonExisting() throws Exception {
-        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+        Set<String> nonExisting = ImmutableSet.of("blinder", "passagier");
 
         testGroup.addMembers(nonExisting.toArray(new String[nonExisting.size()]));
         assertTrue(Iterables.elementsEqual(nonExisting, groupAction.memberIds));
@@ -39,11 +40,11 @@ public class GroupActionBestEffortTest e
 
     @Test
     public void testMembersRemovedNonExisting() throws Exception {
-        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+        Set<String> nonExisting = ImmutableSet.of("blinder", "passagier");
 
         testGroup.removeMembers(nonExisting.toArray(new String[nonExisting.size()]));
         assertFalse(groupAction.memberIds.iterator().hasNext());
-        assertTrue(Iterables.elementsEqual(nonExisting, groupAction.failedIds));
+        assertEquals(nonExisting, groupAction.failedIds);
     }
 
     @Override

Copied: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java (from r1735564, jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java?p2=jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java&p1=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java&r1=1735564&r2=1756639&rev=1756639&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java Wed Aug 17 14:21:07 2016
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.oak.spi.security.user.action;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
@@ -41,6 +42,7 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.jcr.RepositoryException;
 import java.util.List;
+import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -113,20 +115,20 @@ public class GroupActionTest extends Abs
         testUser02 = getUserManager(root).createUser(TEST_USER_PREFIX + "02", "");
         testGroup.addMember(testUser02);
 
-        List<String> memberIds = ImmutableList.of(testUser01.getID());
-        List<String> failedIds = ImmutableList.of(testUser02.getID(), testGroup.getID());
+        Set<String> memberIds = ImmutableSet.of(testUser01.getID());
+        Set<String> failedIds = ImmutableSet.of(testUser02.getID(), testGroup.getID());
         Iterable<String> ids = Iterables.concat(memberIds, failedIds);
 
         testGroup.addMembers(Iterables.toArray(ids, String.class));
         assertTrue(groupAction.onMembersAddedCalled);
         assertEquals(testGroup, groupAction.group);
-        assertTrue(Iterables.elementsEqual(memberIds, groupAction.memberIds));
-        assertTrue(Iterables.elementsEqual(failedIds, groupAction.failedIds));
+        assertEquals(memberIds, groupAction.memberIds);
+        assertEquals(failedIds, groupAction.failedIds);
     }
 
     @Test
     public void testMembersAddedNonExisting() throws Exception {
-        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+        Set<String> nonExisting = ImmutableSet.of("blinder", "passagier");
 
         testGroup.addMembers(nonExisting.toArray(new String[nonExisting.size()]));
         assertFalse(groupAction.memberIds.iterator().hasNext());
@@ -139,20 +141,20 @@ public class GroupActionTest extends Abs
         testUser02 = getUserManager(root).createUser(TEST_USER_PREFIX + "02", "");
         testGroup.addMember(testUser01);
 
-        List<String> memberIds = ImmutableList.of(testUser01.getID());
-        List<String> failedIds = ImmutableList.of(testUser02.getID(), testGroup.getID());
+        Set<String> memberIds = ImmutableSet.of(testUser01.getID());
+        Set<String> failedIds = ImmutableSet.of(testUser02.getID(), testGroup.getID());
         Iterable<String> ids = Iterables.concat(memberIds, failedIds);
 
         testGroup.removeMembers(Iterables.toArray(ids, String.class));
         assertTrue(groupAction.onMembersRemovedCalled);
         assertEquals(testGroup, groupAction.group);
-        assertTrue(Iterables.elementsEqual(memberIds, groupAction.memberIds));
-        assertTrue(Iterables.elementsEqual(failedIds, groupAction.failedIds));
+        assertEquals(memberIds, groupAction.memberIds);
+        assertEquals(failedIds, groupAction.failedIds);
     }
 
     @Test
     public void testMembersRemovedNonExisting() throws Exception {
-        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+        Set<String> nonExisting = ImmutableSet.of("blinder", "passagier");
 
         testGroup.removeMembers(nonExisting.toArray(new String[nonExisting.size()]));
         assertFalse(groupAction.memberIds.iterator().hasNext());
@@ -175,8 +177,8 @@ public class GroupActionTest extends Abs
         boolean onMembersRemovedCalled = false;
 
         Group group;
-        Iterable<String> memberIds;
-        Iterable<String> failedIds;
+        Set<String> memberIds;
+        Set<String> failedIds;
         Authorizable member;
 
         @Override
@@ -189,8 +191,8 @@ public class GroupActionTest extends Abs
         @Override
         public void onMembersAdded(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
             this.group = group;
-            this.memberIds = memberIds;
-            this.failedIds = failedIds;
+            this.memberIds = ImmutableSet.copyOf(memberIds);
+            this.failedIds = ImmutableSet.copyOf(failedIds);
             onMembersAddedCalled = true;
         }
 
@@ -204,8 +206,8 @@ public class GroupActionTest extends Abs
         @Override
         public void onMembersRemoved(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
             this.group = group;
-            this.memberIds = memberIds;
-            this.failedIds = failedIds;
+            this.memberIds = ImmutableSet.copyOf(memberIds);
+            this.failedIds = ImmutableSet.copyOf(failedIds);
             onMembersRemovedCalled = true;
         }
     }

Modified: jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordValidationActionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordValidationActionTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordValidationActionTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordValidationActionTest.java Wed Aug 17 14:21:07 2016
@@ -33,6 +33,7 @@ import org.apache.jackrabbit.oak.securit
 import org.apache.jackrabbit.oak.security.user.UserConfigurationImpl;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
 import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
@@ -200,6 +201,12 @@ public class PasswordValidationActionTes
                         return ConfigurationParameters.of(super.getParameters(),
                                 ConfigurationParameters.of(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, actionProvider));
                     }
+
+                    @Nullable
+                    @Override
+                    public PrincipalProvider getUserPrincipalProvider(@Nonnull Root root, @Nonnull NamePathMapper namePathMapper) {
+                        return null;
+                    }
                 };
             } else {
                 return super.getConfiguration(configClass);

Modified: jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user.md?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user.md (original)
+++ jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user.md Wed Aug 17 14:21:07 2016
@@ -18,14 +18,16 @@
 User Management
 --------------------------------------------------------------------------------
 
-### JCR User Management
+<a href="jcr_api"/>
+### JCR API
 
 JCR itself doesn't come with a dedicated user management API. The only method
 related and ultimately used for user management tasks is `Session.getUserID()`.
 Therefore an API for user and group management has been defined as part of the
 extensions present with Jackrabbit API.
 
-### Jackrabbit User Management API
+<a name="jackrabbit_api"/>
+### Jackrabbit API
 
 The Jackrabbit API provides the user management related extensions that are
 missing in JCR. The relevant interfaces are defined in the
@@ -39,214 +41,26 @@ missing in JCR. The relevant interfaces
 - `QueryBuilder`
     - `Query`
 
-### Oak User Management Implementation
-
-The default user management implementation stores user/group information in the
-content repository. In contrast to Jackrabbit 2.x, which by default used a single,
-dedicated workspace for user/group data, this data will as of Oak 1.0 be stored
-separately for each JCR workspace.
-
-Consequently the `UserManager` associated with the editing sessions, performs
-all actions with this editing session. This corresponds to the behavior as defined
-the alternative implementation present with Jackrabbit 2.x ((see Jackrabbit 2.x `UserPerWorkspaceUserManager`).
-
-#### General
-* The Oak implementation is build on the Oak API. This allows for double usage as
-  extension to the JCR API as well as within the Oak layer (aka SPI).
-* The `UserManager` is always associated with the same JCR workspace as the editing
-  `Session` from which the class has been obtained.
-* Changes made to the user management API are always transient and require `Session#save()` to be persisted.
-* In case of any failure during user management related write operations the API
-  consumer is in charge of specifically revert pending or invalid transient modifications
-  or calling `Session#refresh(false)`.
-
-#### Differences wrt Jackrabbit 2.x
-
-A summary of all changes with respect to the former implementation present with
-Jackrabbit 2.x is present in the corresponding [section](user/differences.html).
-
-#### Built-in Users and Special Groups
-
-The setup of builtin user and group accounts is triggered by the configured `WorkspaceInitializer`
-associated with the user management configuration (see Configuration section below).
-
-The default user management implementation in OAK comes with an initializer that
-creates the following builtin user accounts:
-
-##### Administrator
-The admin user is always being created. The ID of this user is retrieved from the
-user configuration parameter `PARAM_ADMIN_ID`, which defaults to `admin`.
-
-As of OAK 1.0 however the administrator user might be created without initial
-password forcing the application to set the password upon start (see `PARAM_OMIT_ADMIN_PW`
-configuration parameter).
-
-##### Anonymous User
-In contrast to Jackrabbit 2.x the anonymous (or guest) user is optional. Creation
-will be skipped if the value of the `PARAM_ANONYMOUS_ID` configuration parameter
-is `null` or empty.
-
-Note, that the anonymous user will always be created without specifying a password
-in order to prevent regular login with `SimpleCredentials`.
-The proper way to obtain a guest session is:
-
-    Repository#login(new GuestCredentials(), wspName);
-
-See section [Authentication](authentication.html) for further information about
-guest login.
-
-##### Everyone Group
-
-The default user management implementation in Oak contains special handling for
-the optional group that represents _everyone_, which is marked by the reserved
-name [everyone] and corresponds to the `EveryonePrincipal`.
-
-This special group always contains all Authorizable as member and cannot be edited
-with user management API. As of OAK this fact is consistently reflected in all
-group membership related methods. See also [Principal Management](principal.html).
-
-#### Reading Authorizables
-
-##### Handling of the Authorizable ID
-* As of Oak the node type definition of `rep:Authorizable` defines a new property `rep:authorizableId` which is intended to store the ID of a user or group.
-* The default implementation comes with a dedicated property index for `rep:authorizableId` which asserts the uniqueness of that ID.
-* `Authorizable#getID` returns the string value contained in `rep:authorizableID` and for backwards compatibility falls back on the node name in case the ID property is missing.
-* The name of the authorizable node is generated based on a configurable implementation of the `AuthorizableNodeName` interface (see configuration section below). By default it uses the ID as name hint and includes a conversion to a valid JCR node name.
-
-##### equals() and hashCode()
-The implementation of `Object#equals()` and `Object#hashCode()` for user and
-groups slightly differs from Jackrabbit 2.x. It no longer relies on the _sameness_
-of the underlaying JCR node but only compares IDs and the user manager instance.
-
-#### Creating Authorizables
-* The `rep:password` property is no longer defined to be mandatory. Therefore a new user might be created without specifying a password. Note however, that `User#changePassword` does not allow to remove the password property.
-* `UserManager#createGroup(Principal)` will no longer generate a groupID in case the principal name collides with an existing user or group ID. This has been considered redundant as the Jackrabbit API in the mean time added `UserManager#createGroup(String groupID)`.
-* Since OAK is designed to scale with flat hierarchies the former configuration options `autoExpandTree` and `autoExpandSize` are no longer supported.
-
-#### Query
-
-See section [Searching Users and Groups](user/query.html) for details.
-
-#### Group Membership
-
-See section [Group Membership](user/membership.html) for details.
-
-#### Autosave Behavior
-Due to the nature of the UserManager (see above) we decided to drop the auto-save
-behavior in the default implementation present with OAK. Consequently,
-
-* `UserManager#autoSave(boolean)` throws `UnsupportedRepositoryOperationException`
-* `UserManager#isAutoSave()` always returns `false`
-
-See also `PARAM_SUPPORT_AUTOSAVE` below; while this should not be needed if
-application code has been written against the Jackrabbit API (and thus testing if
-auto-save mode is enabled or not) this configuration option can be used as last resort.
-
-
-#### User/Group Representation in the Repository
-
-The following block lists the built-in node types related to user management tasks:
-
-    [rep:Authorizable] > mix:referenceable, nt:hierarchyNode
-      abstract
-      + * (nt:base) = nt:unstructured VERSION
-      - rep:principalName  (STRING) protected mandatory
-      - rep:authorizableId (STRING) protected /* @since oak 1.0 */
-      - * (UNDEFINED)
-      - * (UNDEFINED) multiple
-
-    [rep:Group] > rep:Authorizable, rep:MemberReferences
-      + rep:members (rep:Members) = rep:Members multiple protected VERSION /* @deprecated */
-      + rep:membersList (rep:MemberReferencesList) = rep:MemberReferencesList protected COPY
-
-    /** @since oak 1.0 */
-    [rep:MemberReferences]
-      - rep:members (WEAKREFERENCE) protected multiple < 'rep:Authorizable'
-
-    /** @since oak 1.0 */
-    [rep:MemberReferencesList]
-      + * (rep:MemberReferences) = rep:MemberReferences protected COPY
-
-    /** @deprecated since oak 1.0 */
-    [rep:Members]
-      orderable
-      + * (rep:Members) = rep:Members protected multiple
-      - * (WEAKREFERENCE) protected < 'rep:Authorizable'
-
-<a name="validation"/>
-##### Validation
-
-The consistency of this content structure is asserted by a dedicated `UserValidator`.
-The corresponding errors are all of type `Constraint` with the following codes:
-
-| Code              | Message                                                  |
-|-------------------|----------------------------------------------------------|
-| 0020              | Admin user cannot be disabled                            |
-| 0021              | Invalid jcr:uuid for authorizable (creation)             |
-| 0022              | Changing Id, principal name after creation               |
-| 0023              | Invalid jcr:uuid for authorizable (mod)                  |
-| 0024              | Password may not be plain text                           |
-| 0025              | Attempt to remove id, principalname or pw                |
-| 0026              | Mandatory property rep:principalName missing             |
-| 0027              | The admin user cannot be removed                         |
-| 0028              | Attempt to create outside of configured scope            |
-| 0029              | Intermediate folders not rep:AuthorizableFolder          |
-| 0030              | Missing uuid for group (check for cyclic membership)     |
-| 0031              | Cyclic group membership                                  |
-| 0032              | Attempt to set password with system user                 |
-| 0033              | Attempt to add rep:pwd node to a system user             |
-
-
-#### XML Import
-As of Oak 1.0 user and group nodes can be imported both with Session and Workspace
-import. Other differences compared to Jackrabbit 2.x:
-
-* Importing an authorizable to another tree than the configured user/group node will only failed upon save (-> see `UserValidator` during the `Root#commit`). With Jackrabbit 2.x core it used to fail immediately.
-* The `BestEffort` behavior is now also implemented for the import of impersonators (was missing in Jackrabbit /2.x).
-
+<a name="api_extensions"/>
 ### API Extensions
+
 The Oak project introduces the following user management related public
 interfaces and classes:
 
-#### Authorizable Actions
-
-The former internal Jackrabbit interface `AuthorizableAction` has been slightly
-adjusted to match Oak requirements and is now part of the public Oak SPI interfaces.
-In contrast to Jackrabbit-core the AuthorizableAction(s) now operate directly on
-the Oak API, which eases the handling of implementation specific tasks such as
-writing protected items.
-
-See section [Authorizable Actions](user/authorizableaction.html) for further
-details and examples.
-
-#### Node Name Generation
-
-The default user management implementation with Oak 1.0 allows to specify how
-the name of a new authorizable node is being generated.
-
-See section [Authorizable Node Name](user/authorizablenodename.html) for further
-details and examples.
-
-#### Password Expiry and Force Initial Password Change
+- `AuthorizableType`: ease handling with the different authorizable types.
+- `AuthorizableAction` and `AuthorizableActionProvider`: see [Authorizable Actions](user/authorizableaction.html) for details.
+- `AuthorizableNodeName`: see section  [Authorizable Node Name Generation](user/authorizablenodename.html).
+- `GroupAction` (via `AuthorizableActionProvider`): see [Group Actions](user/groupaction.html) for details.
+- `UserAuthenticationFactory`: see sections [pluggability](user/default.html#pluggability) 
+and [user authentication](authentication/default.html#user_authentication) for additional details.
 
-Since Oak 1.1.0 the default user management and authentication implementation
-provides password expiry and initial password change.
-
-By default these features are disabled. The corresponding configuration options
-are
-
-- `PARAM_PASSWORD_MAX_AGE`: number of days until the password expires.
-- `PARAM_PASSWORD_INITIAL_CHANGE`: boolean flag to enable this feature.
-
-See section [Password Expiry and Force Initial Password Change](user/expiry.html)
-for details.
-
-#### Utilities
+<a href="utilities"/>
+### Utilities
 
 `org.apache.jackrabbit.oak.spi.security.user.*`
 
-- `AuthorizableType` : Ease handling with the different authorizable types.
 - `UserConstants` : Constants (NOTE: OAK names/paths)
+- `UserIdCredentials` : Simple credentials implementation that might be used for `User.getCredentials' without exposing pw information. 
 
 `org.apache.jackrabbit.oak.spi.security.user.util.*`
 
@@ -256,74 +70,53 @@ for details.
   function for password generation.
 - `UserUtil` : Utilities related to general user management tasks.
 
+<a href="default_implementation"/>
+### Oak User Management Implementation
+
+The behavior of the default user management implementation is described in section 
+[User Management: The Default Implementation](user/default.html).
 
+<a name="configuration"/>
 ### Configuration
 
-The following user management specific methods are present with the [UserConfiguration]
-as of OAK 1.0:
+The Oak user management comes with a dedicated entry point called [UserConfiguration]. 
+This class is responsible for passing configuration options to the implementation
+and provides the following two methods:
 
-* getUserManager: Obtain a new user manager instance
+- `getUserManager(Root, NamePathMapper)`: get a new `UserManager` instance
+- `getUserPrincipalProvider(Root, NamePathMapper)`: optional method that allows for optimized principal look-up from user/group accounts (since Oak 1.3.4).
 
-#### Configuration Parameters supported by the default implementation
+#### Configuration Parameters
 
-| Parameter                           | Type    | Default                                      |
-|-------------------------------------|---------|----------------------------------------------|
-| `PARAM_ADMIN_ID`                    | String  | "admin"                                      |
-| `PARAM_OMIT_ADMIN_PW`               | boolean | false                                        |
-| `PARAM_ANONYMOUS_ID`                | String  | "anonymous" (nullable)                       |
-| `PARAM_USER_PATH`                   | String  | "/rep:security/rep:authorizables/rep:users"  |
-| `PARAM_GROUP_PATH`                  | String  | "/rep:security/rep:authorizables/rep:groups" |
-| `PARAM_DEFAULT_DEPTH`               | int     | 2                                            |
-| `PARAM_PASSWORD_HASH_ALGORITHM`     | String  | "SHA-256"                                    |
-| `PARAM_PASSWORD_HASH_ITERATIONS`    | int     | 1000                                         |
-| `PARAM_PASSWORD_SALT_SIZE`          | int     | 8                                            |
-| `PARAM_AUTHORIZABLE_NODE_NAME`      | AuthorizableNodeName | AuthorizableNodeName#DEFAULT    |
-| `PARAM_AUTHORIZABLE_ACTION_PROVIDER`| AuthorizableActionProvider | DefaultAuthorizableActionProvider |
-| `PARAM_SUPPORT_AUTOSAVE`            | boolean | false                                        |
-| `PARAM_IMPORT_BEHAVIOR`             | String ("abort", "ignore", "besteffort") | "ignore"    |
-| `PARAM_PASSWORD_MAX_AGE`            | int     | 0                                            |
-| `PARAM_PASSWORD_INITIAL_CHANGE`     | boolean | false                                        |
-| | | |
-
-The following configuration parameters present with the default implementation in Jackrabbit 2.x are no longer supported and will be ignored:
-
-* 'compatibleJR16'
-* 'autoExpandTree'
-* 'autoExpandSize'
-* 'groupMembershipSplitSize'
+The supported configuration options of the default implementation are described in the corresponding [section](user/default.html#configuration).
 
+<a name="pluggability"/>
 ### Pluggability
 
-The default security setup as present with Oak 1.0 is able to provide custom
-implementation on various levels:
+The default security setup as present with Oak 1.0 is able to have the default
+user management implementation replaced as follows:
 
-1. The complete user management implementation can be changed by plugging a different
-   `UserConfiguration` implementations. In OSGi-base setup this is achieved by making
-   the configuration a service. In a non-OSGi-base setup the custom configuration
-   must be exposed by the `SecurityProvider` implementation.
-2. Within the default user management implementation the following parts can be
-   change/extended at runtime by providing corresponding OSGi services or passing
-   appropriate configuration parameters exposing the custom implementations:
-       - `AuthorizableActionProvider`: Defines the authorizable actions, see [Authorizable Actions](user/authorizableaction.html).
-       - `AuthorizableNodeName`: Defines the generation of the authorizable node names
-          in case the user management implementation stores user information in the repository.
-          See [Authorizable Node Name Generation](user/authorizablenodename.html).
+The complete user management implementation can be changed by plugging a different
+`UserConfiguration` implementations. In OSGi-base setup this is achieved by making
+the configuration a service which must take precedence over the default. 
+In a non-OSGi-base setup the custom configuration must be exposed by the 
+`SecurityProvider` implementation.
+
+Alternatively the default user management implementation can be extended and
+adjusted using various means. See the corresponding [section](user/default.html#pluggability)
+for further details.
 
+<a name="further_reading"/>
 ### Further Reading
 
 - [Differences wrt Jackrabbit 2.x](user/differences.html)
-- [Group Membership](user/membership.html)
-- [Authorizable Actions](user/authorizableaction.html)
-- [Authorizable Node Name](user/authorizablenodename.html)
+- [User Management : The Default Implementation](user/default.html)
+    - [Group Membership](user/membership.html)
+    - [Authorizable Actions](user/authorizableaction.html)
+    - [Authorizable Node Name](user/authorizablenodename.html)
+    - [Password Expiry and Force Initial Password Change](user/expiry.html)
+    - [Password History](user/history.html)
 - [Searching Users and Groups](user/query.html)
-- [Password Expiry and Force Initial Password Change](user/expiry.html)
 
 <!-- hidden references -->
-[everyone]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/principal/EveryonePrincipal.html#NAME
 [UserConfiguration]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/user/UserConfiguration.html
-[OAK-118]: https://issues.apache.org/jira/browse/OAK-118
-[OAK-482]: https://issues.apache.org/jira/browse/OAK-482
-[OAK-793]: https://issues.apache.org/jira/browse/OAK-793
-[OAK-949]: https://issues.apache.org/jira/browse/OAK-949
-[OAK-1183]: https://issues.apache.org/jira/browse/OAK-1183
-

Modified: jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user/membership.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user/membership.md?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user/membership.md (original)
+++ jackrabbit/oak/branches/1.2/oak-doc/src/site/markdown/security/user/membership.md Wed Aug 17 14:21:07 2016
@@ -30,6 +30,8 @@ member relationship of users and groups:
     - `isMember(Authorizable boolean`
     - `addMember(Authorizable) boolean`
     - `removeMember(Authorizable) boolen`
+    - `addMembers(String...) Set<String>`
+    - `removeMembers(String...) Set<String>`
 
 - [org.apache.jackrabbit.api.security.user.Authorizable]
     - `declaredMemberOf() Iterator<Group>`
@@ -139,6 +141,29 @@ internally processed using the normal us
 node structure after the import might not be the same as the one represented in
 the input.
 
+#### Add and Remove Group Members by Id
+
+Since Oak 1.3.4 the default user management implementation also allows to modify
+group membership by specifying the member id(s) (see [JCR-3880] and [OAK-3170]).
+The following details are worth mentioning:
+
+- a `null` or empty String id will immediately fail the operation with `ConstraintViolationException`; changes already made will not be reverted,
+- an attemt to make the same group member of itself will list that id in the return value but will not fail the operation,
+- duplicate ids in the parameter list will be silently ignored,
+- cyclic membership validation is postponed to the validator called upon `Root.commit`
+  and will only fail at that point; the cyclic membership then needs to be manually
+  resolved by the application,
+- whether or not a non-existing (or not accessible) authorizable can be added or
+  removed depends on the configured `ImportBehavior`:
+    - ABORT: each id is resolved to the corresponding authorizable; if it doesn't
+      exist `ConstraintViolationException` is thrown immediately; changes already
+      made will not be reverted.
+    - BESTEFFORT: the specified ids are not resolved to the corresponding
+      authorizables and are silently added|removed to|from the set of members;
+      ids that were not successfully added|removed are listed in the return value.
+    - IGNORE: each id is resolved to the corresponding authorizable; if it doesn't
+      exist it will be returned as _failed_ in the return value.
+
 ### Configuration
 
 Note that as of Oak 1.0 the implementation is responsible for defining the
@@ -149,4 +174,6 @@ with Jackrabbit 2.x is not supported any
 
 <!-- hidden references -->
 [org.apache.jackrabbit.api.security.user.Group]: http://svn.apache.org/repos/asf/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Group.java
-[org.apache.jackrabbit.api.security.user.Authorizable]: http://svn.apache.org/repos/asf/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Authorizable.java
\ No newline at end of file
+[org.apache.jackrabbit.api.security.user.Authorizable]: http://svn.apache.org/repos/asf/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Authorizable.java
+[JCR-3880]: https://issues.apache.org/jira/browse/JCR-3880
+[OAK-3170]: https://issues.apache.org/jira/browse/OAK-3170

Modified: jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/GroupDelegator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/GroupDelegator.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/GroupDelegator.java (original)
+++ jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/GroupDelegator.java Wed Aug 17 14:21:07 2016
@@ -20,6 +20,7 @@
 package org.apache.jackrabbit.oak.jcr.delegate;
 
 import java.util.Iterator;
+import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.jcr.RepositoryException;
@@ -134,6 +135,17 @@ final class GroupDelegator extends Autho
     }
 
     @Override
+    public Set<String> addMembers(@Nonnull final String... memberIds) throws RepositoryException {
+        return sessionDelegate.perform(new SessionOperation<Set<String>>("addMembers") {
+            @Nonnull
+            @Override
+            public Set<String> perform() throws RepositoryException {
+                return getDelegate().addMembers(memberIds);
+            }
+        });
+    }
+
+    @Override
     public boolean removeMember(final Authorizable authorizable) throws RepositoryException {
         return sessionDelegate.perform(new SessionOperation<Boolean>("removeMember") {
             @Nonnull
@@ -143,4 +155,15 @@ final class GroupDelegator extends Autho
             }
         });
     }
+
+    @Override
+    public Set<String> removeMembers(@Nonnull final String... memberIds) throws RepositoryException {
+        return sessionDelegate.perform(new SessionOperation<Set<String>>("removeMembers") {
+            @Nonnull
+            @Override
+            public Set<String> perform() throws RepositoryException {
+                return getDelegate().removeMembers(memberIds);
+            }
+        });
+    }
 }

Modified: jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java (original)
+++ jackrabbit/oak/branches/1.2/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java Wed Aug 17 14:21:07 2016
@@ -69,6 +69,7 @@ import org.apache.jackrabbit.oak.jcr.ses
 import org.apache.jackrabbit.oak.jcr.xml.ImportHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
+import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.ContentHandler;
@@ -745,6 +746,11 @@ public class SessionImpl implements Jack
     //--------------------------------------------------< JackrabbitSession >---
 
     @Override
+    public boolean hasPermission(String absPath, String... actions) throws RepositoryException {
+        return hasPermission(absPath, Text.implode(actions, ","));
+    }
+
+    @Override
     @Nonnull
     public PrincipalManager getPrincipalManager() throws RepositoryException {
         return sessionContext.getPrincipalManager();

Modified: jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/AbstractEvaluationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/AbstractEvaluationTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/AbstractEvaluationTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/AbstractEvaluationTest.java Wed Aug 17 14:21:07 2016
@@ -45,6 +45,7 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
 import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.test.api.security.AbstractAccessControlTest;
 import org.junit.After;

Modified: jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/AbstractImportTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/AbstractImportTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/AbstractImportTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/AbstractImportTest.java Wed Aug 17 14:21:07 2016
@@ -18,7 +18,6 @@ package org.apache.jackrabbit.oak.jcr.se
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
-import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -48,7 +47,6 @@ import org.apache.jackrabbit.oak.spi.xml
 import org.apache.jackrabbit.test.NotExecutableException;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Test;
 
 import static org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest.dispose;
 import static org.junit.Assert.assertFalse;
@@ -155,8 +153,16 @@ public abstract class AbstractImportTest
 
     protected abstract String getTargetPath();
 
+    protected Session getImportSession() {
+        return adminSession;
+    }
+
+    protected UserManager getUserManager() throws RepositoryException {
+        return ((JackrabbitSession) getImportSession()).getUserManager();
+    }
+
     protected Node getTargetNode() throws RepositoryException {
-        return adminSession.getNode(getTargetPath());
+        return getImportSession().getNode(getTargetPath());
     }
 
     protected String getExistingUUID() throws RepositoryException {
@@ -171,6 +177,10 @@ public abstract class AbstractImportTest
     }
 
     protected void doImport(String parentPath, String xml, int importUUIDBehavior) throws Exception {
+        doImport(getImportSession(), parentPath, xml, importUUIDBehavior);
+    }
+
+    protected void doImport(Session importSession, String parentPath, String xml, int importUUIDBehavior) throws Exception {
         InputStream in;
         if (xml.charAt(0) == '<') {
             in = new ByteArrayInputStream(xml.getBytes());
@@ -182,7 +192,7 @@ public abstract class AbstractImportTest
             in = getClass().getResourceAsStream(xml);
         }
         try {
-            adminSession.importXML(parentPath, in, importUUIDBehavior);
+            importSession.importXML(parentPath, in, importUUIDBehavior);
         } finally {
             in.close();
         }
@@ -196,24 +206,4 @@ public abstract class AbstractImportTest
             assertFalse(potentialID.equals(session.getNode(member.getPath()).getIdentifier()));
         }
     }
-
-    private String getTestXml() {
-        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
-        for (StackTraceElement element : stackTraceElements) {
-            try {
-                Class<?> clazz = Class.forName(element.getClassName());
-                for (Method method : clazz.getMethods()){
-                    if(method.getName().equals(element.getMethodName())){
-                        if (method.getAnnotation(Test.class) != null) {
-                            return clazz.getSimpleName() + "-" + method.getName() + ".xml";
-                        }
-                    }
-
-                }
-            } catch (Exception e) {
-                //  oops do something here
-            }
-        }
-        throw new IllegalArgumentException("no import xml given.");
-    }
 }

Modified: jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportAbortTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportAbortTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportAbortTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportAbortTest.java Wed Aug 17 14:21:07 2016
@@ -64,7 +64,7 @@ public class GroupImportAbortTest extend
             } catch (RepositoryException e) {
                 // success as well
             } finally {
-                adminSession.refresh(false);
+                getImportSession().refresh(false);
             }
         }
     }

Modified: jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportBestEffortTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportBestEffortTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportBestEffortTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportBestEffortTest.java Wed Aug 17 14:21:07 2016
@@ -21,11 +21,14 @@ import java.util.List;
 import java.util.UUID;
 import javax.jcr.Node;
 import javax.jcr.PropertyType;
+import javax.jcr.Session;
 import javax.jcr.Value;
+import javax.jcr.nodetype.ConstraintViolationException;
 
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
 import org.apache.jackrabbit.test.NotExecutableException;
@@ -57,6 +60,7 @@ public class GroupImportBestEffortTest e
         invalid.add(UUID.randomUUID().toString()); // random uuid
         invalid.add(getExistingUUID()); // uuid of non-authorizable node
 
+        Session s = getImportSession();
         for (String id : invalid) {
             String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                     "<sv:node sv:name=\"gFolder\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
@@ -70,11 +74,11 @@ public class GroupImportBestEffortTest e
             try {
                 // BESTEFFORT behavior -> must import non-existing members.
                 doImport(getTargetPath(), xml);
-                Authorizable a = userMgr.getAuthorizable("g1");
+                Authorizable a = getUserManager().getAuthorizable("g1");
                 if (a.isGroup()) {
                     // the rep:members property must contain the invalid value
                     boolean found = false;
-                    Node grNode = adminSession.getNode(a.getPath());
+                    Node grNode = s.getNode(a.getPath());
                     for (Value memberValue : grNode.getProperty(UserConstants.REP_MEMBERS).getValues()) {
                         assertEquals(PropertyType.WEAKREFERENCE, memberValue.getType());
                         if (id.equals(memberValue.getString())) {
@@ -85,12 +89,12 @@ public class GroupImportBestEffortTest e
                     assertTrue("ImportBehavior.BESTEFFORT must import non-existing members.",found);
 
                     // declared members must not list the invalid entry.
-                    assertNotDeclaredMember((Group) a, id, adminSession);
+                    assertNotDeclaredMember((Group) a, id, s);
                 } else {
                     fail("'g1' was not imported as Group.");
                 }
             } finally {
-                adminSession.refresh(false);
+                s.refresh(false);
             }
         }
     }
@@ -100,7 +104,7 @@ public class GroupImportBestEffortTest e
 
         String g1Id = "0120a4f9-196a-3f9e-b9f5-23f31f914da7";
         String nonExistingId = "b2f5ff47-4366-31b6-a533-d8dc3614845d"; // groupId of 'g' group.
-        if (userMgr.getAuthorizable("g") != null) {
+        if (getUserManager().getAuthorizable("g") != null) {
             throw new NotExecutableException();
         }
         String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
@@ -115,11 +119,11 @@ public class GroupImportBestEffortTest e
 
         // BESTEFFORT behavior -> must import non-existing members.
         doImport(getTargetPath(), xml);
-        Authorizable g1 = userMgr.getAuthorizable("g1");
+        Authorizable g1 = getUserManager().getAuthorizable("g1");
         if (g1.isGroup()) {
             // the rep:members property must contain the invalid value
             boolean found = false;
-            Node grNode = adminSession.getNode(g1.getPath());
+            Node grNode = getImportSession().getNode(g1.getPath());
             for (Value memberValue : grNode.getProperty(UserConstants.REP_MEMBERS).getValues()) {
                 assertEquals(PropertyType.WEAKREFERENCE, memberValue.getType());
                 if (nonExistingId.equals(memberValue.getString())) {
@@ -138,7 +142,7 @@ public class GroupImportBestEffortTest e
 
         String g1Id = "0120a4f9-196a-3f9e-b9f5-23f31f914da7";
         String nonExistingId = "b2f5ff47-4366-31b6-a533-d8dc3614845d"; // groupId of 'g' group.
-        if (userMgr.getAuthorizable("g") != null) {
+        if (getUserManager().getAuthorizable("g") != null) {
             throw new NotExecutableException();
         }
 
@@ -168,18 +172,31 @@ public class GroupImportBestEffortTest e
         membership references.
         expected:
         - group is imported
-        - circular membership is ignored
+        - circular membership is ignored (OR fails upon save)
         - g is member of g1
-        - g1 isn't member of g
+        - g1 isn't member of g  (circular membership detected) OR saving changes fails
         */
         doImport(getTargetPath() + "/gFolder", xml2);
 
-        Authorizable g = userMgr.getAuthorizable("g");
-        assertNotNull(g);
-        if (g.isGroup()) {
-            assertNotDeclaredMember((Group) g, g1Id, adminSession);
+        Group g = getUserManager().getAuthorizable("g", Group.class);
+        Group g1 = getUserManager().getAuthorizable("g1", Group.class);
+
+        assertNotNull("'g' was not imported as Group.", g);
+        assertNotNull("'g1' was not imported as Group.", g1);
+
+        assertTrue(g1.isDeclaredMember(g));
+        if (g.isDeclaredMember(g1)) {
+            // circular membership created during import
+            try {
+                getImportSession().save();
+                fail("Circular membership must be detected upon save.");
+            } catch (ConstraintViolationException e) {
+                Throwable th = e.getCause();
+                assertTrue(th instanceof CommitFailedException);
+                assertEquals(31, ((CommitFailedException) th).getCode());
+            }
         } else {
-            fail("'g' was not imported as Group.");
+            assertNotDeclaredMember(g, g1Id, getImportSession());
         }
     }
 
@@ -199,10 +216,10 @@ public class GroupImportBestEffortTest e
                 "</sv:node>";
 
         doImport(getTargetPath(), xml);
-        User user = userMgr.createUser("angi", "pw");
-        adminSession.save();
+        User user = getUserManager().createUser("angi", "pw");
+        getImportSession().save();
 
-        Group g1 = (Group) userMgr.getAuthorizable("g1");
+        Group g1 = (Group) getUserManager().getAuthorizable("g1");
         assertTrue(g1.isMember(user));
     }
 

Modified: jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java?rev=1756639&r1=1756638&r2=1756639&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java (original)
+++ jackrabbit/oak/branches/1.2/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java Wed Aug 17 14:21:07 2016
@@ -56,9 +56,9 @@ public class GroupImportIgnoreTest exten
                 "</sv:node>";
         doImport(getTargetPath(), xml);
         // no exception during import -> member must have been ignored though.
-        Authorizable a = userMgr.getAuthorizable("g1");
+        Authorizable a = getUserManager().getAuthorizable("g1");
         if (a.isGroup()) {
-            assertNotDeclaredMember((Group) a, invalidId, adminSession);
+            assertNotDeclaredMember((Group) a, invalidId, getImportSession());
         } else {
             fail("'g1' was not imported as Group.");
         }
@@ -84,14 +84,14 @@ public class GroupImportIgnoreTest exten
                 // there should be no exception during import,
                 // but invalid members must be ignored.
                 doImport(getTargetPath(), xml);
-                Authorizable a = userMgr.getAuthorizable("g1");
+                Authorizable a = getUserManager().getAuthorizable("g1");
                 if (a.isGroup()) {
-                    assertNotDeclaredMember((Group) a, id, adminSession);
+                    assertNotDeclaredMember((Group) a, id, getImportSession());
                 } else {
                     fail("'g1' was not imported as Group.");
                 }
             } finally {
-                adminSession.refresh(false);
+                getImportSession().refresh(false);
             }
         }
     }