blob: f39b1ae3b5577c80eaf295750b0d262680a34526 [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;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
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.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.plugins.tree.TreeUtil;
import org.apache.jackrabbit.oak.spi.security.authentication.external.AbstractExternalAuthTest;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
import org.apache.jackrabbit.oak.spi.security.authentication.external.PrincipalNameResolver;
import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncedIdentity;
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.authentication.external.basic.DefaultSyncContext;
import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
import static org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider.ID_SECOND_USER;
import static org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider.ID_TEST_USER;
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.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.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class DynamicSyncContextTest extends AbstractExternalAuthTest {
static final String PREVIOUS_SYNCED_ID = "third";
static final long PREVIOUS_NESTING_DEPTH = Long.MAX_VALUE;
static final String GROUP_ID = "aaa";
Root r;
UserManager userManager;
ValueFactory valueFactory;
DynamicSyncContext syncContext;
// the external user identity that has been synchronized before dynamic membership is enabled.
ExternalUser previouslySyncedUser;
@Before
public void before() throws Exception {
super.before();
r = getSystemRoot();
createAutoMembershipGroups();
previouslySyncedUser = syncPriorToDynamicMembership();
userManager = getUserManager(r);
valueFactory = getValueFactory(r);
syncContext = new DynamicSyncContext(syncConfig, idp, userManager, valueFactory);
// inject user-configuration as well as sync-handler and sync-hander-mapping to have get dynamic-membership
// providers registered.
context.registerInjectActivateService(getUserConfiguration());
registerSyncHandler(syncConfigAsMap(), idp.getName());
}
@After
public void after() throws Exception {
try {
syncContext.close();
r.refresh();
} finally {
super.after();
}
}
private void createAutoMembershipGroups() throws RepositoryException {
DefaultSyncConfig sc = createSyncConfig();
UserManager um = getUserManager(r);
// create automembership groups
for (String id : Iterables.concat(sc.user().getAutoMembership(), sc.group().getAutoMembership())) {
um.createGroup(id);
}
}
/**
* Synchronized a separate user with DefaultSyncContext to test behavior for previously synchronized user/group
* with deep membership-nesting => all groups synched
*/
private ExternalUser syncPriorToDynamicMembership() throws Exception {
DefaultSyncConfig priorSyncConfig = createSyncConfig();
priorSyncConfig.user().setMembershipNestingDepth(PREVIOUS_NESTING_DEPTH);
String idpName = idp.getName();
TestIdentityProvider tidp = (TestIdentityProvider) idp;
tidp.addGroup(new TestIdentityProvider.TestGroup("ttt", idpName));
tidp.addGroup(new TestIdentityProvider.TestGroup("tt", idpName).withGroups("ttt"));
tidp.addGroup(new TestIdentityProvider.TestGroup("thirdGroup", idpName).withGroups("tt"));
tidp.addUser(new TestIdentityProvider.TestUser(PREVIOUS_SYNCED_ID, idpName).withGroups("thirdGroup"));
UserManager um = getUserManager(r);
DefaultSyncContext ctx = new DefaultSyncContext(priorSyncConfig, idp, um, getValueFactory(r));
ExternalUser previouslySyncedUser = idp.getUser(PREVIOUS_SYNCED_ID);
assertNotNull(previouslySyncedUser);
SyncResult result = ctx.sync(previouslySyncedUser);
assertSame(SyncResult.Status.ADD, result.getStatus());
ctx.close();
r.commit();
return previouslySyncedUser;
}
@Override
@NotNull
protected DefaultSyncConfig createSyncConfig() {
DefaultSyncConfig sc = super.createSyncConfig();
sc.user().setDynamicMembership(true);
return sc;
}
protected void sync(@NotNull ExternalIdentity externalIdentity, @NotNull SyncResult.Status expectedStatus) throws Exception {
SyncResult result = syncContext.sync(externalIdentity);
assertSame(expectedStatus, result.getStatus());
r.commit();
}
protected void assertDynamicMembership(@NotNull ExternalIdentity externalIdentity, long depth) throws Exception {
Authorizable a = userManager.getAuthorizable(externalIdentity.getId());
assertNotNull(a);
assertDynamicMembership(a, externalIdentity, depth);
}
private void assertDynamicMembership(@NotNull Authorizable a, @NotNull ExternalIdentity externalIdentity, long depth) throws Exception {
Value[] vs = a.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
Set<String> pNames = ImmutableList.copyOf(vs).stream().map(value -> {
try {
return value.getString();
} catch (RepositoryException e) {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toSet());
Set<String> expected = new HashSet<>();
collectGroupPrincipals(expected, externalIdentity.getDeclaredGroups(), depth);
assertEquals(expected, pNames);
}
private void collectGroupPrincipals(Set<String> pNames, @NotNull Iterable<ExternalIdentityRef> declaredGroups, long depth) throws ExternalIdentityException {
if (depth <= 0) {
return;
}
for (ExternalIdentityRef ref : declaredGroups) {
ExternalIdentity ei = idp.getIdentity(ref);
pNames.add(ei.getPrincipalName());
collectGroupPrincipals(pNames, ei.getDeclaredGroups(), depth - 1);
}
}
void assertSyncedMembership(@NotNull UserManager userManager,
@NotNull Authorizable a,
@NotNull ExternalIdentity externalIdentity) throws Exception {
assertSyncedMembership(userManager, a, externalIdentity, syncConfig.user().getMembershipNestingDepth());
}
void assertSyncedMembership(@NotNull UserManager userManager,
@NotNull Authorizable a,
@NotNull ExternalIdentity externalIdentity,
long membershipNestingDepth) throws Exception {
Iterable<ExternalIdentityRef> declaredGroupRefs = externalIdentity.getDeclaredGroups();
Set<ExternalIdentityRef> expectedGroupRefs = getExpectedSyncedGroupRefs(membershipNestingDepth, idp, externalIdentity);
for (ExternalIdentityRef ref : expectedGroupRefs) {
Group gr = userManager.getAuthorizable(ref.getId(), Group.class);
assertNotNull(gr);
assertTrue(gr.isMember(a));
assertTrue(Iterators.contains(a.memberOf(), gr));
if (Iterables.contains(declaredGroupRefs, ref)) {
assertTrue(gr.isDeclaredMember(a));
assertTrue(Iterators.contains(a.declaredMemberOf(), gr));
}
}
}
void assertDeclaredGroups(@NotNull ExternalUser externalUser) throws Exception {
Set<ExternalIdentityRef> expectedGroupRefs = getExpectedSyncedGroupRefs(syncConfig.user().getMembershipNestingDepth(), idp, externalUser);
for (ExternalIdentityRef ref : expectedGroupRefs) {
Authorizable gr = userManager.getAuthorizable(ref.getId());
if (syncConfig.group().getDynamicGroups()) {
assertNotNull(gr);
} else {
assertNull(gr);
}
}
}
static boolean hasStoredMembershipInformation(@NotNull Tree groupTree, @NotNull Tree memberTree) {
String ref = TreeUtil.getString(memberTree, JCR_UUID);
assertNotNull(ref);
if (containsMemberRef(groupTree, ref)) {
return true;
}
if (groupTree.hasChild(REP_MEMBERS_LIST)) {
for (Tree t : groupTree.getChild(REP_MEMBERS_LIST).getChildren()) {
if (containsMemberRef(t, ref)) {
return true;
}
}
}
return false;
}
private static boolean containsMemberRef(@NotNull Tree tree, @NotNull String ref) {
Iterable<String> memberRefs = TreeUtil.getStrings(tree, REP_MEMBERS);
return memberRefs != null && Iterables.contains(memberRefs, ref);
}
@Test(expected = IllegalArgumentException.class)
public void testSyncExternalIdentity() throws Exception {
syncContext.sync(new TestIdentityProvider.TestIdentity());
}
@Test
public void testSyncExternalUser() throws Exception {
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);
Authorizable a = userManager.getAuthorizable(USER_ID);
assertNotNull(a);
assertDeclaredGroups(externalUser);
}
@Test
public void testSyncExternalUserDepth0() throws Exception {
syncConfig.user().setMembershipNestingDepth(0);
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);
Tree tree = r.getTree(userManager.getAuthorizable(USER_ID).getPath());
PropertyState extPrincipalNames = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
assertNotNull(extPrincipalNames);
assertEquals(0, extPrincipalNames.count());
}
@Test
public void testSyncExternalUserDepth1() throws Exception {
syncConfig.user().setMembershipNestingDepth(1);
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);
Tree tree = r.getTree(userManager.getAuthorizable(USER_ID).getPath());
PropertyState extPrincipalNames = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
assertNotNull(extPrincipalNames);
Set<String> pNames = Sets.newHashSet(extPrincipalNames.getValue(Type.STRINGS));
for (ExternalIdentityRef ref : externalUser.getDeclaredGroups()) {
assertTrue(pNames.remove(idp.getIdentity(ref).getPrincipalName()));
}
assertTrue(pNames.isEmpty());
}
@Test
public void testSyncExternalUserDepthInfinite() throws Exception {
syncConfig.user().setMembershipNestingDepth(Long.MAX_VALUE);
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);
Tree tree = r.getTree(userManager.getAuthorizable(USER_ID).getPath());
PropertyState extPrincipalNames = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
assertNotNull(extPrincipalNames);
Set<String> pNames = Sets.newHashSet(extPrincipalNames.getValue(Type.STRINGS));
Set<String> expected = Sets.newHashSet();
collectGroupPrincipals(expected, externalUser.getDeclaredGroups(), Long.MAX_VALUE);
assertEquals(expected, pNames);
}
@Test
public void testSyncExternalUserGroupConflict() throws Exception {
ExternalUser externalUser = idp.getUser(USER_ID);
// create a local group that collides with the external group membership
// i.e. doesn't have an rep:externalId set
ExternalIdentity externalGroup = idp.getIdentity(externalUser.getDeclaredGroups().iterator().next());
userManager.createGroup(externalGroup.getId(), new PrincipalImpl(externalGroup.getPrincipalName()), null);
r.commit();
// sync the user with dynamic membership enabled
sync(externalUser, SyncResult.Status.ADD);
// retrieve rep:externalPrincipalNames
Tree tree = r.getTree(userManager.getAuthorizable(USER_ID).getPath());
PropertyState extPrincipalNames = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
assertNotNull(extPrincipalNames);
// the resulting rep:externalPrincipalNames must NOT contain the name of the colliding principal
Set<String> pNames = Sets.newHashSet(extPrincipalNames.getValue(Type.STRINGS));
assertFalse(pNames + " must not contain " + externalGroup.getPrincipalName(), pNames.contains(externalGroup.getPrincipalName()));
}
@Test
public void testSyncExternalUserGroupConflict2() throws Exception {
ExternalUser externalUser = idp.getUser(USER_ID);
// create a local group that collides with the external group membership
// i.e. belongs to a different IDP
ExternalIdentityRef ref = externalUser.getDeclaredGroups().iterator().next();
ExternalIdentity externalGroup = idp.getIdentity(ref);
Group g = userManager.createGroup(externalGroup.getId(), new PrincipalImpl(externalGroup.getPrincipalName()), null);
g.setProperty(REP_EXTERNAL_ID, getValueFactory().createValue(new ExternalIdentityRef(ref.getId(), ref.getProviderName()+"_mod").getString()));
r.commit();
// sync the user with dynamic membership enabled
sync(externalUser, SyncResult.Status.ADD);
// retrieve rep:externalPrincipalNames
Tree tree = r.getTree(userManager.getAuthorizable(USER_ID).getPath());
PropertyState extPrincipalNames = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
assertNotNull(extPrincipalNames);
// the resulting rep:externalPrincipalNames must NOT contain the name of the colliding principal
Set<String> pNames = Sets.newHashSet(extPrincipalNames.getValue(Type.STRINGS));
assertFalse(pNames + " must not contain " + externalGroup.getPrincipalName(), pNames.contains(externalGroup.getPrincipalName()));
}
@Test
public void testSyncExternalUserGroupConflictWithUser() throws Exception {
ExternalUser externalUser = idp.getUser(USER_ID);
// create a local user that collides with the first external group ref
ExternalIdentityRef ref = externalUser.getDeclaredGroups().iterator().next();
ExternalIdentity externalGroup = idp.getIdentity(ref);
User collision = userManager.createUser(externalGroup.getId(), null, new PrincipalImpl(externalGroup.getPrincipalName()), null);
r.commit();
// sync the user with dynamic membership enabled
sync(externalUser, SyncResult.Status.ADD);
// retrieve rep:externalPrincipalNames
Tree tree = r.getTree(userManager.getAuthorizable(USER_ID).getPath());
PropertyState extPrincipalNames = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
assertNotNull(extPrincipalNames);
// the resulting rep:externalPrincipalNames must NOT contain the name of the colliding principal
Set<String> pNames = Sets.newHashSet(extPrincipalNames.getValue(Type.STRINGS));
assertFalse(pNames + " must not contain " + externalGroup.getPrincipalName(), pNames.contains(externalGroup.getPrincipalName()));
}
@Test
public void testSyncExternalUserExistingGroups() throws Exception {
// verify group membership of the previously synced user
Authorizable a = userManager.getAuthorizable(previouslySyncedUser.getId());
assertSyncedMembership(userManager, a, previouslySyncedUser, Long.MAX_VALUE);
// resync the previously synced user with dynamic-membership enabled.
syncContext.setForceUserSync(true);
syncConfig.user().setMembershipExpirationTime(-1);
syncContext.sync(previouslySyncedUser);
Tree t = r.getTree(a.getPath());
assertFalse(t.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
assertSyncedMembership(userManager, a, previouslySyncedUser);
}
@Test
public void testSyncExternalGroup() throws Exception {
ExternalGroup gr = idp.getGroup(GROUP_ID);
syncContext.sync(gr);
assertNull(userManager.getAuthorizable(gr.getId()));
assertFalse(r.hasPendingChanges());
}
@Test
public void testSyncExternalGroupVerifyStatus() throws Exception {
ExternalGroup gr = idp.getGroup(GROUP_ID);
SyncResult result = syncContext.sync(gr);
SyncResult.Status expectedStatus = (syncConfig.group().getDynamicGroups()) ? SyncResult.Status.ADD : SyncResult.Status.NOP;
assertEquals(expectedStatus, result.getStatus());
result = syncContext.sync(gr);
assertEquals(SyncResult.Status.NOP, result.getStatus());
syncContext.setForceGroupSync(true);
result = syncContext.sync(gr);
expectedStatus = (syncConfig.group().getDynamicGroups()) ? SyncResult.Status.UPDATE : SyncResult.Status.NOP;
assertEquals(expectedStatus, result.getStatus());
}
@Test
public void testSyncExternalGroupExisting() throws Exception {
// create an external external group that already has been synced into the repo
ExternalGroup externalGroup = idp.getGroup(previouslySyncedUser.getDeclaredGroups().iterator().next().getId());
assertNotNull(externalGroup);
// synchronizing using DynamicSyncContext must update the existing group
syncContext.setForceGroupSync(true);
SyncResult result = syncContext.sync(externalGroup);
assertSame(SyncResult.Status.UPDATE, result.getStatus());
}
@Test
public void testSyncForeignExternalGroup() throws Exception {
ExternalGroup foreign = new TestIdentityProvider.ForeignExternalGroup();
SyncResult res = syncContext.sync(foreign);
assertNotNull(res);
assertSame(SyncResult.Status.FOREIGN, res.getStatus());
// expect {@code SyncedIdentity} in accordance with {@code sync(String userId)},
// where the authorizable is found to be linked to a different IDP.
SyncedIdentity si = res.getIdentity();
assertNotNull(si);
assertEquals(foreign.getId(), si.getId());
ExternalIdentityRef ref = si.getExternalIdRef();
assertNotNull(ref);
assertEquals(foreign.getExternalId(), ref);
assertTrue(si.isGroup());
assertEquals(-1, si.lastSynced());
assertFalse(r.hasPendingChanges());
}
@Test
public void testSyncExternalGroupRepositoryException() throws Exception {
Exception ex = new RepositoryException();
UserManager um = mock(UserManager.class);
when(um.getAuthorizable(any(String.class))).thenThrow(ex);
DynamicSyncContext ctx = new DynamicSyncContext(syncConfig, idp, um, valueFactory);
try {
ctx.sync(idp.getGroup(GROUP_ID));
fail();
} catch (SyncException e) {
assertEquals(ex, e.getCause());
}
}
@Test
public void testSyncUserByIdUpdate() throws Exception {
ExternalIdentity externalId = idp.getUser(ID_SECOND_USER);
Authorizable a = userManager.createUser(externalId.getId(), null);
a.setProperty(DefaultSyncContext.REP_EXTERNAL_ID, valueFactory.createValue(externalId.getExternalId().getString()));
syncContext.setForceUserSync(true);
SyncResult result = syncContext.sync(externalId.getId());
assertEquals(SyncResult.Status.UPDATE, result.getStatus());
Tree t = r.getTree(a.getPath());
assertTrue(t.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
}
@Test
public void testPreviouslySyncedIdentities() throws Exception {
Authorizable user = userManager.getAuthorizable(PREVIOUS_SYNCED_ID);
assertNotNull(user);
assertFalse(user.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
assertSyncedMembership(userManager, user, previouslySyncedUser, PREVIOUS_NESTING_DEPTH);
}
@Test
public void testSyncUserIdExistingGroupsMembershipNotExpired() throws Exception {
// make sure membership is not expired
long previousExpTime = syncConfig.user().getMembershipExpirationTime();
DefaultSyncConfig.User uc = syncConfig.user();
try {
uc.setMembershipExpirationTime(Long.MAX_VALUE);
syncContext.setForceUserSync(true);
syncContext.sync(previouslySyncedUser.getId());
Authorizable a = userManager.getAuthorizable(PREVIOUS_SYNCED_ID);
Tree t = r.getTree(a.getPath());
assertFalse(t.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
assertSyncedMembership(userManager, a, previouslySyncedUser);
} finally {
uc.setMembershipExpirationTime(previousExpTime);
}
}
@Test
public void testSyncUserIdExistingGroups() throws Exception {
// mark membership information as expired
long previousExpTime = syncConfig.user().getMembershipExpirationTime();
DefaultSyncConfig.User uc = syncConfig.user();
try {
uc.setMembershipExpirationTime(-1);
syncContext.setForceUserSync(true);
syncContext.sync(previouslySyncedUser.getId());
Authorizable a = userManager.getAuthorizable(PREVIOUS_SYNCED_ID);
Tree t = r.getTree(a.getPath());
boolean expectedMigration = (uc.getEnforceDynamicMembership() || syncConfig.group().getDynamicGroups());
if (expectedMigration) {
assertTrue(t.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
int expSize = getExpectedSyncedGroupRefs(uc.getMembershipNestingDepth(), idp, previouslySyncedUser).size();
assertEquals(expSize, t.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES).count());
} else {
assertFalse(t.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
}
if (uc.getEnforceDynamicMembership() && !syncConfig.group().getDynamicGroups()) {
for (String id : getExpectedSyncedGroupIds(uc.getMembershipNestingDepth(), idp, previouslySyncedUser)) {
assertNull(userManager.getAuthorizable(id));
}
} else {
assertSyncedMembership(userManager, a, previouslySyncedUser);
}
} finally {
uc.setMembershipExpirationTime(previousExpTime);
}
}
@Test
public void testSyncMembershipWithNesting() throws Exception {
long nesting = 1;
syncConfig.user().setMembershipNestingDepth(nesting);
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
assertDynamicMembership(externalUser, nesting);
// verify that the membership is always reflected in the rep:externalPrincipalNames property
// 1. membership nesting = -1
nesting = -1;
syncContext.syncMembership(externalUser, a, nesting);
assertDynamicMembership(a, externalUser, nesting);
// 2. membership nesting is > 0
nesting = Long.MAX_VALUE;
syncContext.syncMembership(externalUser, a, nesting);
assertDynamicMembership(a, externalUser, nesting);
}
@Test
public void testSyncMembershipWithChangedGroups() throws Exception {
long nesting = 1;
syncConfig.user().setMembershipNestingDepth(nesting);
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
assertDynamicMembership(a, externalUser, nesting);
// sync user with modified membership => must be reflected
// 1. empty set of declared groups
ExternalUser mod = new TestUserWithGroupRefs(externalUser, ImmutableSet.of());
syncContext.syncMembership(mod, a, nesting);
assertDynamicMembership(a, mod, nesting);
// 2. set with different groups than defined on IDP
mod = new TestUserWithGroupRefs(externalUser, ImmutableSet.of(
idp.getGroup("a").getExternalId(),
idp.getGroup("aa").getExternalId(),
idp.getGroup("secondGroup").getExternalId()));
syncContext.syncMembership(mod, a, nesting);
assertDynamicMembership(a, mod, nesting);
}
@Test
public void testSyncMembershipWithEmptyExistingGroups() throws Exception {
long nesting = syncConfig.user().getMembershipNestingDepth();
Authorizable a = userManager.getAuthorizable(PREVIOUS_SYNCED_ID);
// sync user with modified membership => must be reflected
// 1. empty set of declared groups
ExternalUser mod = new TestUserWithGroupRefs(previouslySyncedUser, ImmutableSet.of());
syncContext.syncMembership(mod, a, nesting);
assertSyncedMembership(userManager, a, mod, nesting);
}
@Test
public void testSyncMembershipWithChangedExistingGroups() throws Exception {
long nesting = syncConfig.user().getMembershipNestingDepth();
Authorizable a = userManager.getAuthorizable(PREVIOUS_SYNCED_ID);
// sync user with modified membership => must be reflected
// 2. set with different groups that defined on IDP
ExternalUser mod = new TestUserWithGroupRefs(previouslySyncedUser, ImmutableSet.of(
idp.getGroup("a").getExternalId(),
idp.getGroup("aa").getExternalId(),
idp.getGroup("secondGroup").getExternalId()));
syncContext.syncMembership(mod, a, nesting);
// persist changes to have the modified membership being reflected through assertions that use queries
r.commit();
assertSyncedMembership(userManager, a, mod);
}
@Test
public void testSyncMembershipForExternalGroup() throws Exception {
// previously synced 'third-group' has declaredGroups (i.e. nested membership)
ExternalGroup externalGroup = idp.getGroup(previouslySyncedUser.getDeclaredGroups().iterator().next().getId());
Authorizable gr = userManager.getAuthorizable(externalGroup.getId());
syncContext.syncMembership(externalGroup, gr, 1);
assertFalse(gr.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
assertFalse(r.hasPendingChanges());
}
@Test
public void testSyncMembershipWithForeignGroups() throws Exception {
TestIdentityProvider.TestUser testuser = (TestIdentityProvider.TestUser) idp.getUser(ID_TEST_USER);
Set<ExternalIdentityRef> sameIdpGroups = getExpectedSyncedGroupRefs(syncConfig.user().getMembershipNestingDepth(), idp, testuser);
TestIdentityProvider.ForeignExternalGroup foreignGroup = new TestIdentityProvider.ForeignExternalGroup();
testuser.withGroups(foreignGroup.getExternalId());
assertNotEquals(sameIdpGroups, testuser.getDeclaredGroups());
sync(testuser, SyncResult.Status.ADD);
Authorizable a = userManager.getAuthorizable(ID_TEST_USER);
assertTrue(a.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
Value[] extPrincipalNames = a.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
assertEquals(Iterables.size(sameIdpGroups), extPrincipalNames.length);
for (Value v : extPrincipalNames) {
assertNotEquals(foreignGroup.getPrincipalName(), v.getString());
}
}
@Test
public void testSyncMembershipWithUserRef() throws Exception {
TestIdentityProvider.TestUser testuser = (TestIdentityProvider.TestUser) idp.getUser(ID_TEST_USER);
Set<ExternalIdentityRef> groupRefs = getExpectedSyncedGroupRefs(syncConfig.user().getMembershipNestingDepth(), idp, testuser);
ExternalUser second = idp.getUser(ID_SECOND_USER);
testuser.withGroups(second.getExternalId());
assertFalse(Iterables.elementsEqual(groupRefs, testuser.getDeclaredGroups()));
sync(testuser, SyncResult.Status.ADD);
Authorizable a = userManager.getAuthorizable(ID_TEST_USER);
assertTrue(a.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
Value[] extPrincipalNames = a.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
if (idp instanceof PrincipalNameResolver) {
// with IDP implementing PrincipalNameResolver the extra verification for all member-refs being groups is omitted.
assertDynamicMembership(testuser, 1);
} else {
assertEquals(Iterables.size(groupRefs), extPrincipalNames.length);
for (Value v : extPrincipalNames) {
assertNotEquals(second.getPrincipalName(), v.getString());
}
}
}
@Test
public void testSyncMembershipDeclaredGroupsFails() throws Exception {
ExternalIdentityProvider extIdp = spy(idp);
ExternalUser externalUser = spy(extIdp.getUser(TestIdentityProvider.ID_TEST_USER));
syncContext.sync(externalUser);
clearInvocations(extIdp);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
assertNotNull(a);
when(externalUser.getDeclaredGroups()).thenThrow(new ExternalIdentityException());
syncContext.syncMembership(externalUser, a, 1);
verify(extIdp, never()).getIdentity(any(ExternalIdentityRef.class));
}
@Test
public void testAutoMembership() throws Exception {
Group gr = userManager.createGroup("group" + UUID.randomUUID());
r.commit();
syncConfig.user().setAutoMembership(gr.getID(), "non-existing-group");
SyncResult result = syncContext.sync(idp.getUser(USER_ID));
assertSame(SyncResult.Status.ADD, result.getStatus());
User u = userManager.getAuthorizable(USER_ID, User.class);
assertFalse(gr.isDeclaredMember(u));
assertFalse(gr.isMember(u));
}
@Test
public void testConvertToDynamicMembershipAlreadyDynamic() throws Exception {
syncConfig.user().setMembershipNestingDepth(1);
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);
User user = userManager.getAuthorizable(externalUser.getId(), User.class);
assertNotNull(user);
assertFalse(syncContext.convertToDynamicMembership(user));
}
@Test
public void testConvertToDynamicMembership() throws Exception {
User user = userManager.getAuthorizable(PREVIOUS_SYNCED_ID, User.class);
assertNotNull(user);
assertFalse(user.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
assertTrue(syncContext.convertToDynamicMembership(user));
assertTrue(user.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES));
assertDeclaredGroups(previouslySyncedUser);
}
@Test
public void testConvertToDynamicMembershipForGroup() throws Exception {
Authorizable gr = when(mock(Authorizable.class).isGroup()).thenReturn(true).getMock();
assertFalse(syncContext.convertToDynamicMembership(gr));
}
static final class TestUserWithGroupRefs extends TestIdentityProvider.TestIdentity implements ExternalUser {
private final Iterable<ExternalIdentityRef> declaredGroupRefs;
TestUserWithGroupRefs(@NotNull ExternalUser base, @NotNull Iterable<ExternalIdentityRef> declaredGroupRefs) {
super(base);
this.declaredGroupRefs = declaredGroupRefs;
}
public String getPassword() {
return "";
}
@NotNull
@Override
public Iterable<ExternalIdentityRef> getDeclaredGroups() {
return declaredGroupRefs;
}
}
}