| /* |
| * 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; |
| } |
| } |
| } |