Author: stillalex Date: Thu Feb 21 14:44:15 2019 New Revision: 1854053 URL: http://svn.apache.org/viewvc?rev=1854053&view=rev Log: OAK-8054 RepMembersConflictHandler creates property with wrong type - backport
Modified: jackrabbit/oak/branches/1.10/ (props changed) jackrabbit/oak/branches/1.10/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandler.java jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/GroupImplTest.java jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandlerTest.java Propchange: jackrabbit/oak/branches/1.10/ ------------------------------------------------------------------------------ --- svn:mergeinfo (original) +++ svn:mergeinfo Thu Feb 21 14:44:15 2019 @@ -1,3 +1,3 @@ /jackrabbit/oak/branches/1.0:1665962 -/jackrabbit/oak/trunk:1851236,1851253,1851451,1852052,1852084,1852120,1852451,1852492-1852493,1852528,1852582,1852584,1852920,1853393,1853429,1853433,1853441,1853866,1853870,1853893,1853969,1853997 +/jackrabbit/oak/trunk:1851236,1851253,1851451,1852052,1852084,1852120,1852451,1852492-1852493,1852528,1852582,1852584,1852920,1853393,1853429,1853433,1853441,1853866,1853868,1853870,1853893,1853969,1853997 /jackrabbit/trunk:1345480 Modified: jackrabbit/oak/branches/1.10/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandler.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.10/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandler.java?rev=1854053&r1=1854052&r2=1854053&view=diff ============================================================================== --- jackrabbit/oak/branches/1.10/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandler.java (original) +++ jackrabbit/oak/branches/1.10/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandler.java Thu Feb 21 14:44:15 2019 @@ -18,8 +18,10 @@ */ package org.apache.jackrabbit.oak.security.user; +import java.util.LinkedHashSet; import java.util.Set; +import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder; @@ -29,6 +31,7 @@ import org.apache.jackrabbit.oak.spi.sta import org.apache.jackrabbit.oak.spi.state.NodeState; import org.jetbrains.annotations.NotNull; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; /** @@ -37,10 +40,14 @@ import com.google.common.collect.Sets; *<p> * The conflict handler deals with the following conflicts: * <ul> - * <li>{@code addExistingProperty} : {@code Resolution.MERGED},</li> + * <li>{@code addExistingProperty} : {@code Resolution.MERGED}.</li> * <li>{@code changeDeletedProperty}: {@code Resolution.THEIRS}, removing the members property takes precedence. * <li>{@code changeChangedProperty}: {@code Resolution.MERGED}, merge of the 2 members sets into a single one * <li>{@code deleteChangedProperty}: {@code Resolution.OURS} removing the members property takes precedence. + * <li>{@code deleteDeletedProperty}: {@code Resolution.MERGED}.</li> + * <li>{@code changeDeletedNode} : {@code Resolution.THEIRS}, removal takes precedence.</li> + * <li>{@code deleteChangedNode} : {@code Resolution.OURS}, removal takes precedence.</li> + * <li>{@code deleteDeletedNode} : {@code Resolution.MERGED}.</li> * </ul> */ class RepMembersConflictHandler implements ThreeWayConflictHandler { @@ -50,7 +57,7 @@ class RepMembersConflictHandler implemen public Resolution addExistingProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState theirs) { if (isRepMembersProperty(theirs)) { - mergeChange(parent, ours, theirs,Sets.newHashSet()); + mergeChange(parent, ours, theirs, ImmutableSet.of()); return Resolution.MERGED; } else { return Resolution.IGNORED; @@ -74,7 +81,7 @@ class RepMembersConflictHandler implemen public Resolution changeChangedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState ours, @NotNull PropertyState theirs, @NotNull PropertyState base) { if (isRepMembersProperty(theirs)) { - Set<String> baseMembers = Sets.newHashSet(base.getValue(Type.STRINGS)); + Set<String> baseMembers = ImmutableSet.copyOf(base.getValue(Type.STRINGS)); mergeChange(parent, ours, theirs, baseMembers); return Resolution.MERGED; } else { @@ -85,8 +92,12 @@ class RepMembersConflictHandler implemen @NotNull @Override public Resolution deleteDeletedProperty(@NotNull NodeBuilder parent, @NotNull PropertyState base) { - // both are removing the members property, ignoring - return Resolution.IGNORED; + if (isRepMembersProperty(base)) { + // both are removing the members property + return Resolution.MERGED; + } else { + return Resolution.IGNORED; + } } @NotNull @@ -101,7 +112,6 @@ class RepMembersConflictHandler implemen } } - @NotNull @Override public Resolution addExistingNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState ours, @@ -113,33 +123,45 @@ class RepMembersConflictHandler implemen @Override public Resolution changeDeletedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState ours, @NotNull NodeState base) { - return Resolution.IGNORED; + if (isMemberRefType(base)) { + return Resolution.THEIRS; + } else { + return Resolution.IGNORED; + } } @NotNull @Override public Resolution deleteChangedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState theirs, @NotNull NodeState base) { - return Resolution.IGNORED; + if (isMemberRefType(base)) { + return Resolution.OURS; + } else { + return Resolution.IGNORED; + } } @NotNull @Override public Resolution deleteDeletedNode(@NotNull NodeBuilder parent, @NotNull String name, @NotNull NodeState base) { - return Resolution.IGNORED; + if (isMemberRefType(base)) { + return Resolution.MERGED; + } else { + return Resolution.IGNORED; + } } //----------------------------< internal >---------------------------------- private static void mergeChange(NodeBuilder parent, PropertyState ours, PropertyState theirs, Set<String> base) { - PropertyBuilder<String> merged = PropertyBuilder.array(Type.STRING); + PropertyBuilder<String> merged = PropertyBuilder.array(Type.WEAKREFERENCE); merged.setName(UserConstants.REP_MEMBERS); - Set<String> theirMembers = Sets.newHashSet(theirs.getValue(Type.STRINGS)); - Set<String> ourMembers = Sets.newHashSet(ours.getValue(Type.STRINGS)); + Set<String> theirMembers = ImmutableSet.copyOf(theirs.getValue(Type.STRINGS)); + Set<String> ourMembers = ImmutableSet.copyOf(ours.getValue(Type.STRINGS)); // merge ours and theirs to a de-duplicated set - Set<String> combined = Sets.newHashSet(Sets.intersection(ourMembers, theirMembers)); + Set<String> combined = new LinkedHashSet<>(Sets.intersection(ourMembers, theirMembers)); for (String m : Sets.difference(ourMembers, theirMembers)) { if (!base.contains(m)) { combined.add(m); @@ -158,4 +180,8 @@ class RepMembersConflictHandler implemen return UserConstants.REP_MEMBERS.equals(p.getName()); } + private static boolean isMemberRefType(NodeState base) { + String type = base.getName(JcrConstants.JCR_PRIMARYTYPE); + return UserConstants.NT_REP_MEMBER_REFERENCES.equals(type); + } } Modified: jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/GroupImplTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/GroupImplTest.java?rev=1854053&r1=1854052&r2=1854053&view=diff ============================================================================== --- jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/GroupImplTest.java (original) +++ jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/GroupImplTest.java Thu Feb 21 14:44:15 2019 @@ -20,14 +20,21 @@ import java.security.Principal; import java.util.Iterator; import java.util.UUID; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; 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.ConfigurationParameters; import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal; import org.junit.Test; import org.mockito.Mockito; +import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.REP_MEMBERS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -121,4 +128,36 @@ public class GroupImplTest extends Abstr Iterator<Authorizable> members = groupPrincipal.getMembers(); assertTrue(Iterators.elementsEqual(group.getMembers(), members)); } + + @Test + public void testImpactOfOak8054AddingMembers() throws Exception { + Tree groupTree = root.getTree(group.getPath()); + groupTree.setProperty(REP_MEMBERS, ImmutableList.of(new UserProvider(root, ConfigurationParameters.EMPTY).getContentID(getTestUser().getID())), Type.STRINGS); + root.commit(); + + group.addMember(uMgr.createUser("userid", null)); + root.commit(); + + groupTree = root.getTree(group.getPath()); + PropertyState membersProp = groupTree.getProperty(REP_MEMBERS); + assertEquals(Type.WEAKREFERENCES, membersProp.getType()); + assertEquals(2, membersProp.count()); + } + + @Test + public void testImpactOfOak8054RemovingMembers() throws Exception { + User user = uMgr.createUser("userid", null); + UserProvider up = new UserProvider(root, ConfigurationParameters.EMPTY); + Tree groupTree = root.getTree(group.getPath()); + groupTree.setProperty(REP_MEMBERS, ImmutableList.of(up.getContentID(getTestUser().getID()), up.getContentID(user.getID())), Type.STRINGS); + root.commit(); + + group.removeMembers(user.getID()); + root.commit(); + + groupTree = root.getTree(group.getPath()); + PropertyState membersProp = groupTree.getProperty(REP_MEMBERS); + assertEquals(Type.WEAKREFERENCES, membersProp.getType()); + assertEquals(1, membersProp.count()); + } } \ No newline at end of file Modified: jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandlerTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandlerTest.java?rev=1854053&r1=1854052&r2=1854053&view=diff ============================================================================== --- jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandlerTest.java (original) +++ jackrabbit/oak/branches/1.10/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/RepMembersConflictHandlerTest.java Thu Feb 21 14:44:15 2019 @@ -19,30 +19,68 @@ package org.apache.jackrabbit.oak.securi import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +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.Root; +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.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 com.google.common.collect.Lists; +@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"); @@ -53,6 +91,14 @@ public class RepMembersConflictHandlerTe 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(); @@ -105,7 +151,8 @@ public class RepMembersConflictHandlerTe } /** - * Remove-Add with different ids test + * 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 { @@ -120,11 +167,12 @@ public class RepMembersConflictHandlerTe root.refresh(); assertTrue(group.isDeclaredMember(users[0])); assertFalse(group.isDeclaredMember(users[1])); - assertTrue(group.isDeclaredMember(users[2])); + // group.isDeclaredMember(users[2]) might be true or false } /** - * Add-Remove with different ids test + * 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 { @@ -139,7 +187,7 @@ public class RepMembersConflictHandlerTe root.refresh(); assertTrue(group.isDeclaredMember(users[0])); assertFalse(group.isDeclaredMember(users[1])); - assertTrue(group.isDeclaredMember(users[2])); + // group.isDeclaredMember(users[2]) might be true or false } /** @@ -225,6 +273,25 @@ public class RepMembersConflictHandlerTe } /** + * 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 @@ -260,6 +327,34 @@ public class RepMembersConflictHandlerTe 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); @@ -281,6 +376,14 @@ public class RepMembersConflictHandlerTe 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(); }