| /* |
| * 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.security.user; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| 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.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Tree; |
| import org.apache.jackrabbit.oak.api.Type; |
| import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; |
| import org.jetbrains.annotations.NotNull; |
| 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.assertTrue; |
| |
| /** |
| * Tests large group and user graphs. |
| * |
| * @since OAK 1.0 |
| */ |
| public class MembershipWriterTest extends MembershipBaseTest { |
| |
| private MembershipWriter writer; |
| |
| @Before |
| public void before() throws Exception { |
| super.before(); |
| writer = new MembershipWriter(); |
| // set the threshold low for testing |
| writer.setMembershipSizeThreshold(SIZE_TH); |
| } |
| |
| private static void assertContentStructure(@NotNull Tree groupTree) { |
| assertEquals( |
| "rep:members property must have correct number of references", |
| SIZE_TH, |
| groupTree.getProperty(REP_MEMBERS).count() |
| ); |
| |
| Tree membersList = groupTree.getChild(REP_MEMBERS_LIST); |
| assertTrue( |
| "rep:memberList must exist", |
| membersList.exists() |
| ); |
| |
| assertEquals( |
| "rep:memberList must have correct primary type.", |
| NT_REP_MEMBER_REFERENCES_LIST, |
| membersList.getProperty(JcrConstants.JCR_PRIMARYTYPE).getValue(Type.STRING) |
| ); |
| |
| assertEquals( |
| "rep:memberList must have correct number of child nodes.", |
| (MembershipBaseTest.NUM_USERS / SIZE_TH) - 1, |
| membersList.getChildrenCount(Long.MAX_VALUE) |
| ); |
| } |
| |
| @Test |
| public void testAddMemberExceedThreshold() throws Exception { |
| Group grp = createGroup(); |
| for (String contentId : createIdMap(0, NUM_USERS).keySet()) { |
| writer.addMember(getTree(grp), contentId); |
| } |
| root.commit(); |
| |
| // check storage structure |
| assertContentStructure(getTree(grp)); |
| } |
| |
| @Test |
| public void testAddMembersExceedThreshold() throws Exception { |
| Group grp = createGroup(); |
| Map<String, String> idMap = createIdMap(0, NUM_USERS); |
| writer.addMembers(getTree(grp), idMap); |
| root.commit(); |
| |
| // check storage structure |
| assertContentStructure(getTree(grp)); |
| } |
| |
| @Test |
| public void testAddMembersExceedThreshold2() throws Exception { |
| Tree groupTree = getTree(createGroup()); |
| |
| // 1. add array of 21 memberIDs exceeding the threshold |
| Map<String, String> memberIds = createIdMap(0, 21); |
| writer.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); |
| writer.addMembers(groupTree, memberIds); |
| assertMemberList(groupTree, 2, 9); |
| |
| // 3. fill up second ref-members node => a new one must be created |
| memberIds = createIdMap(29, 35); |
| writer.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(getContentID("member1"), "member1"); |
| memberIds.put(getContentID("member2"), "member2"); |
| writer.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); |
| writer.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); |
| writer.addMembers(groupTree, memberIds); |
| |
| assertEquals(SIZE_TH, groupTree.getProperty(REP_MEMBERS).count()); |
| assertEquals(4, groupTree.getChild(REP_MEMBERS_LIST).getChildrenCount(10)); |
| } |
| |
| @Test |
| public void testAddMemberAgain() throws Exception { |
| Group grp = createGroup(); |
| List<String> memberPaths = createMembers(grp, NUM_USERS); |
| root.commit(); |
| |
| Tree groupTree = getTree(grp); |
| for (String path : memberPaths) { |
| Tree memberTree = root.getTree(path); |
| String contentId = getContentID(memberTree); |
| assertFalse(writer.addMember(groupTree, contentId)); |
| } |
| } |
| |
| @Test |
| public void testAddMembersAgain() throws Exception { |
| Group grp = createGroup(); |
| List<String> memberPaths = createMembers(grp, NUM_USERS); |
| root.commit(); |
| |
| Map<String, String> m = new HashMap<>(); |
| for (String path : memberPaths) { |
| Tree memberTree = root.getTree(path); |
| String memberId = TreeUtil.getString(memberTree, REP_AUTHORIZABLE_ID); |
| m.put(getContentID(memberTree), memberId); |
| } |
| |
| Set<String> expected = Sets.newHashSet(m.values()); |
| Set<String> failed = writer.addMembers(getTree(grp), m); |
| assertFalse(failed.isEmpty()); |
| assertEquals(expected, failed); |
| } |
| |
| @Test |
| public void testAddFewMembers() throws Exception { |
| Group g = createGroup(); |
| g.addMembers(createGroup().getID(), createUser().getID()); |
| |
| assertFalse(getTree(g).hasChild(REP_MEMBERS_LIST)); |
| } |
| |
| @Test |
| 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++) { |
| User usr = createUser(); |
| grp.addMember(usr); |
| members.add(usr.getID()); |
| users[i] = usr.getID(); |
| } |
| root.commit(); |
| |
| // 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++) { |
| Authorizable auth = userMgr.getAuthorizable(users[i]); |
| members.remove(users[i]); |
| grp.removeMember(auth); |
| } |
| root.commit(); |
| |
| assertMembers(grp, members); |
| |
| // also check storage structure |
| Tree tree = getTree(grp); |
| assertNull( |
| "rep:members property not exist", |
| tree.getProperty(REP_MEMBERS) |
| ); |
| |
| // now add TH/2 again. |
| for (int i = 0; i < SIZE_TH / 2; i++) { |
| Authorizable auth = userMgr.getAuthorizable(users[i]); |
| members.add(users[i]); |
| grp.addMember(auth); |
| } |
| root.commit(); |
| |
| assertEquals( |
| "rep:members property must have correct number of references", |
| SIZE_TH / 2, |
| tree.getProperty(REP_MEMBERS).count() |
| ); |
| 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++) { |
| Authorizable auth = userMgr.getAuthorizable(users[i]); |
| members.remove(users[i]); |
| grp.removeMember(auth); |
| } |
| root.commit(); |
| |
| assertMembers(grp, members); |
| |
| 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++) { |
| Authorizable auth = userMgr.getAuthorizable(users[i]); |
| members.add(users[i]); |
| grp.addMember(auth); |
| } |
| root.commit(); |
| assertMembers(grp, members); |
| |
| membersList = tree.getChild(REP_MEMBERS_LIST); |
| assertTrue("the first overflow node must not exist", membersList.getChild("1").exists() |
| ); |
| } |
| } |