blob: ef8eba290f00090c9c5d5140c7786f73a1dfeb42 [file] [log] [blame]
/*
* 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();
}
}
}
}