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


Reply via email to