Modified: jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java (original) +++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java Thu Aug 18 10:04:48 2016 @@ -16,94 +16,257 @@ */ package org.apache.jackrabbit.oak.spi.security.authentication.external.basic; -import java.util.ArrayList; -import java.util.Iterator; +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.List; import java.util.Map; import java.util.UUID; -import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; +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.Iterators; +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.AbstractSecurityTest; -import org.apache.jackrabbit.oak.namepath.NamePathMapper; -import org.apache.jackrabbit.oak.plugins.value.ValueFactoryImpl; +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.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 AbstractSecurityTest { - - private TestIdentityProvider idp = new TestIdentityProvider(); - private DefaultSyncConfig config = new DefaultSyncConfig(); +public class DefaultSyncContextTest extends AbstractExternalAuthTest { + private UserManager userManager; + private ValueFactory valueFactory; private DefaultSyncContext syncCtx; - private List<String> authorizableIds = new ArrayList<String>(); - @Before public void before() throws Exception { super.before(); - syncCtx = new DefaultSyncContext(config, idp, getUserManager(root), new ValueFactoryImpl(root, NamePathMapper.DEFAULT)); + userManager = getUserManager(root); + valueFactory = getValueFactory(); + syncCtx = new DefaultSyncContext(syncConfig, idp, userManager, valueFactory); } @After public void after() throws Exception { try { syncCtx.close(); - UserManager umgr = getUserManager(root); - Iterator<ExternalIdentity> ids = Iterators.concat(idp.listGroups(), idp.listUsers()); - while (ids.hasNext()) { - Authorizable a = umgr.getAuthorizable(ids.next().getId()); - if (a != null) { - a.remove(); - } - } - for (String id : authorizableIds) { - Authorizable a = umgr.getAuthorizable(id); - if (a != null) { - a.remove(); - } - } - root.commit(); + root.refresh(); } finally { super.after(); } } + @Override + protected DefaultSyncConfig createSyncConfig() { + return new DefaultSyncConfig(); + } + private Group createTestGroup() throws Exception { - Group gr = getUserManager(root).createGroup("group" + UUID.randomUUID()); - authorizableIds.add(gr.getID()); - return gr; + 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(@Nonnull ExternalIdentity externalIdentity) throws Exception { + SyncResult result = syncCtx.sync(externalIdentity); + assertSame(SyncResult.Status.ADD, result.getStatus()); + root.commit(); } private void setExternalID(@Nonnull Authorizable authorizable, @Nullable String idpName) throws RepositoryException { - authorizable.setProperty(DefaultSyncContext.REP_EXTERNAL_ID, getValueFactory().createValue(authorizable.getID() + ';' + idpName)); + 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 TestExternalIdentity()); + syncCtx.sync(new TestIdentityProvider.TestIdentity()); } @Test @@ -140,6 +303,50 @@ public class DefaultSyncContextTest exte } @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 testSyncUserById() throws Exception { ExternalIdentity externalId = idp.listUsers().next(); @@ -159,24 +366,22 @@ public class DefaultSyncContextTest exte @Test public void testSyncRemovedUserById() throws Exception { // mark a regular repo user as external user from the test IDP - User u = getUserManager(root).createUser("test" + UUID.randomUUID(), null); + User u = userManager.createUser("test" + UUID.randomUUID(), null); String userId = u.getID(); - authorizableIds.add(userId); - setExternalID(u, idp.getName()); // test sync with 'keepmissing' = true syncCtx.setKeepMissing(true); SyncResult result = syncCtx.sync(userId); assertEquals(SyncResult.Status.MISSING, result.getStatus()); - assertNotNull(getUserManager(root).getAuthorizable(userId)); + assertNotNull(userManager.getAuthorizable(userId)); // test sync with 'keepmissing' = false syncCtx.setKeepMissing(false); result = syncCtx.sync(userId); assertEquals(SyncResult.Status.DELETE, result.getStatus()); - assertNull(getUserManager(root).getAuthorizable(userId)); + assertNull(userManager.getAuthorizable(userId)); } @Test @@ -208,14 +413,14 @@ public class DefaultSyncContextTest exte syncCtx.setKeepMissing(true); SyncResult result = syncCtx.sync(groupId); assertEquals(SyncResult.Status.MISSING, result.getStatus()); - assertNotNull(getUserManager(root).getAuthorizable(groupId)); + assertNotNull(userManager.getAuthorizable(groupId)); // test sync with 'keepmissing' = false syncCtx.setKeepMissing(false); result = syncCtx.sync(groupId); assertEquals(SyncResult.Status.DELETE, result.getStatus()); - assertNull(getUserManager(root).getAuthorizable(groupId)); + assertNull(userManager.getAuthorizable(groupId)); } @Test @@ -231,47 +436,74 @@ public class DefaultSyncContextTest exte syncCtx.setKeepMissing(true); SyncResult result = syncCtx.sync(groupId); assertEquals(SyncResult.Status.NOP, result.getStatus()); - assertNotNull(getUserManager(root).getAuthorizable(groupId)); + assertNotNull(userManager.getAuthorizable(groupId)); // test sync with 'keepmissing' = false syncCtx.setKeepMissing(false); result = syncCtx.sync(groupId); assertEquals(SyncResult.Status.NOP, result.getStatus()); - assertNotNull(getUserManager(root).getAuthorizable(groupId)); + assertNotNull(userManager.getAuthorizable(groupId)); } @Test - public void testSyncByForeignId() throws Exception { + 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 = getTestUser(); + 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(); - config.user().setAutoMembership(gr.getID()); + syncConfig.user().setAutoMembership(gr.getID()); SyncResult result = syncCtx.sync(idp.listUsers().next()); assertEquals(SyncResult.Status.ADD, result.getStatus()); - Authorizable a = getUserManager(root).getAuthorizable(result.getIdentity().getId()); + Authorizable a = userManager.getAuthorizable(result.getIdentity().getId()); assertTrue(gr.isDeclaredMember(a)); } @Test public void testSyncAutoMembershipListsNonExistingGroup() throws Exception { - config.user().setAutoMembership("nonExistingGroup"); + syncConfig.user().setAutoMembership("nonExistingGroup"); SyncResult result = syncCtx.sync(idp.listUsers().next()); assertEquals(SyncResult.Status.ADD, result.getStatus()); @@ -280,7 +512,7 @@ public class DefaultSyncContextTest exte @Test public void testSyncAutoMembershipListsUser() throws Exception { // set auto-membership config to point to a user instead a group - config.user().setAutoMembership(getTestUser().getID()); + syncConfig.user().setAutoMembership(getTestUser().getID()); syncCtx.sync(idp.listUsers().next()); } @@ -295,21 +527,21 @@ public class DefaultSyncContextTest exte // 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 = getUserManager(root).getAuthorizable(result.getIdentity().getId(), User.class); + 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); - config.user().setMembershipExpirationTime(-1); + syncConfig.user().setMembershipExpirationTime(-1); // 1. membership nesting is < 0 => membership not synchronized - config.user().setMembershipNestingDepth(-1); + syncConfig.user().setMembershipNestingDepth(-1); syncCtx.sync(user.getID()).getStatus(); assertTrue(gr.isDeclaredMember(user)); // 2. membership nesting is > 0 => membership gets synchronized - config.user().setMembershipNestingDepth(1); + syncConfig.user().setMembershipNestingDepth(1); assertEquals(SyncResult.Status.UPDATE, syncCtx.sync(user.getID()).getStatus()); assertFalse(gr.isDeclaredMember(user)); @@ -325,14 +557,14 @@ public class DefaultSyncContextTest exte // 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 = getUserManager(root).getAuthorizable(result.getIdentity().getId(), User.class); + 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); - config.user().setMembershipExpirationTime(-1); - config.user().setMembershipNestingDepth(1); + syncConfig.user().setMembershipExpirationTime(-1); + syncConfig.user().setMembershipNestingDepth(1); assertEquals(SyncResult.Status.UPDATE, syncCtx.sync(user.getID()).getStatus()); @@ -341,45 +573,640 @@ public class DefaultSyncContextTest exte assertTrue(gr.isDeclaredMember(user)); } - /** - * ExternalIdentity implementation that is neither user nor group. - */ - private final class TestExternalIdentity implements ExternalIdentity { + @Test + public void testGetAuthorizableUser() throws Exception { + ExternalIdentity extUser = idp.listUsers().next(); + User user = syncCtx.getAuthorizable(extUser, User.class); + assertNull(user); - @Nonnull - @Override - public ExternalIdentityRef getExternalId() { - return new ExternalIdentityRef(getId(), idp.getName()); + 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)); } + } - @Nonnull - @Override - public String getId() { - return "externalId"; + @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)); + } } + } - @Nonnull - @Override - public String getPrincipalName() { - return "principalName"; + @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(); + } } + } - @CheckForNull - @Override - public String getIntermediatePath() { - return null; + @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(@Nonnull ExternalIdentityRef declaredGroupRef) { + super("externalId"); + this.declaredGroupRef = declaredGroupRef; + } + + private ExternalUserWithDeclaredGroup(@Nonnull ExternalIdentityRef declaredGroupRef, @Nonnull ExternalIdentity base) { + super(base); + this.declaredGroupRef = declaredGroupRef; } @Nonnull @Override public Iterable<ExternalIdentityRef> getDeclaredGroups() { - return ImmutableSet.of(); + return ImmutableSet.of(declaredGroupRef); } + } - @Nonnull - @Override - public Map<String, ?> getProperties() { - return ImmutableMap.of(); + private final class ExternalUserFromGroup extends TestIdentityProvider.TestIdentity implements ExternalUser { + + private ExternalUserFromGroup(@Nonnull ExternalIdentity base) { + super(base); } } } \ No newline at end of file
Copied: jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/CustomCredentialsSupportTest.java (from r1735141, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/CustomCredentialsSupportTest.java) URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/CustomCredentialsSupportTest.java?p2=jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/CustomCredentialsSupportTest.java&p1=jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/CustomCredentialsSupportTest.java&r1=1735141&r2=1756752&rev=1756752&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/CustomCredentialsSupportTest.java (original) +++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/CustomCredentialsSupportTest.java Thu Aug 18 10:04:48 2016 @@ -52,8 +52,6 @@ import static org.junit.Assert.fail; */ public class CustomCredentialsSupportTest extends ExternalLoginModuleTestBase { - private final IDP idp = new IDP(); - private static void assertAttributes(@Nonnull Map<String, ?> expected, @Nonnull AuthInfo info) { assertEquals(expected.size(), info.getAttributeNames().length); for (String aName : info.getAttributeNames()) { @@ -69,7 +67,7 @@ public class CustomCredentialsSupportTes try { AuthInfo info = cs.getAuthInfo(); assertEquals("testUser", info.getUserID()); - assertAttributes(idp.getAttributes(creds), info); + assertAttributes(((IDP) idp).getAttributes(creds), info); } finally { cs.close(); } @@ -93,12 +91,7 @@ public class CustomCredentialsSupportTes @Override protected ExternalIdentityProvider createIDP() { - return idp; - } - - @Override - protected void destroyIDP(ExternalIdentityProvider idp) { - // ignore + return new IDP(); } private static final class TestCredentials implements Credentials { @@ -138,7 +131,7 @@ public class CustomCredentialsSupportTes @Nonnull @Override public ExternalIdentityRef getExternalId() { - return new ExternalIdentityRef(uid, "test"); + return new ExternalIdentityRef(uid, getName()); } @Nonnull Copied: jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java (from r1739712, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java) URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java?p2=jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java&p1=jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java&r1=1739712&r2=1756752&rev=1756752&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java (original) +++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java Thu Aug 18 10:04:48 2016 @@ -56,8 +56,6 @@ import static org.junit.Assert.assertTru */ public class DefaultSyncHandlerTest extends ExternalLoginModuleTestBase { - private String userId = TestIdentityProvider.ID_TEST_USER; - private UserManager userManager; private DefaultSyncHandler syncHandler; @@ -77,15 +75,6 @@ public class DefaultSyncHandlerTest exte super.after(); } - protected ExternalIdentityProvider createIDP() { - return new TestIdentityProvider(); - } - - @Override - protected void destroyIDP(ExternalIdentityProvider idp) { - // ignore - } - @Override protected void setSyncConfig(DefaultSyncConfig cfg) { if (cfg != null) { @@ -129,13 +118,13 @@ public class DefaultSyncHandlerTest exte @Test public void testFindExternalIdentity() throws Exception { - login(new SimpleCredentials(userId, new char[0])).close(); + login(new SimpleCredentials(USER_ID, new char[0])).close(); root.refresh(); - SyncedIdentity id = syncHandler.findIdentity(userManager, userId); + SyncedIdentity id = syncHandler.findIdentity(userManager, USER_ID); assertNotNull("known authorizable should exist", id); assertEquals("external user should have correct external ref.idp", idp.getName(), id.getExternalIdRef().getProviderName()); - assertEquals("external user should have correct external ref.id", userId, id.getExternalIdRef().getId()); + assertEquals("external user should have correct external ref.id", USER_ID, id.getExternalIdRef().getId()); } @Test @@ -153,23 +142,23 @@ public class DefaultSyncHandlerTest exte @Test public void testFindIdentityWithRemovedExternalId() throws Exception { - sync(userId, false); + sync(USER_ID, false); // NOTE: this is only possible as long the rep:externalId property is not protected - Authorizable authorizable = userManager.getAuthorizable(userId); + Authorizable authorizable = userManager.getAuthorizable(USER_ID); authorizable.removeProperty(DefaultSyncContext.REP_EXTERNAL_ID); root.commit(); - SyncedIdentity si = syncHandler.findIdentity(userManager, userId); + SyncedIdentity si = syncHandler.findIdentity(userManager, USER_ID); assertNull(si.getExternalIdRef()); } @Test public void testRequiresSyncAfterCreate() throws Exception { - login(new SimpleCredentials(userId, new char[0])).close(); + login(new SimpleCredentials(USER_ID, new char[0])).close(); root.refresh(); - SyncedIdentity id = syncHandler.findIdentity(userManager, userId); + SyncedIdentity id = syncHandler.findIdentity(userManager, USER_ID); assertNotNull("Known authorizable should exist", id); assertFalse("Freshly synced id should not require sync", syncHandler.requiresSync(id)); @@ -177,18 +166,18 @@ public class DefaultSyncHandlerTest exte @Test public void testRequiresSyncExpiredSyncProperty() throws Exception { - login(new SimpleCredentials(userId, new char[0])).close(); + login(new SimpleCredentials(USER_ID, new char[0])).close(); root.refresh(); final Calendar nowCal = Calendar.getInstance(); nowCal.setTimeInMillis(nowCal.getTimeInMillis() - 1000); Value nowValue = getValueFactory().createValue(nowCal); - Authorizable a = userManager.getAuthorizable(userId); + Authorizable a = userManager.getAuthorizable(USER_ID); a.setProperty(DefaultSyncContext.REP_LAST_SYNCED, nowValue); root.commit(); - SyncedIdentity id = syncHandler.findIdentity(userManager, userId); + SyncedIdentity id = syncHandler.findIdentity(userManager, USER_ID); assertNotNull("known authorizable should exist", id); assertTrue("synced id should require sync", syncHandler.requiresSync(id)); @@ -196,25 +185,25 @@ public class DefaultSyncHandlerTest exte @Test public void testRequiresSyncMissingSyncProperty() throws Exception { - sync(userId, false); + sync(USER_ID, false); - Authorizable a = userManager.getAuthorizable(userId); + Authorizable a = userManager.getAuthorizable(USER_ID); a.removeProperty(DefaultSyncContext.REP_LAST_SYNCED); root.commit(); - SyncedIdentity si = syncHandler.findIdentity(userManager, userId); + SyncedIdentity si = syncHandler.findIdentity(userManager, USER_ID); assertNotNull(si); assertTrue(syncHandler.requiresSync(si)); } @Test public void testRequiresSyncMissingExternalIDRef() throws Exception { - assertTrue(syncHandler.requiresSync(new DefaultSyncedIdentity(userId, null, false, Long.MAX_VALUE))); + assertTrue(syncHandler.requiresSync(new DefaultSyncedIdentity(USER_ID, null, false, Long.MAX_VALUE))); } @Test public void testRequiresSyncNotYetSynced() throws Exception { - assertTrue(syncHandler.requiresSync(new DefaultSyncedIdentity(userId, idp.getUser(userId).getExternalId(), false, Long.MIN_VALUE))); + assertTrue(syncHandler.requiresSync(new DefaultSyncedIdentity(USER_ID, idp.getUser(USER_ID).getExternalId(), false, Long.MIN_VALUE))); } @Test @@ -238,11 +227,11 @@ public class DefaultSyncHandlerTest exte @Test public void testListIdentitiesAfterSync() throws Exception { - sync(userId, false); + sync(USER_ID, false); - // membership-nesting is 1 => expect only 'userId' plus the declared group-membership - Set<String> expected = Sets.newHashSet(userId); - for (ExternalIdentityRef extRef : idp.getUser(userId).getDeclaredGroups()) { + // membership-nesting is 1 => expect only 'USER_ID' plus the declared group-membership + Set<String> expected = Sets.newHashSet(USER_ID); + for (ExternalIdentityRef extRef : idp.getUser(USER_ID).getDeclaredGroups()) { expected.add(extRef.getId()); } Copied: jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java (from r1744292, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java) URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java?p2=jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java&p1=jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java&r1=1744292&r2=1756752&rev=1756752&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java (original) +++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java Thu Aug 18 10:04:48 2016 @@ -115,7 +115,7 @@ public class DynamicSyncContextTest exte }; }); - Set<String> expected = new HashSet<>(); + Set<String> expected = new HashSet<String>(); collectGroupPrincipals(expected, externalIdentity.getDeclaredGroups(), depth); assertEquals(expected, ImmutableSet.copyOf(pNames)); Modified: jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java?rev=1756752&r1=1744292&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java (original) +++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java Thu Aug 18 10:04:48 2016 @@ -50,6 +50,16 @@ import static org.junit.Assert.assertTru */ public class ExternalIdentityImporterTest { + public static final String XML_EXTERNAL_USER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" + + " <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" + + " <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" + + " <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" + + " <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>tPrinc</sv:value></sv:property>" + + " <sv:property sv:name=\"rep:externalId\" sv:type=\"String\"><sv:value>idp;ext-t</sv:value></sv:property>" + + " <sv:property sv:name=\"rep:lastSynced\" sv:type=\"Date\"><sv:value>2016-05-03T10:03:08.061+02:00</sv:value></sv:property>" + + "</sv:node>"; + public static final String XML_EXTERNAL_USER_WITH_PRINCIPAL_NAMES = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" + " <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" + @@ -67,7 +77,7 @@ public class ExternalIdentityImporterTes @Before public void before() throws Exception { - securityProvider = new TestSecurityProvider(getConfigurationParameters()); + securityProvider = new TestSecurityProvider(getConfigurationParameters(), new ExternalPrincipalConfiguration()); Jcr jcr = new Jcr(); jcr.with(securityProvider); repo = jcr.createRepository(); @@ -133,6 +143,20 @@ public class ExternalIdentityImporterTes } @Test + public void importExternalUser() throws Exception { + Node parent = doImport(createSession(false), UserConstants.DEFAULT_USER_PATH, XML_EXTERNAL_USER); + assertHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_ID, ExternalIdentityConstants.REP_LAST_SYNCED); + assertNotHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES); + } + + @Test + public void importExternalUserAsSystem() throws Exception { + Node parent = doImport(createSession(true), UserConstants.DEFAULT_USER_PATH, XML_EXTERNAL_USER); + assertHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_ID, ExternalIdentityConstants.REP_LAST_SYNCED); + assertNotHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES); + } + + @Test public void importExternalUserWithPrincipalNames() throws Exception { Node parent = doImport(createSession(false), UserConstants.DEFAULT_USER_PATH, XML_EXTERNAL_USER_WITH_PRINCIPAL_NAMES); assertHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_ID); Modified: jackrabbit/oak/branches/1.4/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java (original) +++ jackrabbit/oak/branches/1.4/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java Thu Aug 18 10:04:48 2016 @@ -162,7 +162,7 @@ public abstract class LdapLoginTestBase } @Override - protected void destroyIDP(ExternalIdentityProvider idp) { + protected void destroyIDP() { ((LdapIdentityProvider) idp).close(); } Modified: jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/util/TreeUtil.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/util/TreeUtil.java?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/util/TreeUtil.java (original) +++ jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/util/TreeUtil.java Thu Aug 18 10:04:48 2016 @@ -26,6 +26,7 @@ import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; +import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -304,16 +305,12 @@ public final class TreeUtil { String now = ISO8601.format(Calendar.getInstance()); return PropertyStates.createProperty(name, now, DATE); } else if (JCR_CREATEDBY.equals(name)) { - if (userID != null) { - return PropertyStates.createProperty(name, userID, STRING); - } + return PropertyStates.createProperty(name, Strings.nullToEmpty(userID), STRING); } else if (JCR_LASTMODIFIED.equals(name)) { String now = ISO8601.format(Calendar.getInstance()); return PropertyStates.createProperty(name, now, DATE); } else if (JCR_LASTMODIFIEDBY.equals(name)) { - if (userID != null) { - return PropertyStates.createProperty(name, userID, STRING); - } + return PropertyStates.createProperty(name, Strings.nullToEmpty(userID), STRING); } // does the definition have a default value? Modified: jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java (original) +++ jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java Thu Aug 18 10:04:48 2016 @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; @@ -118,6 +119,10 @@ public abstract class AbstractSecurityTe } } + protected ContentRepository getContentRepository() { + return contentRepository; + } + protected SecurityProvider getSecurityProvider() { if (securityProvider == null) { securityProvider = new SecurityProviderImpl(getSecurityConfigParameters()); @@ -197,6 +202,10 @@ public abstract class AbstractSecurityTe } protected ValueFactory getValueFactory() { + return getValueFactory(root); + } + + protected ValueFactory getValueFactory(@Nonnull Root root) { return new ValueFactoryImpl(root, getNamePathMapper()); } Modified: jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication.md URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication.md?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication.md (original) +++ jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication.md Thu Aug 18 10:04:48 2016 @@ -76,7 +76,7 @@ LoginModule is configured and succeeds, LoginModule need to have succeeded for the overall authentication to succeed. If no Required or Requisite LoginModules are configured for an application, then at least one Sufficient or Optional LoginModule must succeed. -<a href="jcr_api"/> +<a name="jcr_api"/> ### JCR API Within the scope of JCR `Repository.login` is used to authenticate a given user. @@ -104,7 +104,7 @@ for further details. In addition JCR defines `Session.impersonate(Credentials)` to impersonate another user or - as of JSR 333 - clone an existing session. -<a href="oak_api"/> +<a name="oak_api"/> ### Oak API The Oak API contains the following authentication related methods and interfaces @@ -138,7 +138,7 @@ security related interfaces (e.g. `Princ Subclasses are required to implement the following methods: -- `getSupportedCredentials()`: return a set of supported credential classes. +- `getSupportedCredentials()`: return a set of supported credential classes. See also section [Supported Credentials](#supported_credentials) - `login()`: The login method defined by `LoginModule` - `commit()`: The commit method defined by `LoginModule` @@ -185,7 +185,17 @@ Subclasses are required to implement the } } -<a href="default_implementation"/> +<a name="supported_credentials"/> +#### Supported Credentials + +Since Oak 1.5.1 the extensions additionally contain a dedicated interface that +eases the support for different `Credentials` in the package space +`org.apache.jackrabbit.oak.spi.security.authentication.credentials`: + +- [CredentialsSupport]: Interface definition exposing the set of supported `Credentials` classes and some common utility methods. +- [SimpleCredentialsSupport]: Default implementation for the widely used `SimpleCredentials` + +<a name="default_implementation"/> ### Oak Authentication Implementation A description of the various requirements covered by Oak by default as well @@ -251,4 +261,6 @@ implementation on various levels: [AuthInfo]: /oak/docs/apidocs/org/apache/jackrabbit/oak/api/AuthInfo.html [AbstractLoginModule]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.html [AuthenticationConfiguration]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/AuthenticationConfiguration.html -[JAAS config]: http://docs.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html \ No newline at end of file +[JAAS config]: http://docs.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html +[CredentialsSupport]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/credentials/CredentialsSupport.html +[SimpleCredentialsSupport]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/credentials/SimpleCredentialsSupport.html Modified: jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md (original) +++ jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md Thu Aug 18 10:04:48 2016 @@ -63,16 +63,18 @@ If a user needs re-authentication (for e if the user is not yet present in the local system at all), the login module must check the credentials with the external system during the `login()` method. -Note: - -* users (and groups) that are synced from the 3rd party system contain a `rep:externalId` property. This allows to identify the external users and distinguish them from others. -* to reduce expensive syncing, the synced users and groups have sync timestamp `rep:lastSynced` and are considered valid for a configurable time. if they expire, they need to be validated against the 3rd party system again. +The details of the default user/group synchronization mechanism are described in section +[User and Group Synchronization : The Default Implementation](defaultusersync.html) ##### Supported Credentials -Currently this login module supports the following credentials: - -- `SimpleCredentials` +As of Oak 1.5.1 the `ExternalLoginModule` can deal for any kind of `Credentials` +implementations. By default (i.e. unless configured otherwise) the module supports +`SimpleCredentials` and thus behaves backwards compatible to previous versions. + +Additional/other credentials can be supported by providing an `ExternalIdentityProvider` +that additionally implements the [CredentialsSupport] interface. +See section [Pluggability](#pluggability) for instructions and an example. ##### Authentication in Detail @@ -162,6 +164,82 @@ The default implementations ([ExternalID extend `AbstractServiceTracker` and will automatically keep track of new [ExternalIdentityProvider] and [SyncHandler] services, respectively. +Since Oak 1.5.1 support for different or multiple types of `Credentials` can easily +be plugged by providing an [ExternalIdentityProvider] that additionally implements +[CredentialsSupport]. This is an optional extension point for each IDP; if +missing the `ExternalLoginModule` will fall back to a default implementation and +assume the IDP only supports `SimpleCredentials`. See details below. + +#### Supported Credentials + +The following steps are required in order to change or extend the set credential +classes supported by the `ExternalLoginModule`: + +- Extend your `ExternalIdentityProvider` to additionally implement the [CredentialsSupport] interface. + +Don't forget to make sure that `ExternalIdentityProvider.authenticate(Credentials)` +handles the same set of supported credentials! + +##### Examples + +###### Example CredentialsSupport + + @Component() + @Service(ExternalIdentityProvider.class, CredentialsSupport.class) + public class MyIdentityProvider implements ExternalIdentityProvider, CredentialsSupport { + + public MyCredentialsSupport() {} + + //-----------------------------------------< CredentialsSupport >--- + @Nonnull + @Override + public Set<Class> getCredentialClasses() { + return ImmutableSet.<Class>of(MyCredentials.class); + } + + @CheckForNull + @Override + public String getUserId(@Nonnull Credentials credentials) { + if (credentials instanceof MyCredentials) { + return ((MyCredentials) credentials).getID(); + } else { + return null; + } + } + + @Nonnull + @Override + public Map<String, ?> getAttributes(@Nonnull Credentials credentials) { + // our credentials never contain additional attributes + return ImmutableMap.of(); + } + + //-------------------------------------< ExternalIdentityProvider >--- + + @CheckForNull + @Override + public ExternalUser authenticate(@Nonnull Credentials credentials) { + if (credentials instanceof MyCredentials) { + MyCredentials mc = (MyCredentials) credentials; + if (internalAuthenticate(mc)) { + return new MyExternalUser(mc.getID()); + } else { + throw new LoginException(); + } + } else { + return null; + } + } + + [...] + + //----------------------------------------------< SCR Integration >--- + @Activate + private void activate() { + // TODO + } + } + <!-- references --> [DefaultSyncConfig]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.html [ExternalIdentityProvider]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityProvider.html @@ -173,3 +251,4 @@ new [ExternalIdentityProvider] and [Sync [SyncHandler]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.html [SyncManager]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncManager.html [SyncManagerImpl]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.html +[CredentialsSupport]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/credentials/CredentialsSupport.html Modified: jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/usersync.md URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/usersync.md?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/usersync.md (original) +++ jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/usersync.md Thu Aug 18 10:04:48 2016 @@ -24,9 +24,6 @@ The synchronization of users and groups after a user is successfully authenticated against the IDP or if it's no longer present on the IDP. -Oak comes with a default implementation of the `SyncHandler` interface: -[org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler]. - ### Synchronization API - [SyncManager]: factory for all configured `SyncHandler` implementations. @@ -52,39 +49,9 @@ for the following tasks: ### Default Implementation Oak 1.0 provides a default implementation of the user synchronization API that allow -to plug additional `SyncHandler` implementations. - -The [DefaultSyncHandler] itself comes with a set of configuration options that -allow to specify the synchronization behavior (see below). All users/groups -synchronized by this handler will get the following properties set: - -- `rep:externalId` -- `rep:lastSynced` - -These properties allow to run separat task for periodical update and make sure -the authorizables can later on be identitied as external users. - -### Configuration - -#### Configuration of the DefaultSyncHandler - -The default sync handler implementation is configured via [DefaultSyncConfig]: - -| Name | Property | Description | -|-------------------------------|-------------------------------|------------------------------------------| -| Sync Handler Name | `handler.name` | Name of this sync configuration. This is used to reference this handler by the login modules. | -| User auto membership | `user.autoMembership` | List of groups that a synced user is added to automatically | -| User Expiration Time | `user.expirationTime` | Duration until a synced user gets expired (eg. '1h 30m' or '1d'). | -| User Membership Expiration | `user.membershipExpTime` | Time after which membership expires (eg. '1h 30m' or '1d'). | -| User membership nesting depth | `user.membershipNestingDepth` | Returns the maximum depth of group nesting when membership relations are synced. A value of 0 effectively disables group membership lookup. A value of 1 only adds the direct groups of a user. This value has no effect when syncing individual groups only when syncing a users membership ancestry. | -| User Path Prefix | `user.pathPrefix` | The path prefix used when creating new users. | -| User property mapping | `user.propertyMapping` | List mapping definition of local properties from external ones. eg: 'profile/email=mail'.Use double quotes for fixed values. eg: 'profile/nt:primaryType="nt:unstructured" | -| Group auto membership | `group.autoMembership` | List of groups that a synced group is added to automatically | -| Group Expiration Time | `group.expirationTime` | Duration until a synced group expires (eg. '1h 30m' or '1d'). | -| Group Path Prefix | `group.pathPrefix` | The path prefix used when creating new groups. | -| Group property mapping | `group.propertyMapping` | List mapping definition of local properties from external ones. | -| | | | +to plug additional `SyncHandler` implementations. +Default implementation is described in section [User and Group Synchronization : The Default Implementation](defaultusersync.html). ### Pluggability @@ -110,5 +77,3 @@ or plug a new implementation of the `Syn [SyncedIdentity]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncedIdentity.html [SyncResult]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncResult.html [SyncException]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncException.html -[DefaultSyncHandler]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.html -[DefaultSyncConfig]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.html Modified: jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/principal/principalprovider.md URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/principal/principalprovider.md?rev=1756752&r1=1756751&r2=1756752&view=diff ============================================================================== --- jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/principal/principalprovider.md (original) +++ jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/principal/principalprovider.md Thu Aug 18 10:04:48 2016 @@ -63,7 +63,29 @@ Custom `PrincipalProvider` implementatio different source i.e. detaching principal management from the user management, where principals are backed by an existing user/group account. +### [ExternalGroupPrincipalProvider] + +Implementation of the `PrincipalProvider` interface that exposes _external_ principals +of type `java.security.acl.Group`. _External_ refers to the fact that these +principals are defined and managed by an external identity provider in contrast to +the default implementation that represents principals native to the repository. +This implies that the principals known and exposed by this provider implementation +does not expect principals to be backed by an authorizable group. As such they +can only be retrieved using Jackrabbit Principal Management API but not with +User Management calls. + +For performance reasons the `ExternalGroupPrincipalProvider` doesn't lookup +principals on the IDP but relies on a persisted cache inside the repository where +the names of these external principals are synchronized to based on a configurable +expiration time. + +See section [User and Group Synchronization : The Default Implementation](../authentication/defaultusersync.html) +for additional details. + +Since Oak 1.5.3 + <!-- references --> [PrincipalProviderImpl]: /oak/docs/apidocs/org/apache/jackrabbit/oak/security/principal/PrincipalProviderImpl.html [CompositePrincipalProvider]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/principal/CompositePrincipalProvider.html [UserPrincipalProvider]: /oak/docs/apidocs/org/apache/jackrabbit/oak/security/user/UserPrincipalProvider.html +[ExternalGroupPrincipalProvider]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.html
