blob: 6e874dc66a2d997aa7bbe6ed939ac0592b63243b [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.basic;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.jcr.Binary;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
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.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.ExternalIdentityRef;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
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.impl.ExternalIdentityConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class DefaultSyncContextTest extends AbstractExternalAuthTest {
private UserManager userManager;
private ValueFactory valueFactory;
private DefaultSyncContext syncCtx;
@Before
public void before() throws Exception {
super.before();
userManager = getUserManager(root);
valueFactory = getValueFactory();
syncCtx = new DefaultSyncContext(syncConfig, idp, userManager, valueFactory);
}
@After
public void after() throws Exception {
try {
syncCtx.close();
root.refresh();
} finally {
super.after();
}
}
@Override
protected DefaultSyncConfig createSyncConfig() {
return new DefaultSyncConfig();
}
private Group createTestGroup() throws Exception {
return userManager.createGroup("group" + UUID.randomUUID());
}
/**
* Test utility method to synchronize the given identity into the repository.
* This is intended to simplify those tests that require a given user/group
* to be synchronized before executing the test.
*
* @param externalIdentity The external identity to be synchronized.
* @throws Exception
*/
private void sync(@NotNull ExternalIdentity externalIdentity) throws Exception {
SyncResult result = syncCtx.sync(externalIdentity);
assertSame(SyncResult.Status.ADD, result.getStatus());
root.commit();
}
private void setExternalID(@NotNull Authorizable authorizable, @Nullable String idpName) throws RepositoryException {
authorizable.setProperty(DefaultSyncContext.REP_EXTERNAL_ID, valueFactory.createValue(authorizable.getID() + ';' + idpName));
}
@Test
public void testCreateSyncedIdentityNull() throws Exception {
assertNull(DefaultSyncContext.createSyncedIdentity(null));
}
@Test
public void testCreateSyncedIdentityLocalGroup() throws Exception {
Group gr = createTestGroup();
SyncedIdentity si = DefaultSyncContext.createSyncedIdentity(gr);
assertNotNull(si);
assertEquals(gr.getID(), si.getId());
assertNull(si.getExternalIdRef());
assertTrue(si.isGroup());
assertEquals(-1, si.lastSynced());
}
@Test
public void testCreateSyncedIdentityLocalUser() throws Exception {
User u = getTestUser();
SyncedIdentity si = DefaultSyncContext.createSyncedIdentity(u);
assertNotNull(si);
assertEquals(u.getID(), si.getId());
assertNull(si.getExternalIdRef());
assertFalse(si.isGroup());
assertEquals(-1, si.lastSynced());
}
@Test
public void testCreateSyncedIdentitySyncedGroup() throws Exception {
ExternalIdentity externalGroup = idp.listGroups().next();
sync(externalGroup);
Authorizable a = userManager.getAuthorizable(externalGroup.getId());
SyncedIdentity si = DefaultSyncContext.createSyncedIdentity(a);
assertNotNull(si);
assertEquals(a.getID(), si.getId());
assertNotNull(si.getExternalIdRef());
assertTrue(si.isGroup());
assertEquals(syncCtx.now, si.lastSynced());
}
@Test
public void testCreateSyncedIdentitySyncedUser() throws Exception {
ExternalIdentity externalUser = idp.listUsers().next();
sync(externalUser);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
SyncedIdentity si = DefaultSyncContext.createSyncedIdentity(a);
assertNotNull(si);
assertEquals(a.getID(), si.getId());
assertNotNull(si.getExternalIdRef());
assertFalse(si.isGroup());
assertEquals(syncCtx.now, si.lastSynced());
}
@Test
public void testCreateSyncedIdentityEmptyLastSyncedProperty() throws Exception {
Group gr = createTestGroup();
gr.setProperty(DefaultSyncContext.REP_LAST_SYNCED, new Value[0]);
SyncedIdentity si = DefaultSyncContext.createSyncedIdentity(gr);
assertNotNull(si);
assertEquals(-1, si.lastSynced());
}
@Test
public void testGetIdentityRefNull() throws Exception {
assertNull(DefaultSyncContext.getIdentityRef(null));
}
@Test
public void testGetIdentityRefLocalGroup() throws Exception {
assertNull(DefaultSyncContext.getIdentityRef(createTestGroup()));
}
@Test
public void testGetIdentityRefLocalUser() throws Exception {
assertNull(DefaultSyncContext.getIdentityRef(getTestUser()));
}
@Test
public void testGetIdentityRefSyncGroup() throws Exception {
ExternalIdentity externalGroup = idp.listGroups().next();
sync(externalGroup);
ExternalIdentityRef ref = DefaultSyncContext.getIdentityRef(userManager.getAuthorizable(externalGroup.getId()));
assertNotNull(ref);
assertEquals(externalGroup.getExternalId(), ref);
}
@Test
public void testGetIdentityRefSyncUser() throws Exception {
ExternalIdentity externalUser = idp.listUsers().next();
sync(externalUser);
ExternalIdentityRef ref = DefaultSyncContext.getIdentityRef(userManager.getAuthorizable(externalUser.getId()));
assertNotNull(ref);
assertEquals(externalUser.getExternalId(), ref);
}
@Test
public void testGetIdentityRefEmptyMvProperty() throws Exception {
Group gr = createTestGroup();
// NOTE: making rep:externalId a multivalue property without any value
// not committing the changes as this prop is expected to become
// protected to prevent unintentional or malicious modification.
gr.setProperty(DefaultSyncContext.REP_EXTERNAL_ID, new Value[0]);
ExternalIdentityRef ref = DefaultSyncContext.getIdentityRef(gr);
assertNull(ref);
}
@Test
public void testIsKeepMissing() {
assertFalse(syncCtx.isKeepMissing());
assertSame(syncCtx, syncCtx.setKeepMissing(true));
assertTrue(syncCtx.isKeepMissing());
assertSame(syncCtx, syncCtx.setKeepMissing(false));
assertFalse(syncCtx.isKeepMissing());
}
@Test
public void testIsForceUserSync() {
assertFalse(syncCtx.isForceUserSync());
assertSame(syncCtx, syncCtx.setForceUserSync(true));
assertTrue(syncCtx.isForceUserSync());
assertSame(syncCtx, syncCtx.setForceUserSync(false));
assertFalse(syncCtx.isForceUserSync());
}
@Test
public void testIsForceGroupSync() {
assertFalse(syncCtx.isForceGroupSync());
assertSame(syncCtx, syncCtx.setForceGroupSync(true));
assertTrue(syncCtx.isForceGroupSync());
assertSame(syncCtx, syncCtx.setForceGroupSync(false));
assertFalse(syncCtx.isForceGroupSync());
}
@Test(expected = IllegalArgumentException.class)
public void testSyncInvalidExternalIdentity() throws Exception {
syncCtx.sync(new TestIdentityProvider.TestIdentity());
}
@Test
public void testSyncExternalUser() throws Exception {
ExternalUser user = idp.listUsers().next();
assertNotNull(user);
SyncResult result = syncCtx.sync(user);
assertEquals(SyncResult.Status.ADD, result.getStatus());
result = syncCtx.sync(user);
assertEquals(SyncResult.Status.NOP, result.getStatus());
syncCtx.setForceUserSync(true);
result = syncCtx.sync(user);
assertEquals(SyncResult.Status.UPDATE, result.getStatus());
}
@Test
public void testSyncExternalGroup() throws Exception {
ExternalGroup gr = idp.listGroups().next();
assertNotNull(gr);
SyncResult result = syncCtx.sync(gr);
assertEquals(SyncResult.Status.ADD, result.getStatus());
result = syncCtx.sync(gr);
assertEquals(SyncResult.Status.NOP, result.getStatus());
syncCtx.setForceGroupSync(true);
result = syncCtx.sync(gr);
assertEquals(SyncResult.Status.UPDATE, result.getStatus());
}
@Test
public void testSyncForeignExternalUser() throws Exception {
ExternalIdentity foreign = new TestIdentityProvider.ForeignExternalUser();
SyncResult res = syncCtx.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);
assertFalse(si.isGroup());
assertEquals(-1, si.lastSynced());
assertFalse(root.hasPendingChanges());
}
@Test
public void testSyncForeignExternalGroup() throws Exception {
ExternalIdentity foreign = new TestIdentityProvider.ForeignExternalGroup();
SyncResult res = syncCtx.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(root.hasPendingChanges());
}
@Test
public void testSyncExternalForeignLocalUser() throws Exception {
ExternalUser external = idp.listUsers().next();
syncCtx.sync(external);
User u = userManager.getAuthorizable(external.getId(), User.class);
setExternalID(u, "differentIDP");
SyncResult result = syncCtx.sync(external);
assertEquals(SyncResult.Status.FOREIGN, result.getStatus());
SyncedIdentity si = result.getIdentity();
assertNotNull(si);
assertEquals(external.getExternalId(), si.getExternalIdRef());
}
@Test
public void testSyncExternalToForeignLocalGroup() throws Exception {
ExternalGroup external = idp.listGroups().next();
syncCtx.sync(external);
Group gr = userManager.getAuthorizable(external.getId(), Group.class);
setExternalID(gr, "differentIDP");
SyncResult result = syncCtx.sync(external);
assertEquals(SyncResult.Status.FOREIGN, result.getStatus());
SyncedIdentity si = result.getIdentity();
assertNotNull(si);
assertEquals(external.getExternalId(), si.getExternalIdRef());
}
@Test
public void testSyncExternalToExistingLocalUser() throws Exception {
ExternalUser external = idp.listUsers().next();
syncCtx.sync(external);
User u = userManager.getAuthorizable(external.getId(), User.class);
u.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
SyncResult result = syncCtx.sync(external);
assertEquals(SyncResult.Status.FOREIGN, result.getStatus());
SyncedIdentity si = result.getIdentity();
assertNotNull(si);
assertEquals(external.getExternalId(), si.getExternalIdRef());
}
@Test
public void testSyncExternalToExistingLocalGroup() throws Exception {
ExternalGroup external = idp.listGroups().next();
syncCtx.sync(external);
Group gr = userManager.getAuthorizable(external.getId(), Group.class);
gr.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
SyncResult result = syncCtx.sync(external);
assertEquals(SyncResult.Status.FOREIGN, result.getStatus());
SyncedIdentity si = result.getIdentity();
assertNotNull(si);
assertEquals(external.getExternalId(), si.getExternalIdRef());
}
@Test
public void testSyncUserById() throws Exception {
ExternalIdentity externalId = idp.listUsers().next();
// no initial sync -> sync-by-id doesn't succeed
SyncResult result = syncCtx.sync(externalId.getId());
assertEquals(SyncResult.Status.NO_SUCH_AUTHORIZABLE, result.getStatus());
// force sync
syncCtx.sync(externalId);
// try again
syncCtx.setForceUserSync(true);
result = syncCtx.sync(externalId.getId());
assertEquals(SyncResult.Status.UPDATE, result.getStatus());
}
@Test
public void testSyncRemovedUserById() throws Exception {
// mark a regular repo user as external user from the test IDP
User u = userManager.createUser("test" + UUID.randomUUID(), null);
String userId = u.getID();
setExternalID(u, idp.getName());
// test sync with 'keepmissing' = true
syncCtx.setKeepMissing(true);
SyncResult result = syncCtx.sync(userId);
assertEquals(SyncResult.Status.MISSING, result.getStatus());
assertNotNull(userManager.getAuthorizable(userId));
// test sync with 'keepmissing' = false
syncCtx.setKeepMissing(false);
result = syncCtx.sync(userId);
assertEquals(SyncResult.Status.DELETE, result.getStatus());
assertNull(userManager.getAuthorizable(userId));
}
@Test
public void testSyncDisabledUserById() throws Exception {
// configure to disable missing users
syncConfig.user().setDisableMissing(true);
// mark a regular repo user as external user from the test IDP
User u = userManager.createUser("test" + UUID.randomUUID(), null);
String userId = u.getID();
setExternalID(u, idp.getName());
// test sync with 'keepmissing' = true
syncCtx.setKeepMissing(true);
SyncResult result = syncCtx.sync(userId);
assertEquals(SyncResult.Status.MISSING, result.getStatus());
assertNotNull(userManager.getAuthorizable(userId));
// test sync with 'keepmissing' = false
syncCtx.setKeepMissing(false);
result = syncCtx.sync(userId);
assertEquals(SyncResult.Status.DISABLE, result.getStatus());
Authorizable authorizable = userManager.getAuthorizable(userId);
assertNotNull(authorizable);
assertTrue(((User)authorizable).isDisabled());
// add external user back
addIDPUser(userId);
result = syncCtx.sync(userId);
assertEquals(SyncResult.Status.ENABLE, result.getStatus());
assertNotNull(userManager.getAuthorizable(userId));
assertFalse(((User)authorizable).isDisabled());
}
@Test
public void testSyncDoesNotEnableUsers() throws Exception {
// configure to remove missing users, check that sync does not mess with disabled status
syncConfig.user().setDisableMissing(false);
// test sync with 'keepmissing' = false
syncCtx.setKeepMissing(false);
ExternalUser user = idp.listUsers().next();
assertNotNull(user);
SyncResult result = syncCtx.sync(user);
assertEquals(SyncResult.Status.ADD, result.getStatus());
Authorizable authorizable = userManager.getAuthorizable(user.getId());
assertTrue(authorizable instanceof User);
User u = (User) authorizable;
// disable user
u.disable("disabled locally");
root.commit();
// sync
syncCtx.setForceUserSync(true);
result = syncCtx.sync(user.getId());
assertEquals(SyncResult.Status.UPDATE, result.getStatus());
authorizable = userManager.getAuthorizable(user.getId());
assertNotNull(authorizable);
assertTrue(authorizable instanceof User);
assertTrue(((User)authorizable).isDisabled());
}
@Test
public void testSyncGroupById() throws Exception {
ExternalIdentity externalId = idp.listGroups().next();
// no initial sync -> sync-by-id doesn't succeed
SyncResult result = syncCtx.sync(externalId.getId());
assertEquals(SyncResult.Status.NO_SUCH_AUTHORIZABLE, result.getStatus());
// force sync
syncCtx.sync(externalId);
// try again
syncCtx.setForceGroupSync(true);
result = syncCtx.sync(externalId.getId());
assertEquals(SyncResult.Status.UPDATE, result.getStatus());
}
@Test
public void testSyncRemovedGroupById() throws Exception {
// mark a regular repo user as external user from the test IDP
Group gr = createTestGroup();
String groupId = gr.getID();
setExternalID(gr, idp.getName());
// test sync with 'keepmissing' = true
syncCtx.setKeepMissing(true);
SyncResult result = syncCtx.sync(groupId);
assertEquals(SyncResult.Status.MISSING, result.getStatus());
assertNotNull(userManager.getAuthorizable(groupId));
// test sync with 'keepmissing' = false
syncCtx.setKeepMissing(false);
result = syncCtx.sync(groupId);
assertEquals(SyncResult.Status.DELETE, result.getStatus());
assertNull(userManager.getAuthorizable(groupId));
}
@Test
public void testSyncRemovedGroupWithMembers() throws Exception {
// mark a regular repo user as external user from the test IDP
Group gr = createTestGroup();
gr.addMember(getTestUser());
String groupId = gr.getID();
setExternalID(gr, idp.getName());
// test sync with 'keepmissing' = true
syncCtx.setKeepMissing(true);
SyncResult result = syncCtx.sync(groupId);
assertEquals(SyncResult.Status.NOP, result.getStatus());
assertNotNull(userManager.getAuthorizable(groupId));
// test sync with 'keepmissing' = false
syncCtx.setKeepMissing(false);
result = syncCtx.sync(groupId);
assertEquals(SyncResult.Status.NOP, result.getStatus());
assertNotNull(userManager.getAuthorizable(groupId));
}
@Test
public void testSyncByForeignUserId() throws Exception {
SyncResult result = syncCtx.sync(getTestUser().getID());
assertEquals(SyncResult.Status.FOREIGN, result.getStatus());
SyncedIdentity si = result.getIdentity();
assertNotNull(si);
assertNull(si.getExternalIdRef());
assertFalse(si.isGroup());
}
@Test
public void testSyncByForeignGroupId() throws Exception {
SyncResult result = syncCtx.sync(createTestGroup().getID());
assertEquals(SyncResult.Status.FOREIGN, result.getStatus());
SyncedIdentity si = result.getIdentity();
assertNotNull(si);
assertNull(si.getExternalIdRef());
assertTrue(si.isGroup());
}
@Test
public void testSyncByForeignId2() throws Exception {
User u = userManager.getAuthorizable(getTestUser().getID(), User.class);
setExternalID(u, "differentIDP");
SyncResult result = syncCtx.sync(u.getID());
assertEquals(SyncResult.Status.FOREIGN, result.getStatus());
SyncedIdentity si = result.getIdentity();
assertNotNull(si);
assertEquals(DefaultSyncContext.getIdentityRef(u), si.getExternalIdRef());
}
@Test(expected = SyncException.class)
public void testSyncByIdUsingExceptionId() throws Exception {
Group gr = userManager.createGroup(TestIdentityProvider.ID_EXCEPTION);
setExternalID(gr, idp.getName());
syncCtx.sync(TestIdentityProvider.ID_EXCEPTION);
}
@Test
public void testSyncAutoMembership() throws Exception {
Group gr = createTestGroup();
syncConfig.user().setAutoMembership(gr.getID());
SyncResult result = syncCtx.sync(idp.listUsers().next());
assertEquals(SyncResult.Status.ADD, result.getStatus());
Authorizable a = userManager.getAuthorizable(result.getIdentity().getId());
assertTrue(gr.isDeclaredMember(a));
}
@Test
public void testSyncAutoMembershipListsNonExistingGroup() throws Exception {
syncConfig.user().setAutoMembership("nonExistingGroup");
SyncResult result = syncCtx.sync(idp.listUsers().next());
assertEquals(SyncResult.Status.ADD, result.getStatus());
}
@Test
public void testSyncAutoMembershipListsUser() throws Exception {
// set auto-membership config to point to a user instead a group
syncConfig.user().setAutoMembership(getTestUser().getID());
syncCtx.sync(idp.listUsers().next());
}
@Test
public void testLostMembership() throws Exception {
// create a group in the repository which is marked as being external
// and associated with the test-IDP to setup the situation that a
// repository group is no longer listed in the IDP.
Group gr = createTestGroup();
setExternalID(gr, idp.getName());
// sync an external user from the IDP into the repo and make it member
// of the test group
SyncResult result = syncCtx.sync(idp.listUsers().next());
User user = userManager.getAuthorizable(result.getIdentity().getId(), User.class);
gr.addMember(user);
root.commit();
// enforce synchronization of the user and it's group membership
syncCtx.setForceUserSync(true);
syncConfig.user().setMembershipExpirationTime(-1);
// 1. membership nesting is < 0 => membership not synchronized
syncConfig.user().setMembershipNestingDepth(-1);
syncCtx.sync(user.getID()).getStatus();
assertTrue(gr.isDeclaredMember(user));
// 2. membership nesting is > 0 => membership gets synchronized
syncConfig.user().setMembershipNestingDepth(1);
assertEquals(SyncResult.Status.UPDATE, syncCtx.sync(user.getID()).getStatus());
assertFalse(gr.isDeclaredMember(user));
}
@Test
public void testLostMembershipDifferentIDP() throws Exception {
// create a group in the repository which is marked as being external
// and associated with another IPD.
Group gr = createTestGroup();
setExternalID(gr, "differentIDP");
// sync an external user from the IDP into the repo and make it member
// of the test group
SyncResult result = syncCtx.sync(idp.listUsers().next());
User user = userManager.getAuthorizable(result.getIdentity().getId(), User.class);
gr.addMember(user);
root.commit();
// enforce synchronization of the user and it's group membership
syncCtx.setForceUserSync(true);
syncConfig.user().setMembershipExpirationTime(-1);
syncConfig.user().setMembershipNestingDepth(1);
assertEquals(SyncResult.Status.UPDATE, syncCtx.sync(user.getID()).getStatus());
// since the group is not associated with the test-IDP the group-membership
// must NOT be modified during the sync.
assertTrue(gr.isDeclaredMember(user));
}
@Test
public void testLostMembershipWithExpirationSet() throws Exception {
long expTime = 2;
syncConfig.user().setMembershipNestingDepth(1).setMembershipExpirationTime(expTime).setExpirationTime(expTime);
Group gr = createTestGroup();
setExternalID(gr, idp.getName());
SyncResult result = syncCtx.sync(idp.listUsers().next());
User user = (User) userManager.getAuthorizable(result.getIdentity().getId());
gr.addMember(user);
root.commit();
waitUntilExpired(user, root, expTime);
DefaultSyncContext newCtx = new DefaultSyncContext(syncConfig, idp, userManager, valueFactory);
result = newCtx.sync(user.getID());
root.commit();
assertSame(SyncResult.Status.UPDATE, result.getStatus());
gr = (Group) userManager.getAuthorizable(gr.getID());
assertFalse(gr.isDeclaredMember(userManager.getAuthorizable(user.getID())));
}
/**
* @see <a href="https://issues.apache.org/jira/browse/OAK-4397">OAK-4397</a>
*/
@Test
public void testMembershipForExistingForeignGroup() throws Exception {
syncConfig.user().setMembershipNestingDepth(1).setMembershipExpirationTime(-1).setExpirationTime(-1);
syncConfig.group().setExpirationTime(-1);
ExternalUser externalUser = idp.getUser(USER_ID);
ExternalIdentityRef groupRef = externalUser.getDeclaredGroups().iterator().next();
// create the group as if it had been synced by a foreign IDP
Group gr = userManager.createGroup(groupRef.getId());
setExternalID(gr, "foreignIDP"); // but don't set rep:lastSynced :-)
root.commit();
SyncResult result = syncCtx.sync(externalUser);
assertSame(SyncResult.Status.ADD, result.getStatus());
User user = userManager.getAuthorizable(externalUser.getId(), User.class);
assertNotNull(user);
// synchronizing the user from our IDP must _neither_ change the group
// members of the group belonging to a different IDP nor synchronizing
// that foreign group with information retrieved from this IDP (e.g.
// properties and as such must _not_ set the last-synced property.
// -> verify group last-synced has not been added
assertFalse(gr.hasProperty(DefaultSyncContext.REP_LAST_SYNCED));
// -> verify group membership has not changed
assertFalse(gr.isDeclaredMember(user));
Iterator<Group> declared = user.declaredMemberOf();
while (declared.hasNext()) {
assertFalse(gr.getID().equals(declared.next().getID()));
}
}
@Test
public void testGetAuthorizableUser() throws Exception {
ExternalIdentity extUser = idp.listUsers().next();
User user = syncCtx.getAuthorizable(extUser, User.class);
assertNull(user);
sync(extUser);
user = syncCtx.getAuthorizable(extUser, User.class);
assertNotNull(user);
}
@Test(expected = SyncException.class)
public void testGetAuthorizableUserWrongType() throws Exception {
ExternalIdentity extUser = idp.listUsers().next();
sync(extUser);
syncCtx.getAuthorizable(extUser, Group.class);
}
@Test
public void testGetAuthorizableGroup() throws Exception {
ExternalIdentity extGroup = idp.listGroups().next();
Group gr = syncCtx.getAuthorizable(extGroup, Group.class);
assertNull(gr);
sync(extGroup);
gr = syncCtx.getAuthorizable(extGroup, Group.class);
assertNotNull(gr);
}
@Test(expected = SyncException.class)
public void testGetAuthorizableGroupWrongType() throws Exception {
ExternalIdentity extGroup = idp.listGroups().next();
sync(extGroup);
syncCtx.getAuthorizable(extGroup, User.class);
}
@Test
public void testSyncMembershipDepthNoSync() throws Exception {
ExternalUser externalUser = idp.listUsers().next();
Authorizable a = syncCtx.createUser(externalUser);
root.commit();
assertTrue(externalUser.getDeclaredGroups().iterator().hasNext());
syncCtx.syncMembership(externalUser, a, 0);
assertFalse(root.hasPendingChanges());
syncCtx.syncMembership(externalUser, a, -1);
assertFalse(root.hasPendingChanges());
}
@Test
public void testSyncMembershipDepth1() throws Exception {
ExternalUser externalUser = idp.listUsers().next();
Authorizable a = syncCtx.createUser(externalUser);
syncCtx.syncMembership(externalUser, a, 1);
assertTrue(root.hasPendingChanges());
for (ExternalIdentityRef ref : externalUser.getDeclaredGroups()) {
Group g = userManager.getAuthorizable(ref.getId(), Group.class);
assertNotNull(g);
assertTrue(g.isDeclaredMember(a));
}
}
@Test
public void testSyncMembershipDepthInfinite() throws Exception {
ExternalUser externalUser = idp.listUsers().next();
Authorizable a = syncCtx.createUser(externalUser);
syncCtx.syncMembership(externalUser, a, Long.MAX_VALUE);
assertTrue(root.hasPendingChanges());
root.commit();
for (ExternalIdentityRef ref : externalUser.getDeclaredGroups()) {
ExternalIdentity extGr = idp.getIdentity(ref);
assertNotNull(extGr);
for (ExternalIdentityRef inheritedGrRef : extGr.getDeclaredGroups()) {
Group g = userManager.getAuthorizable(inheritedGrRef.getId(), Group.class);
assertNotNull(g);
if (Iterables.contains(externalUser.getDeclaredGroups(), inheritedGrRef)) {
assertTrue(g.isDeclaredMember(a));
} else {
assertFalse(g.isDeclaredMember(a));
}
assertTrue(g.isMember(a));
}
}
}
@Test
public void testSyncMembershipGroupIsExternalUser() throws Exception {
// sync the 'wrong' external group into the repository
ExternalIdentity externalIdentity = idp.listUsers().next();
sync(externalIdentity);
// create external user with an synced-ext-user as declared group
ExternalUser withWrongDeclaredGroup = new ExternalUserWithDeclaredGroup(externalIdentity.getExternalId());
try {
Authorizable a = syncCtx.createUser(withWrongDeclaredGroup);
root.commit();
syncCtx.syncMembership(withWrongDeclaredGroup, a, 1);
assertFalse(root.hasPendingChanges());
} finally {
Authorizable a = userManager.getAuthorizable(withWrongDeclaredGroup.getId());
if (a != null) {
a.remove();
root.commit();
}
}
}
@Test
public void testSyncMembershipGroupIsSyncedAsUser() throws Exception {
ExternalUser fromIDP = idp.listUsers().next();
ExternalIdentityRef groupRef = fromIDP.getDeclaredGroups().iterator().next();
// sync the the ext-user from the idp (but make it just declare a single group)
ExternalUser extuser = new ExternalUserWithDeclaredGroup(groupRef, fromIDP);
Authorizable a = syncCtx.createUser(extuser);
// create an external-user based on info that the IDP knows as group and sync it
ExternalUser externalIdentity = new ExternalUserFromGroup(idp.getIdentity(groupRef));
Authorizable a2 = syncCtx.createUser(externalIdentity);
assertFalse(a2.isGroup());
root.commit();
// now sync-ing the membership should not have any effect as the external
// group referenced from 'extuser' has already been created in the system
// as user.
syncCtx.syncMembership(extuser, a, 1);
assertFalse(root.hasPendingChanges());
}
@Test
public void testApplyMembershipNonExistingGroup() throws Exception {
User u = getTestUser();
assertNull(userManager.getAuthorizable("anyGroup", Group.class));
syncCtx.applyMembership(u, ImmutableSet.of("anyGroup"));
assertFalse(root.hasPendingChanges());
}
@Test
public void testApplyMembershipNonGroup() throws Exception {
ExternalUser externalUser = idp.listUsers().next();
sync(externalUser);
User u = getTestUser();
syncCtx.applyMembership(userManager.getAuthorizable(externalUser.getId()), ImmutableSet.of(u.getID()));
assertFalse(root.hasPendingChanges());
}
@Test
public void testApplyMembership() throws Exception {
User u = getTestUser();
Group gr = createTestGroup();
syncCtx.applyMembership(u, ImmutableSet.of(gr.getID()));
assertTrue(gr.isDeclaredMember(u));
assertTrue(root.hasPendingChanges());
}
@Test
public void testSyncPropertiesEmptyMap() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
Authorizable a = syncCtx.createUser(externalUser);
syncCtx.syncProperties(externalUser, a, ImmutableMap.<String, String>of());
for (String propName : externalUser.getProperties().keySet()) {
assertFalse(a.hasProperty(propName));
}
}
@Test
public void testSyncPropertiesEmptyMapExistingProps() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
Authorizable a = syncCtx.createUser(externalUser);
Value anyValue = valueFactory.createValue("any");
Map<String, ?> extProps = externalUser.getProperties();
for (String propName : extProps.keySet()) {
a.setProperty(propName, anyValue);
}
syncCtx.syncProperties(externalUser, a, ImmutableMap.<String, String>of());
for (String propName : extProps.keySet()) {
assertTrue(a.hasProperty(propName));
assertEquals(anyValue, a.getProperty(propName)[0]);
}
}
@Test
public void testSyncPropertiesMappingRemovesExisting() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
sync(externalUser);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
// create mapping that doesn't match to names in the external-properties
// -> previously synced properties must be removed
Map<String, String> mapping = new HashMap();
Map<String, ?> extProps = externalUser.getProperties();
for (String propName : extProps.keySet()) {
mapping.put(propName, "any");
}
syncCtx.syncProperties(externalUser, a, mapping);
for (String propName : extProps.keySet()) {
assertFalse(a.hasProperty(propName));
}
}
@Test
public void testSyncPropertiesMappingConstants() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
sync(externalUser);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
// create mapping that doesn't match to names in the external-properties
// -> previously synced properties must be removed
Map<String, String> mapping = new HashMap();
Map<String, ?> extProps = externalUser.getProperties();
for (String propName : extProps.keySet()) {
mapping.put(propName, "\"any\"");
}
syncCtx.syncProperties(externalUser, a, mapping);
Value anyValue = valueFactory.createValue("any");
for (String propName : extProps.keySet()) {
assertTrue(a.hasProperty(propName));
assertEquals(anyValue, a.getProperty(propName)[0]);
}
}
@Test
public void testSyncPropertiesMappingDQuoteName() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
sync(externalUser);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
// mapping to '"' (i.e. name size = 1) which doesn't qualify as constant
// -> same behavior expected as with 'testSyncPropertiesMappingRemovesExisting'
Map<String, String> mapping = new HashMap();
Map<String, ?> extProps = externalUser.getProperties();
for (String propName : extProps.keySet()) {
mapping.put(propName, "\"");
}
syncCtx.syncProperties(externalUser, a, mapping);
for (String propName : extProps.keySet()) {
assertFalse(a.hasProperty(propName));
}
}
@Test
public void testSyncPropertiesMappingNameStartsWithDQuote() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
sync(externalUser);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
// mapping to '"any', which doesn't qualify as constant
// -> same behavior expected as with 'testSyncPropertiesMappingRemovesExisting'
Map<String, String> mapping = new HashMap();
Map<String, ?> extProps = externalUser.getProperties();
for (String propName : extProps.keySet()) {
mapping.put(propName, "\"any");
}
syncCtx.syncProperties(externalUser, a, mapping);
for (String propName : extProps.keySet()) {
assertFalse(a.hasProperty(propName));
}
}
@Test
public void testSyncProperties() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
Authorizable a = syncCtx.createUser(externalUser);
// create exact mapping
Map<String, String> mapping = new HashMap();
Map<String, ?> extProps = externalUser.getProperties();
for (String propName : extProps.keySet()) {
mapping.put(propName, propName);
}
syncCtx.syncProperties(externalUser, a, mapping);
for (String propName : extProps.keySet()) {
assertTrue(a.hasProperty(propName));
Object obj = extProps.get(propName);
Value[] vs = a.getProperty(propName);
if (vs.length == 1) {
assertEquals(syncCtx.createValue(obj), a.getProperty(propName)[0]);
} else {
Value[] expected = (obj instanceof Collection) ?
syncCtx.createValues((Collection) obj) :
syncCtx.createValues(Arrays.asList((Object[]) obj));
assertArrayEquals(expected, a.getProperty(propName));
}
}
}
@Test
public void testSyncPropertiesRemapped() throws Exception {
ExternalUser externalUser = idp.getUser(TestIdentityProvider.ID_SECOND_USER);
Authorizable a = syncCtx.createUser(externalUser);
// create exact mapping
Map<String, String> mapping = new HashMap();
Map<String, ?> extProps = externalUser.getProperties();
for (String propName : extProps.keySet()) {
mapping.put("a/"+propName, propName);
}
syncCtx.syncProperties(externalUser, a, mapping);
for (String propName : extProps.keySet()) {
String relPath = "a/" + propName;
assertTrue(a.hasProperty(relPath));
Object obj = extProps.get(propName);
Value[] vs = a.getProperty(relPath);
if (vs.length == 1) {
assertEquals(syncCtx.createValue(obj), a.getProperty(relPath)[0]);
} else {
Value[] expected = (obj instanceof Collection) ?
syncCtx.createValues((Collection) obj) :
syncCtx.createValues(Arrays.asList((Object[]) obj));
assertArrayEquals(expected, a.getProperty(relPath));
}
}
}
@Test
public void testIsExpiredLocalGroup() throws Exception {
Group gr = createTestGroup();
assertTrue(syncCtx.isExpired(gr, syncConfig.group().getExpirationTime(), "any"));
}
@Test
public void testIsExpiredEmptyLastSyncedProperty() throws Exception {
Group gr = createTestGroup();
gr.setProperty(DefaultSyncContext.REP_LAST_SYNCED, new Value[0]);
assertTrue(syncCtx.isExpired(gr, syncConfig.group().getExpirationTime(), "any"));
}
@Test
public void testIsExpiredSyncedUser() throws Exception {
ExternalIdentity externalUser = idp.listUsers().next();
sync(externalUser);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
assertFalse(syncCtx.isExpired(a, syncConfig.user().getExpirationTime(), "any"));
assertTrue(syncCtx.isExpired(a, -1, "any"));
// create a ctx with a newer 'now'
DefaultSyncContext ctx = new DefaultSyncContext(syncConfig, idp, userManager, valueFactory);
long expTime = ctx.now - syncCtx.now - 1;
assertTrue(ctx.isExpired(a, expTime, "any"));
// remove last-sync property
a.removeProperty(DefaultSyncContext.REP_LAST_SYNCED);
assertTrue(syncCtx.isExpired(a, syncConfig.user().getExpirationTime(), "any"));
}
@Test
public void testIsExpiredSyncedGroup() throws Exception {
ExternalIdentity externalGroup = idp.listGroups().next();
sync(externalGroup);
Authorizable a = userManager.getAuthorizable(externalGroup.getId());
assertFalse(syncCtx.isExpired(a, syncConfig.group().getExpirationTime(), "any"));
assertTrue(syncCtx.isExpired(a, -1, "any"));
// create a ctx with a newer 'now'
DefaultSyncContext ctx = new DefaultSyncContext(syncConfig, idp, userManager, valueFactory);
long expTime = ctx.now - syncCtx.now - 1;
assertTrue(ctx.isExpired(a, expTime, "any"));
// remove last-sync property
a.removeProperty(DefaultSyncContext.REP_LAST_SYNCED);
assertTrue(syncCtx.isExpired(a, syncConfig.group().getExpirationTime(), "any"));
}
@Test
public void testCreateValueNull() throws Exception {
assertNull(syncCtx.createValue(null));
}
@Test
public void testCreateValueString() throws Exception {
Value v = syncCtx.createValue("s");
assertNotNull(v);
assertEquals(PropertyType.STRING, v.getType());
assertEquals("s", v.getString());
v = syncCtx.createValue(new char[] {'s'});
assertNotNull(v);
assertEquals(PropertyType.STRING, v.getType());
assertEquals("s", v.getString());
Object o = new TestIdentityProvider.ForeignExternalUser();
v = syncCtx.createValue(o);
assertNotNull(v);
assertEquals(PropertyType.STRING, v.getType());
assertEquals(o.toString(), v.getString());
}
@Test
public void testCreateValueBoolean() throws Exception {
Value v = syncCtx.createValue(true);
assertNotNull(v);
assertEquals(PropertyType.BOOLEAN, v.getType());
assertEquals(true, v.getBoolean());
}
@Test
public void testCreateValueLong() throws Exception {
Value v = syncCtx.createValue(Long.MAX_VALUE);
assertNotNull(v);
assertEquals(PropertyType.LONG, v.getType());
assertEquals(Long.MAX_VALUE, v.getLong());
v = syncCtx.createValue(Integer.valueOf(23));
assertNotNull(v);
assertEquals(PropertyType.LONG, v.getType());
assertEquals(23, v.getLong());
v = syncCtx.createValue(Short.MIN_VALUE);
assertNotNull(v);
assertEquals(PropertyType.LONG, v.getType());
assertEquals(Short.MIN_VALUE, v.getLong());
v = syncCtx.createValue(Byte.MAX_VALUE);
assertNotNull(v);
assertEquals(PropertyType.LONG, v.getType());
assertEquals(Byte.MAX_VALUE, v.getLong());
}
@Test
public void testCreateValueDouble() throws Exception {
Value v = syncCtx.createValue(Double.valueOf(1.1));
assertNotNull(v);
assertEquals(PropertyType.DOUBLE, v.getType());
assertEquals(1.1, v.getDouble(), 0);
v = syncCtx.createValue(Float.NaN);
assertNotNull(v);
assertEquals(PropertyType.DOUBLE, v.getType());
assertEquals(Float.NaN, v.getDouble(), 0);
}
@Test
public void testCreateValueDate() throws Exception {
Date d = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(d);
Value v = syncCtx.createValue(cal);
assertNotNull(v);
assertEquals(PropertyType.DATE, v.getType());
Value v2 = syncCtx.createValue(d);
assertNotNull(v2);
assertEquals(PropertyType.DATE, v2.getType());
assertEquals(v, v2);
}
@Test
public void testCreateValueDecimal() throws Exception {
BigDecimal dec = new BigDecimal(123);
Value v = syncCtx.createValue(dec);
assertNotNull(v);
assertEquals(PropertyType.DECIMAL, v.getType());
assertEquals(dec, v.getDecimal());
}
@Test
public void testCreateValueFromBytesArray() throws Exception {
byte[] bytes = new byte[]{'a', 'b'};
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Binary binary = valueFactory.createBinary(is);
Value v = syncCtx.createValue(bytes);
assertNotNull(v);
assertEquals(PropertyType.BINARY, v.getType());
assertEquals(binary, v.getBinary());
}
/**
* @see <a href="https://issues.apache.org/jira/browse/OAK-4231">OAK-4231</a>
*/
@Test
public void testCreateValueFromBinary() throws Exception {
byte[] bytes = new byte[]{'a', 'b'};
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Binary binary = valueFactory.createBinary(is);
Value v = syncCtx.createValue(binary);
assertNotNull(v);
assertEquals(PropertyType.BINARY, v.getType());
assertEquals(binary, v.getBinary());
}
/**
* @see <a href="https://issues.apache.org/jira/browse/OAK-4231">OAK-4231</a>
*/
@Test
public void testCreateValueFromInputStream() throws Exception {
byte[] bytes = new byte[]{'a', 'b'};
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Binary binary = valueFactory.createBinary(is);
Value v = syncCtx.createValue(is);
assertNotNull(v);
assertEquals(PropertyType.BINARY, v.getType());
assertEquals(binary, v.getBinary());
}
@Test
public void testCreateValuesEmptyCollection() throws Exception {
Value[] vs = syncCtx.createValues(ImmutableList.of());
assertNotNull(vs);
assertEquals(0, vs.length);
}
@Test
public void testCreateValuesSkipsNull() throws Exception {
List<String> strings = Lists.newArrayList("s", null, null, "t");
Value[] vs = syncCtx.createValues(strings);
assertNotNull(vs);
assertEquals(2, vs.length);
}
@Test
public void testIsSameIDPNull() throws Exception {
assertFalse(syncCtx.isSameIDP((Authorizable) null));
}
@Test
public void testIsSameIDPLocalGroup() throws Exception {
assertFalse(syncCtx.isSameIDP(createTestGroup()));
}
@Test
public void testIsSameIDPLocalUser() throws Exception {
assertFalse(syncCtx.isSameIDP(getTestUser()));
}
@Test
public void testIsSameIDPSyncedGroup() throws Exception {
ExternalIdentity externalGroup = idp.listGroups().next();
sync(externalGroup);
assertTrue(syncCtx.isSameIDP(userManager.getAuthorizable(externalGroup.getId())));
}
@Test
public void testIsSameIDPSyncedUser() throws Exception {
ExternalIdentity externalUser = idp.listUsers().next();
sync(externalUser);
assertTrue(syncCtx.isSameIDP(userManager.getAuthorizable(externalUser.getId())));
}
@Test
public void testIsSameIDPMissingExternalId() throws Exception {
ExternalIdentity externalUser = idp.listUsers().next();
sync(externalUser);
Authorizable a = userManager.getAuthorizable(externalUser.getId());
a.removeProperty(DefaultSyncContext.REP_EXTERNAL_ID);
assertFalse(syncCtx.isSameIDP(a));
}
@Test
public void testIsSameIDPForeign() throws Exception {
Group gr = createTestGroup();
setExternalID(gr, "some_other_idp");
assertFalse(syncCtx.isSameIDP(gr));
}
@Test
public void testIsSameIDPExternalIdentityRef() throws Exception {
assertFalse(syncCtx.isSameIDP(new TestIdentityProvider.ForeignExternalUser().getExternalId()));
assertFalse(syncCtx.isSameIDP(new TestIdentityProvider.ForeignExternalGroup().getExternalId()));
assertTrue(syncCtx.isSameIDP(new TestIdentityProvider.TestIdentity().getExternalId()));
assertTrue(syncCtx.isSameIDP(idp.listGroups().next().getExternalId()));
assertTrue(syncCtx.isSameIDP(idp.listUsers().next().getExternalId()));
}
private final class ExternalUserWithDeclaredGroup extends TestIdentityProvider.TestIdentity implements ExternalUser {
private final ExternalIdentityRef declaredGroupRef;
private ExternalUserWithDeclaredGroup(@NotNull ExternalIdentityRef declaredGroupRef) {
super("externalId");
this.declaredGroupRef = declaredGroupRef;
}
private ExternalUserWithDeclaredGroup(@NotNull ExternalIdentityRef declaredGroupRef, @NotNull ExternalIdentity base) {
super(base);
this.declaredGroupRef = declaredGroupRef;
}
@NotNull
@Override
public Iterable<ExternalIdentityRef> getDeclaredGroups() {
return ImmutableSet.of(declaredGroupRef);
}
}
private final class ExternalUserFromGroup extends TestIdentityProvider.TestIdentity implements ExternalUser {
private ExternalUserFromGroup(@NotNull ExternalIdentity base) {
super(base);
}
}
}