| /* |
| * 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 com.google.common.collect.Lists; |
| 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.api.security.user.UserManager; |
| import org.apache.jackrabbit.oak.AbstractSecurityTest; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Root; |
| import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; |
| import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler; |
| import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; |
| import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration; |
| import org.apache.jackrabbit.oak.spi.security.user.UserConstants; |
| import org.apache.jackrabbit.oak.spi.state.NodeBuilder; |
| import org.apache.jackrabbit.oak.spi.state.NodeState; |
| import org.apache.jackrabbit.oak.spi.xml.ImportBehavior; |
| import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter; |
| import org.jetbrains.annotations.NotNull; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; |
| import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.NT_REP_MEMBER_REFERENCES; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| @RunWith(Parameterized.class) |
| public class RepMembersConflictHandlerTest extends AbstractSecurityTest { |
| |
| @Parameters(name = "name={1}") |
| public static Collection<Object[]> parameters() { |
| return Lists.newArrayList( |
| new Object[] { 0, "Empty Group" }, |
| new Object[] { 5, "Tiny Group" }, |
| new Object[] { MembershipWriter.DEFAULT_MEMBERSHIP_THRESHOLD - 1, "Border Group" }, |
| new Object[] { MembershipWriter.DEFAULT_MEMBERSHIP_THRESHOLD * 2, "Large Group" }); |
| } |
| |
| /** |
| * The id of the test group |
| */ |
| private static final String GROUP_ID = "test-groupId"; |
| |
| private final int count; |
| private Group group; |
| private User[] users; |
| |
| public RepMembersConflictHandlerTest(int count, String name) { |
| this.count = count; |
| } |
| |
| @Override |
| protected ConfigurationParameters getSecurityConfigParameters() { |
| return ConfigurationParameters.of(UserConfiguration.NAME, ConfigurationParameters |
| .of(ProtectedItemImporter.PARAM_IMPORT_BEHAVIOR, ImportBehavior.NAME_BESTEFFORT)); |
| } |
| |
| @Override |
| public void before() throws Exception { |
| super.before(); |
| UserManager um = getUserManager(root); |
| // create a group to receive users |
| group = um.createGroup(GROUP_ID); |
| fillGroup(group, count); |
| // create future members of the above group |
| User u1 = um.createUser("u1", "pass"); |
| User u2 = um.createUser("u2", "pass"); |
| User u3 = um.createUser("u3", "pass"); |
| User u4 = um.createUser("u4", "pass"); |
| User u5 = um.createUser("u5", "pass"); |
| root.commit(); |
| users = new User[] { u1, u2, u3, u4, u5 }; |
| } |
| |
| void fillGroup(@NotNull Group group, int count) throws Exception { |
| List<String> memberIds = new ArrayList<>(); |
| for (int i = 0; i < count; i++) { |
| memberIds.add("member" + i); |
| } |
| assertTrue(group.addMembers(memberIds.toArray(new String[0])).isEmpty()); |
| } |
| |
| @Test |
| public void addExistingProperty() throws Exception { |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| add(ours, users[1].getID()); |
| add(theirs, users[2].getID()); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[1])); |
| assertTrue(group.isDeclaredMember(users[2])); |
| } |
| |
| /** |
| * Add-Add test |
| */ |
| @Test |
| public void changeChangedPropertyAA() throws Exception { |
| add(root, users[0].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| add(ours, users[1].getID()); |
| add(theirs, users[2].getID()); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertTrue(group.isDeclaredMember(users[1])); |
| assertTrue(group.isDeclaredMember(users[2])); |
| } |
| |
| /** |
| * Remove-Remove test |
| */ |
| @Test |
| public void changeChangedPropertyRR() throws Exception { |
| add(root, users[0].getID(), users[1].getID(), users[2].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| rm(ours, users[1].getID()); |
| rm(theirs, users[2].getID()); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| assertFalse(group.isDeclaredMember(users[2])); |
| } |
| |
| /** |
| * Remove-Add with different ids test. Depending on the size of the group |
| * the addition might work, but the removal has to definitely work. |
| */ |
| @Test |
| public void changeChangedPropertyRA() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| rm(ours, users[1].getID()); |
| add(theirs, users[2].getID()); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| // group.isDeclaredMember(users[2]) might be true or false |
| } |
| |
| /** |
| * Add-Remove with different ids test. Depending on the size of the group |
| * the addition might work, but the removal has to definitely work. |
| */ |
| @Test |
| public void changeChangedPropertyAR() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| add(ours, users[2].getID()); |
| rm(theirs, users[1].getID()); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| // group.isDeclaredMember(users[2]) might be true or false |
| } |
| |
| /** |
| * Remove-Add same value test. value was already part of the group |
| */ |
| @Test |
| public void changeChangedPropertyRA2() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| String id = users[1].getID(); |
| rm(ours, id); |
| add(theirs, id); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| } |
| |
| /** |
| * Add-Remove same value test. value was already part of the group |
| */ |
| @Test |
| public void changeChangedPropertyAR2() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| String id = users[1].getID(); |
| add(ours, id); |
| rm(theirs, id); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| } |
| |
| /** |
| * Remove-Add same value test. value was not part of the group |
| */ |
| @Test |
| public void changeChangedPropertyRA3() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| String id = users[2].getID(); |
| // we are removing an item that does not yet exist, this should |
| // not overlap/conflict with the other session adding the same item |
| rm(ours, id); |
| add(theirs, id); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertTrue(group.isDeclaredMember(users[1])); |
| assertTrue(group.isDeclaredMember(users[2])); |
| } |
| |
| /** |
| * Add-Remove same value test. value was not part of the group |
| */ |
| @Test |
| public void changeChangedPropertyAR3() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| String id = users[2].getID(); |
| add(ours, id); |
| // we are removing an item that does not yet exist, this should |
| // not overlap/conflict with the other session adding the same item |
| rm(theirs, id); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertTrue(group.isDeclaredMember(users[1])); |
| assertTrue(group.isDeclaredMember(users[2])); |
| } |
| |
| /** |
| * Add-Remove wiht single element, generating zero item group. Depending on |
| * the addition might work, but the removal has to definitely work. |
| */ |
| @Test |
| public void changeChangedPropertyAR4() throws Exception { |
| add(root, users[0].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| add(ours, users[1].getID()); |
| rm(theirs, users[0].getID()); |
| |
| root.refresh(); |
| assertFalse(group.isDeclaredMember(users[0])); |
| // group.isDeclaredMember(users[1]) might be true or false |
| } |
| |
| /** |
| * Delete-Changed. Delete takes precedence |
| */ |
| @Test |
| public void deleteChangedProperty() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| add(theirs, users[2].getID()); |
| wipeGroup(ours); |
| |
| root.refresh(); |
| assertFalse(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| } |
| |
| /** |
| * Changed-Deleted. Delete takes precedence |
| */ |
| @Test |
| public void changeDeletedProperty() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| wipeGroup(theirs); |
| add(ours, users[2].getID()); |
| |
| root.refresh(); |
| assertFalse(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| } |
| |
| @Test |
| public void deleteDeletedTest() throws Exception { |
| add(root, users[0].getID(), users[1].getID()); |
| |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| wipeGroup(theirs); |
| wipeGroup(ours); |
| |
| root.refresh(); |
| assertFalse(group.isDeclaredMember(users[0])); |
| assertFalse(group.isDeclaredMember(users[1])); |
| } |
| |
| @Test |
| public void addAddedTest() throws Exception { |
| Root ours = login(getAdminCredentials()).getLatestRoot(); |
| Root theirs = login(getAdminCredentials()).getLatestRoot(); |
| |
| add(ours, users[0].getID()); |
| add(theirs, users[1].getID()); |
| |
| root.refresh(); |
| assertTrue(group.isDeclaredMember(users[0])); |
| assertTrue(group.isDeclaredMember(users[1])); |
| } |
| |
| private void add(Root r, String... ids) throws Exception { |
| UserManager um = getUserManager(r); |
| Group g = (Group) um.getAuthorizable(GROUP_ID); |
| for (String id : ids) { |
| g.addMember(um.getAuthorizable(id)); |
| } |
| r.commit(); |
| } |
| |
| private void rm(Root r, String... ids) throws Exception { |
| UserManager um = getUserManager(r); |
| Group g = (Group) um.getAuthorizable(GROUP_ID); |
| for (String id : ids) { |
| g.removeMember(um.getAuthorizable(id)); |
| } |
| r.commit(); |
| } |
| |
| private void wipeGroup(Root r) throws Exception { |
| UserManager um = getUserManager(r); |
| Group g = (Group) um.getAuthorizable(GROUP_ID); |
| |
| List<String> members = new ArrayList<>(); |
| Iterator<Authorizable> it = g.getDeclaredMembers(); |
| while (it.hasNext()) { |
| members.add(it.next().getID()); |
| } |
| assertTrue(g.removeMembers(members.toArray(new String[0])).isEmpty()); |
| // needed to trigger conflict events in the 'small group' case |
| r.getTree(g.getPath()).removeProperty(UserConstants.REP_MEMBERS); |
| r.commit(); |
| } |
| |
| @Test |
| public void testAddExistingPropertyOther() throws Exception { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| PropertyState ours = PropertyStates.createProperty("any", "value"); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.addExistingProperty(mock(NodeBuilder.class), ours, mock(PropertyState.class))); |
| } |
| |
| @Test |
| public void testChangeDeletedPropertyRepMembers() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| PropertyState ours = PropertyStates.createProperty(UserConstants.REP_MEMBERS, "value"); |
| assertSame(ThreeWayConflictHandler.Resolution.THEIRS, handler.changeDeletedProperty(mock(NodeBuilder.class), ours, mock(PropertyState.class))); |
| } |
| |
| @Test |
| public void testChangeDeletedPropertyOther() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| PropertyState ours = PropertyStates.createProperty("any", "value"); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.changeDeletedProperty(mock(NodeBuilder.class), ours, mock(PropertyState.class))); |
| } |
| |
| @Test |
| public void testDeleteDeletedPropertyRepMembers() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| PropertyState base = PropertyStates.createProperty(UserConstants.REP_MEMBERS, "value"); |
| assertSame(ThreeWayConflictHandler.Resolution.MERGED, handler.deleteDeletedProperty(mock(NodeBuilder.class), base)); |
| } |
| |
| @Test |
| public void testDeleteDeletedPropertyOther() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| PropertyState base = PropertyStates.createProperty("any", "value"); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.deleteDeletedProperty(mock(NodeBuilder.class), base)); |
| } |
| |
| @Test |
| public void testDeleteChangedPropertyRepMembers() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| PropertyState theirs = PropertyStates.createProperty(UserConstants.REP_MEMBERS, "value"); |
| assertSame(ThreeWayConflictHandler.Resolution.OURS, handler.deleteChangedProperty(mock(NodeBuilder.class), theirs, mock(PropertyState.class))); |
| } |
| |
| @Test |
| public void testDeleteChangedPropertyOther() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| PropertyState theirs = PropertyStates.createProperty("any", "value"); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.deleteChangedProperty(mock(NodeBuilder.class), theirs, mock(PropertyState.class))); |
| } |
| |
| @Test |
| public void testAddExistingNode() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.addExistingNode(mock(NodeBuilder.class), "name", mock(NodeState.class), mock(NodeState.class))); |
| } |
| |
| @Test |
| public void testChangeDeletedNodeMemberRef() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| NodeState base = when(mock(NodeState.class).getName(JCR_PRIMARYTYPE)).thenReturn(NT_REP_MEMBER_REFERENCES).getMock(); |
| assertSame(ThreeWayConflictHandler.Resolution.THEIRS, handler.changeDeletedNode(mock(NodeBuilder.class), "name", mock(NodeState.class), base)); |
| } |
| |
| @Test |
| public void testChangeDeletedNodeOther() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.changeDeletedNode(mock(NodeBuilder.class), "name", mock(NodeState.class), mock(NodeState.class))); |
| } |
| |
| @Test |
| public void testDeleteChangedNodeMemberRef() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| NodeState base = when(mock(NodeState.class).getName(JCR_PRIMARYTYPE)).thenReturn(NT_REP_MEMBER_REFERENCES).getMock(); |
| assertSame(ThreeWayConflictHandler.Resolution.OURS, handler.deleteChangedNode(mock(NodeBuilder.class), "name", mock(NodeState.class), base)); |
| } |
| |
| @Test |
| public void testDeleteChangedNodeOther() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.deleteChangedNode(mock(NodeBuilder.class), "name", mock(NodeState.class), mock(NodeState.class))); |
| } |
| |
| @Test |
| public void testDeleteDeletedNodeMemberRef() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| NodeState base = when(mock(NodeState.class).getName(JCR_PRIMARYTYPE)).thenReturn(NT_REP_MEMBER_REFERENCES).getMock(); |
| assertSame(ThreeWayConflictHandler.Resolution.MERGED, handler.deleteDeletedNode(mock(NodeBuilder.class), "name", base)); |
| } |
| |
| @Test |
| public void testDeleteDeletedNodeOther() { |
| RepMembersConflictHandler handler = new RepMembersConflictHandler(); |
| assertSame(ThreeWayConflictHandler.Resolution.IGNORED, handler.deleteDeletedNode(mock(NodeBuilder.class), "name", mock(NodeState.class))); |
| } |
| } |