| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Lists; |
| 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.api.CommitFailedException; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Root; |
| import org.apache.jackrabbit.oak.api.Tree; |
| import org.apache.jackrabbit.oak.api.Type; |
| import org.apache.jackrabbit.oak.commons.PathUtils; |
| import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; |
| import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; |
| import org.apache.jackrabbit.oak.spi.commit.CommitInfo; |
| import org.apache.jackrabbit.oak.spi.commit.Validator; |
| import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; |
| import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef; |
| import org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider; |
| import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig; |
| import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl; |
| import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType; |
| import org.apache.jackrabbit.oak.spi.security.user.UserConstants; |
| import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil; |
| import org.apache.jackrabbit.oak.spi.state.NodeState; |
| import org.jetbrains.annotations.NotNull; |
| import org.junit.Test; |
| |
| import javax.jcr.ValueFactory; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; |
| import static org.apache.jackrabbit.JcrConstants.JCR_UUID; |
| import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.JCR_LASTMODIFIEDBY; |
| import static org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants.REP_EXTERNAL_ID; |
| import static org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES; |
| import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.NT_REP_GROUP; |
| import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.REP_MEMBERS; |
| import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.REP_MEMBERS_LIST; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| public class DynamicGroupValidatorTest extends AbstractPrincipalTest { |
| |
| private Root r; |
| private UserManager userManager; |
| private User testUser; |
| private Group localGroup; |
| private Group dynamicGroup; |
| |
| @Override |
| public void before() throws Exception { |
| super.before(); |
| User tu = getTestUser(); |
| |
| r = getSystemRoot(); |
| userManager = getUserManager(r); |
| testUser = userManager.getAuthorizable(tu.getID(), User.class); |
| localGroup = createTestGroup(r); |
| dynamicGroup = userManager.getAuthorizable("aaa", Group.class); |
| assertNotNull(dynamicGroup); |
| |
| registerSyncHandler(syncConfigAsMap(), idp.getName()); |
| } |
| |
| @Override |
| public void after() throws Exception { |
| try { |
| r.refresh(); |
| if (localGroup != null) { |
| localGroup.remove(); |
| r.commit(); |
| } |
| } finally { |
| super.after(); |
| } |
| } |
| |
| @Override |
| protected @NotNull DefaultSyncConfig createSyncConfig() { |
| DefaultSyncConfig config = super.createSyncConfig(); |
| config.group().setDynamicGroups(true); |
| config.user().setMembershipNestingDepth(2); |
| return config; |
| } |
| |
| @Override |
| @NotNull Set<String> getIdpNamesWithDynamicGroups() { |
| return Collections.singleton(idp.getName()); |
| } |
| |
| @Test |
| public void testAddMemberDynamicGroup() throws Exception { |
| dynamicGroup.addMember(userManager.getAuthorizable(USER_ID)); |
| try { |
| r.commit(); |
| fail("CommitFailedException 77 expected."); |
| } catch (CommitFailedException e) { |
| assertEquals(77, e.getCode()); |
| } |
| } |
| |
| @Test |
| public void testAddMemberLocalGroup() throws Exception { |
| localGroup.addMember(testUser); |
| r.commit(); |
| |
| assertTrue(localGroup.isDeclaredMember(testUser)); |
| } |
| |
| @Test |
| public void testAddMembersProperty() throws Exception { |
| Tree groupTree = r.getTree(dynamicGroup.getPath()); |
| assertFalse(groupTree.hasProperty(REP_MEMBERS)); |
| |
| String uuid = r.getTree(userManager.getAuthorizable(USER_ID).getPath()).getProperty(JCR_UUID).getValue(Type.STRING); |
| groupTree.setProperty(REP_MEMBERS, ImmutableList.of(uuid), Type.WEAKREFERENCES); |
| try { |
| r.commit(); |
| fail("CommitFailedException 77 expected."); |
| } catch (CommitFailedException e) { |
| assertEquals(77, e.getCode()); |
| } |
| } |
| |
| @Test |
| public void testAddMembersListTree() throws Exception { |
| Tree groupTree = r.getTree(dynamicGroup.getPath()); |
| assertFalse(groupTree.hasChild(UserConstants.REP_MEMBERS_LIST)); |
| TreeUtil.addChild(groupTree, UserConstants.REP_MEMBERS_LIST, UserConstants.NT_REP_MEMBER_REFERENCES_LIST); |
| try { |
| r.commit(); |
| fail("CommitFailedException 77 expected."); |
| } catch (CommitFailedException e) { |
| assertEquals(77, e.getCode()); |
| } |
| } |
| |
| @Test |
| public void testAddMembersTree() throws Exception { |
| Tree groupTree = r.getTree(dynamicGroup.getPath()); |
| assertFalse(groupTree.hasChild(REP_MEMBERS)); |
| TreeUtil.addChild(groupTree, REP_MEMBERS, UserConstants.NT_REP_MEMBERS); |
| try { |
| r.commit(); |
| fail("CommitFailedException 77 expected."); |
| } catch (CommitFailedException e) { |
| assertEquals(77, e.getCode()); |
| } |
| } |
| |
| @Test |
| public void testAddMembersTreeWithoutPrimaryType() throws Exception { |
| NodeState ns = mock(NodeState.class); |
| when(ns.getChildNode(anyString())).thenReturn(ns); |
| |
| String groupRoot = UserUtil.getAuthorizableRootPath(getUserConfiguration().getParameters(), AuthorizableType.GROUP); |
| DynamicGroupValidatorProvider provider = new DynamicGroupValidatorProvider(getRootProvider(), getTreeProvider(), getSecurityProvider(), getIdpNamesWithDynamicGroups()); |
| Validator v = provider.getRootValidator(ns, ns, CommitInfo.EMPTY); |
| // traverse the subtree-validator |
| for (String name : PathUtils.elements(groupRoot)) { |
| v = v.childNodeAdded(name, ns); |
| } |
| |
| // add a dynamic group node |
| PropertyState ps = PropertyStates.createProperty(REP_EXTERNAL_ID, new ExternalIdentityRef("gr", idp.getName()).getString(), Type.STRING); |
| when(ns.getProperty(REP_EXTERNAL_ID)).thenReturn(ps); |
| PropertyState primaryPs = PropertyStates.createProperty(JCR_PRIMARYTYPE, NT_REP_GROUP, Type.NAME); |
| when(ns.getProperty(JCR_PRIMARYTYPE)).thenReturn(primaryPs); |
| v = v.childNodeAdded("group", ns); |
| |
| // add rep:membersList child node without primary type inside the dynamic group -> must not fail |
| NodeState ns2 = mock(NodeState.class); |
| assertNotNull(v.childNodeAdded(REP_MEMBERS_LIST, ns2)); |
| } |
| |
| @Test |
| public void testAddMembersToPreviouslySyncedGroup() throws Exception { |
| User second = userManager.getAuthorizable(TestIdentityProvider.ID_SECOND_USER, User.class); |
| assertNotNull(second); |
| assertFalse(second.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)); |
| Group secondGroup = userManager.getAuthorizable("secondGroup", Group.class); |
| assertNotNull(secondGroup); |
| Tree groupTree = r.getTree(secondGroup.getPath()); |
| assertTrue(groupTree.hasProperty(REP_MEMBERS)); |
| |
| // adding members to a group that has been synced-before must not succeed as the DynamicSyncContext will |
| // eventually migrate them to dynamic membership |
| secondGroup.addMember(userManager.getAuthorizable(USER_ID)); |
| try { |
| r.commit(); |
| fail("CommitFailedException 77 expected."); |
| } catch (CommitFailedException e) { |
| assertEquals(77, e.getCode()); |
| } |
| } |
| |
| @Test |
| public void testAddProperties() throws Exception { |
| Tree groupTree = r.getTree(dynamicGroup.getPath()); |
| ValueFactory vf = getValueFactory(r); |
| |
| dynamicGroup.setProperty("rel/path/test", vf.createValue("value")); |
| r.commit(); |
| assertTrue(groupTree.hasChild("rel")); |
| |
| groupTree.setProperty("test", "value"); |
| r.commit(); |
| assertTrue(dynamicGroup.hasProperty("test")); |
| } |
| |
| @Test |
| public void testModifyProperties() throws Exception { |
| ValueFactory vf = getValueFactory(r); |
| |
| dynamicGroup.setProperty("rel/path/test", vf.createValue("value")); |
| r.commit(); |
| assertTrue(dynamicGroup.hasProperty("rel/path/test")); |
| |
| dynamicGroup.setProperty("rel/path/test", vf.createValue("value2")); |
| r.commit(); |
| assertTrue(dynamicGroup.hasProperty("rel/path/test")); |
| } |
| |
| @Test |
| public void testModifyPropertiesLocalGroup() throws Exception { |
| ValueFactory vf = getValueFactory(r); |
| |
| localGroup.setProperty("test", vf.createValue("value")); |
| r.commit(); |
| assertTrue(localGroup.hasProperty("test")); |
| |
| localGroup.setProperty("test", vf.createValue("value2")); |
| r.commit(); |
| assertTrue(localGroup.hasProperty("test")); |
| } |
| |
| @Test |
| public void testModifyMembersPropertyLocalGroup() throws Exception { |
| localGroup.addMember(testUser); |
| r.commit(); |
| |
| Tree groupTree = r.getTree(localGroup.getPath()); |
| Tree userTree = r.getTree(userManager.getAuthorizable(USER_ID).getPath()); |
| List<String> members = Lists.newArrayList(groupTree.getProperty(REP_MEMBERS).getValue(Type.STRINGS)); |
| members.add(userTree.getProperty(JCR_UUID).getValue(Type.STRING)); |
| groupTree.setProperty(REP_MEMBERS, members, Type.WEAKREFERENCES); |
| r.commit(); |
| |
| assertEquals(2, Iterators.size(localGroup.getMembers())); |
| } |
| |
| |
| @Test |
| public void testModifyFolderProperties() throws Exception { |
| Tree folderTree = r.getTree(localGroup.getPath()).getParent(); |
| TreeUtil.addMixin(folderTree, NodeTypeConstants.MIX_LASTMODIFIED, r.getTree(NodeTypeConstants.NODE_TYPES_PATH), "id"); |
| r.commit(); |
| |
| folderTree.setProperty(JCR_LASTMODIFIEDBY, "otherId"); |
| r.commit(); |
| |
| assertEquals("otherId", folderTree.getProperty(JCR_LASTMODIFIEDBY).getValue(Type.STRING)); |
| assertEquals("otherId", folderTree.getProperty(JCR_LASTMODIFIEDBY).getValue(Type.STRING)); |
| } |
| |
| @Test |
| public void testModifyMembersPropertyRemove() throws Exception { |
| localGroup.addMember(testUser); |
| localGroup.addMember(userManager.getAuthorizable(USER_ID)); |
| // mark the local group as dynamic (setting the extid property) |
| String extid = new ExternalIdentityRef(localGroup.getID(), idp.getName()).getString(); |
| localGroup.setProperty(REP_EXTERNAL_ID, getValueFactory(r).createValue(extid)); |
| r.commit(); |
| |
| Tree groupTree = r.getTree(localGroup.getPath()); |
| List<String> members = Lists.newArrayList(groupTree.getProperty(REP_MEMBERS).getValue(Type.STRINGS)); |
| members.remove(1); |
| groupTree.setProperty(REP_MEMBERS, members, Type.WEAKREFERENCES); |
| r.commit(); |
| |
| assertEquals(1, Iterators.size(localGroup.getMembers())); |
| } |
| |
| @Test |
| public void testModifyMembersPropertyAdd() throws Exception { |
| localGroup.addMember(testUser); |
| // mark the local group as dynamic (setting the extid property) |
| String extid = new ExternalIdentityRef(localGroup.getID(), idp.getName()).getString(); |
| localGroup.setProperty(REP_EXTERNAL_ID, getValueFactory(r).createValue(extid)); |
| r.commit(); |
| |
| Tree groupTree = r.getTree(localGroup.getPath()); |
| Tree userTree = r.getTree(userManager.getAuthorizable(USER_ID).getPath()); |
| List<String> members = Lists.newArrayList(groupTree.getProperty(REP_MEMBERS).getValue(Type.STRINGS)); |
| members.add(userTree.getProperty(JCR_UUID).getValue(Type.STRING)); |
| groupTree.setProperty(REP_MEMBERS, members, Type.WEAKREFERENCES); |
| try { |
| r.commit(); |
| fail("CommitFailedException 77 expected."); |
| } catch (CommitFailedException e) { |
| assertEquals(77, e.getCode()); |
| } |
| } |
| |
| @Test |
| public void testCreateDynamicGroup() throws Exception { |
| ExternalIdentityRef ref = new ExternalIdentityRef("thirdGroup", idp.getName()); |
| Group gr = null; |
| try { |
| gr = userManager.createGroup(ref.getId(), new PrincipalImpl(ref.getId()), "some/intermediate/path"); |
| gr.setProperty(REP_EXTERNAL_ID, getValueFactory(r).createValue(ref.getString())); |
| r.commit(); |
| |
| root.refresh(); |
| assertNotNull(getUserManager(root).getAuthorizable(ref.getId())); |
| } finally { |
| if (gr != null) { |
| gr.remove(); |
| r.commit(); |
| } |
| } |
| } |
| |
| @Test |
| public void testCreateGroupIncompleteExtId() throws Exception { |
| Group gr = null; |
| try { |
| gr = userManager.createGroup("thirdGroup", new PrincipalImpl("thirdGroup"), "some/intermediate/path"); |
| gr.setProperty(REP_EXTERNAL_ID, getValueFactory(r).createValue("thirdGroup")); |
| r.commit(); |
| |
| root.refresh(); |
| assertNotNull(getUserManager(root).getAuthorizable("thirdGroup")); |
| } finally { |
| if (gr != null) { |
| gr.remove(); |
| r.commit(); |
| } |
| } |
| } |
| |
| @Test |
| public void testCreateGroupDifferentIDP() throws Exception { |
| ExternalIdentityRef ref = new ExternalIdentityRef("thirdGroup", "anotherIDP"); |
| Group gr = null; |
| try { |
| gr = userManager.createGroup(ref.getId(), new PrincipalImpl(ref.getId()), "some/intermediate/path"); |
| gr.setProperty(REP_EXTERNAL_ID, getValueFactory(r).createValue(ref.getString())); |
| r.commit(); |
| |
| root.refresh(); |
| assertNotNull(getUserManager(root).getAuthorizable(ref.getId())); |
| } finally { |
| if (gr != null) { |
| gr.remove(); |
| r.commit(); |
| } |
| } |
| } |
| |
| @Test |
| public void testCreateLocalGroup() throws Exception { |
| Group gr = null; |
| try { |
| String id = "testGroup"+ UUID.randomUUID(); |
| gr = userManager.createGroup(id); |
| r.commit(); |
| |
| root.refresh(); |
| assertNotNull(getUserManager(root).getAuthorizable(id)); |
| } finally { |
| if (gr != null) { |
| gr.remove(); |
| r.commit(); |
| } |
| } |
| } |
| } |