You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2016/05/17 16:18:30 UTC

svn commit: r1744292 [3/3] - in /jackrabbit/oak/trunk: oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/ oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/...

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java Tue May 17 16:18:29 2016
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.AbstractExternalAuthTest;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.util.TreeUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class ExternalIdentityRepositoryInitializerTest extends AbstractExternalAuthTest {
+
+    @Test
+    public void testIndexDefinitions() throws Exception {
+        Tree oakIndex = root.getTree('/' + IndexConstants.INDEX_DEFINITIONS_NAME);
+        assertTrue(oakIndex.exists());
+
+        Tree externalPrincipalNames = oakIndex.getChild("externalPrincipalNames");
+        assertIndexDefinition(externalPrincipalNames, ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, false);
+    }
+
+    private static void assertIndexDefinition(Tree tree, String propName, boolean isUnique) {
+        assertTrue(tree.exists());
+        assertEquals(isUnique, TreeUtil.getBoolean(tree, IndexConstants.UNIQUE_PROPERTY_NAME));
+        assertArrayEquals(
+                propName, new String[]{propName},
+                Iterables.toArray(TreeUtil.getStrings(tree, IndexConstants.PROPERTY_NAMES), String.class));
+        Iterable<String> declaringNtNames = TreeUtil.getStrings(tree, IndexConstants.DECLARING_NODE_TYPES);
+        assertNull(declaringNtNames);
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java Tue May 17 16:18:29 2016
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import javax.jcr.SimpleCredentials;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalLoginModuleTestBase;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class ExternalIdentityValidatorTest extends ExternalLoginModuleTestBase {
+
+    String testUserPath;
+    String externalUserPath;
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        testUserPath = getTestUser().getPath();
+
+        // force an external user to be synchronized into the repo
+        login(new SimpleCredentials(USER_ID, new char[0])).close();
+        root.refresh();
+
+        Authorizable a = getUserManager(root).getAuthorizable(USER_ID);
+        assertNotNull(a);
+        assertTrue(a.hasProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES));
+        externalUserPath = a.getPath();
+    }
+
+    @Override
+    protected DefaultSyncConfig createSyncConfig() {
+        DefaultSyncConfig config = super.createSyncConfig();
+        config.user().setDynamicMembership(true);
+        return config;
+    }
+
+    @Test
+    public void testRemoveRepExternalIdAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        try {
+            NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
+
+            n.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+            systemRoot.commit();
+            fail("Removing rep:externalId is not allowed if rep:externalPrincipalNames is present.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(73, e.getCode());
+        } finally {
+            systemRoot.refresh();
+        }
+    }
+
+    @Test
+    public void testAddExternalPrincipalNames() throws Exception {
+        Tree userTree = root.getTree(testUserPath);
+        NodeUtil userNode = new NodeUtil(userTree);
+        try {
+            userNode.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalName");
+            root.commit();
+            fail("Creating rep:externalPrincipalNames must be detected.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(70, e.getCode());
+        } finally {
+            root.refresh();
+        }
+    }
+
+    @Test
+    public void testAddExternalPrincipalNamesAsSystemMissingExternalId() throws Exception {
+        Root systemRoot = getSystemRoot();
+        try {
+            NodeUtil n = new NodeUtil(systemRoot.getTree(testUserPath));
+            n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalName");
+            systemRoot.commit();
+            fail("Creating rep:externalPrincipalNames without rep:externalId must be detected.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(72, e.getCode());
+        } finally {
+            systemRoot.refresh();
+        }
+    }
+
+    @Test
+    public void testAddExternalPrincipalNamesAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        NodeUtil n = new NodeUtil(systemRoot.getTree(testUserPath));
+        n.setString(ExternalIdentityConstants.REP_EXTERNAL_ID, "externalId");
+        n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalName");
+        systemRoot.commit();
+    }
+
+    @Test
+    public void testRemoveExternalPrincipalNames() throws Exception {
+        Tree userTree = root.getTree(externalUserPath);
+        try {
+            userTree.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES);
+            root.commit();
+            fail("Removing rep:externalPrincipalNames must be detected.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(70, e.getCode());
+        } finally {
+            root.refresh();
+        }
+    }
+
+    @Test
+    public void testRemoveExternalPrincipalNamesAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
+
+        // removal with system root must succeed
+        n.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES);
+        systemRoot.commit();
+    }
+
+    @Test
+    public void testModifyExternalPrincipalNames() throws Exception {
+        Tree userTree = root.getTree(externalUserPath);
+        try {
+            userTree.setProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, Lists.newArrayList("principalNames"), Type.STRINGS);
+            root.commit();
+            fail("Changing rep:externalPrincipalNames must be detected.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(70, e.getCode());
+        } finally {
+            root.refresh();
+        }
+    }
+
+    @Test
+    public void testModifyExternalPrincipalNamesAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
+
+        // changing with system root must succeed
+        n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalNames");
+        systemRoot.commit();
+    }
+
+    @Test
+    public void testExternalPrincipalNamesType() throws Exception {
+        Root systemRoot = getSystemRoot();
+        Tree userTree = systemRoot.getTree(testUserPath);
+
+        java.util.Map<Type, Object> valMap = ImmutableMap.<Type, Object>of(
+                Type.BOOLEANS, ImmutableSet.of(Boolean.TRUE),
+                Type.LONGS, ImmutableSet.of(new Long(1234)),
+                Type.NAMES, ImmutableSet.of("id", "id2")
+        );
+        for (Type t : valMap.keySet()) {
+            Object val = valMap.get(t);
+            try {
+                userTree.setProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, val, t);
+                systemRoot.commit();
+                fail("Creating rep:externalPrincipalNames with type "+t+" must be detected.");
+            } catch (CommitFailedException e) {
+                // success
+                assertEquals(71, e.getCode());
+            } finally {
+                systemRoot.refresh();
+            }
+        }
+    }
+
+    @Test
+    public void testExternalPrincipalNamesSingle() throws Exception {
+        Root systemRoot = getSystemRoot();
+        try {
+            NodeUtil n = new NodeUtil(systemRoot.getTree(testUserPath));
+            n.setString(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "id");
+            systemRoot.commit();
+            fail("Creating rep:externalPrincipalNames as single STRING property must be detected.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(71, e.getCode());
+        } finally {
+            systemRoot.refresh();
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java Tue May 17 16:18:29 2016
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFactory;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.commit.MoveTracker;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.lifecycle.WorkspaceInitializer;
+import org.apache.jackrabbit.oak.spi.security.Context;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.AbstractExternalAuthTest;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncedIdentity;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfigImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class ExternalPrincipalConfigurationTest extends AbstractExternalAuthTest {
+
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+
+    private ExternalPrincipalConfiguration principalConfiguration;
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        principalConfiguration = new ExternalPrincipalConfiguration(getSecurityProvider());
+        context.registerInjectActivateService(principalConfiguration);
+    }
+
+    private void enable() {
+        context.registerService(DefaultSyncHandler.class, new DefaultSyncHandler(), ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, true));
+    }
+
+    private void assertIsEnabled(ExternalPrincipalConfiguration externalPrincipalConfiguration, boolean expected) throws Exception {
+        PrincipalProvider pp = externalPrincipalConfiguration.getPrincipalProvider(root, getNamePathMapper());
+        assertEquals(expected, pp instanceof ExternalGroupPrincipalProvider);
+    }
+
+    @Test
+    public void testGetPrincipalManager() {
+        assertNotNull(principalConfiguration.getPrincipalManager(root, NamePathMapper.DEFAULT));
+    }
+
+    @Test
+    public void testGetPrincipalManagerEnabled() {
+        enable();
+        assertNotNull(principalConfiguration.getPrincipalManager(root, NamePathMapper.DEFAULT));
+    }
+
+    @Test
+    public void testGetPrincipalProvider() throws Exception {
+        PrincipalProvider pp = principalConfiguration.getPrincipalProvider(root, NamePathMapper.DEFAULT);
+        assertNotNull(pp);
+        assertFalse(pp instanceof ExternalGroupPrincipalProvider);
+
+    }
+
+    @Test
+    public void testGetPrincipalProviderEnabled() {
+        enable();
+        PrincipalProvider pp = principalConfiguration.getPrincipalProvider(root, NamePathMapper.DEFAULT);
+        assertNotNull(pp);
+        assertTrue(pp instanceof ExternalGroupPrincipalProvider);
+    }
+
+    @Test
+    public void testGetName() {
+        assertEquals(PrincipalConfiguration.NAME, principalConfiguration.getName());
+
+        enable();
+        assertEquals(PrincipalConfiguration.NAME, principalConfiguration.getName());
+    }
+
+    @Test
+    public void testGetContext() {
+        assertSame(Context.DEFAULT, principalConfiguration.getContext());
+
+        enable();
+        assertSame(Context.DEFAULT, principalConfiguration.getContext());
+    }
+
+    @Test
+    public void testGetWorkspaceInitializer() {
+        assertSame(WorkspaceInitializer.DEFAULT, principalConfiguration.getWorkspaceInitializer());
+
+        enable();
+        assertSame(WorkspaceInitializer.DEFAULT, principalConfiguration.getWorkspaceInitializer());
+    }
+
+    @Test
+    public void testGetRepositoryInitializer() {
+        assertTrue(principalConfiguration.getRepositoryInitializer() instanceof ExternalIdentityRepositoryInitializer);
+
+        enable();
+        assertTrue(principalConfiguration.getRepositoryInitializer() instanceof ExternalIdentityRepositoryInitializer);
+    }
+
+    @Test
+    public void testGetValidators() {
+        ContentSession cs = root.getContentSession();
+        List<? extends ValidatorProvider> validatorProviders = principalConfiguration.getValidators(cs.getWorkspaceName(), cs.getAuthInfo().getPrincipals(), new MoveTracker());
+
+        assertFalse(validatorProviders.isEmpty());
+        assertEquals(1, validatorProviders.size());
+        assertTrue(validatorProviders.get(0) instanceof ExternalIdentityValidatorProvider);
+
+        validatorProviders = principalConfiguration.getValidators(cs.getWorkspaceName(), cs.getAuthInfo().getPrincipals(), new MoveTracker());
+        assertFalse(validatorProviders.isEmpty());
+        assertEquals(1, validatorProviders.size());
+        assertTrue(validatorProviders.get(0) instanceof ExternalIdentityValidatorProvider);
+
+        enable();
+
+        validatorProviders = principalConfiguration.getValidators(cs.getWorkspaceName(), cs.getAuthInfo().getPrincipals(), new MoveTracker());
+        assertFalse(validatorProviders.isEmpty());
+        assertEquals(1, validatorProviders.size());
+        assertTrue(validatorProviders.get(0) instanceof ExternalIdentityValidatorProvider);
+    }
+
+    @Test
+    public void testGetProtectedItemImporters() {
+        List<? extends ProtectedItemImporter> importers = principalConfiguration.getProtectedItemImporters();
+
+        assertFalse(importers.isEmpty());
+        assertEquals(1, importers.size());
+        assertTrue(importers.get(0) instanceof ExternalIdentityImporter);
+
+        enable();
+
+        importers = principalConfiguration.getProtectedItemImporters();
+        assertFalse(importers.isEmpty());
+        assertEquals(1, importers.size());
+        assertTrue(importers.get(0) instanceof ExternalIdentityImporter);
+    }
+
+    @Test
+    public void testAddingSyncHandler() throws Exception {
+        Map<String, Object> enableProps =  ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, true);
+        Map<String, Object> disableProps =  ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, false);
+
+        SyncHandler sh = new DefaultSyncHandler();
+        context.registerService(SyncHandler.class, sh, ImmutableMap.<String, Object>of());
+        assertIsEnabled(principalConfiguration, false);
+
+        context.registerService(SyncHandler.class, sh, disableProps);
+        assertIsEnabled(principalConfiguration, false);
+
+        context.registerService(SyncHandler.class, sh, enableProps);
+        assertIsEnabled(principalConfiguration, true);
+
+        context.registerService(DefaultSyncHandler.class, new DefaultSyncHandler(), enableProps);
+        assertIsEnabled(principalConfiguration, true);
+    }
+
+    @Test
+    public void testAddingCustomSyncHandler() throws Exception {
+        Map<String, Object> enableProps =  ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, true);
+
+        SyncHandler sh = new TestSyncHandler();
+        context.registerService(SyncHandler.class, sh, ImmutableMap.<String, Object>of());
+        assertIsEnabled(principalConfiguration, false);
+
+        context.registerService(SyncHandler.class, sh, enableProps);
+        assertIsEnabled(principalConfiguration, true);
+    }
+
+    @Ignore("TODO: mock doesn't reflect property-changes on the registration.")
+    @Test
+    public void testModifySyncHandler() throws Exception {
+        Dictionary<String, Object> enableProps =  new Hashtable(ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, true));
+        Dictionary<String, Object> disableProps =  new Hashtable(ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, false));
+
+        DefaultSyncHandler sh = new DefaultSyncHandler();
+        BundleContext bundleContext = context.bundleContext();
+
+        ServiceRegistration registration = bundleContext.registerService(DefaultSyncHandler.class.getName(), sh, disableProps);
+        assertIsEnabled(principalConfiguration, false);
+
+        registration.setProperties(enableProps);
+        assertIsEnabled(principalConfiguration, true);
+
+        registration.setProperties(disableProps);
+        assertIsEnabled(principalConfiguration, false);
+    }
+
+    @Test
+    public void testRemoveSyncHandler() throws Exception {
+        Dictionary<String, Object> enableProps =  new Hashtable(ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, true));
+        Dictionary<String, Object> disableProps =  new Hashtable(ImmutableMap.<String, Object>of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, false));
+
+        DefaultSyncHandler sh = new DefaultSyncHandler();
+        BundleContext bundleContext = context.bundleContext();
+
+        ServiceRegistration registration1 = bundleContext.registerService(DefaultSyncHandler.class.getName(), sh, enableProps);
+        ServiceRegistration registration2 = bundleContext.registerService(DefaultSyncHandler.class.getName(), sh, enableProps);
+        ServiceRegistration registration3 = bundleContext.registerService(DefaultSyncHandler.class.getName(), sh, disableProps);
+        assertIsEnabled(principalConfiguration, true);
+
+        registration2.unregister();
+        assertIsEnabled(principalConfiguration, true);
+
+        registration1.unregister();
+        assertIsEnabled(principalConfiguration, false);
+
+        registration3.unregister();
+        assertIsEnabled(principalConfiguration, false);
+    }
+
+    private static final class TestSyncHandler implements SyncHandler {
+
+        @Nonnull
+        @Override
+        public String getName() {
+            return "name";
+        }
+
+        @Nonnull
+        @Override
+        public SyncContext createContext(@Nonnull ExternalIdentityProvider idp, @Nonnull UserManager userManager, @Nonnull ValueFactory valueFactory) throws SyncException {
+            return new DefaultSyncContext(new DefaultSyncConfig(), idp, userManager, valueFactory);
+        }
+
+        @Override
+        public SyncedIdentity findIdentity(@Nonnull UserManager userManager, @Nonnull String id) throws RepositoryException {
+            return null;
+        }
+
+        @Override
+        public boolean requiresSync(@Nonnull SyncedIdentity identity) {
+            return false;
+        }
+
+        @Nonnull
+        @Override
+        public Iterator<SyncedIdentity> listIdentities(@Nonnull UserManager userManager) throws RepositoryException {
+            return Iterators.emptyIterator();
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java Tue May 17 16:18:29 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/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md Tue May 17 16:18:29 2016
@@ -18,25 +18,189 @@
 User and Group Synchronization : The Default Implementation
 --------------------------------------------------------------------------------
 
-### DefaultSyncHandler
+### Default Implementation of Sync API
+
+#### SyncManager
+
+The default implementation (`SyncManagerImpl`) is intended for use in an OSGi-base
+repository setup: it tracks all `SyncHandler` registered via OSGi.
+
+It can be used in non-OSGi environments by passing a `org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard`
+to the constructor.
+
+#### SyncHandler
 
 The [DefaultSyncHandler] 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.
+allow to specify the synchronization behavior (see below). Depending on the 
+configuration it chooses between two different `SyncContext` implementations.
+
+#### SyncContext
+
+Oak provides the following implementations of the [SyncContext] interface:
 
+- [DefaultSyncContext]: base implementation that synchronizes external user and group accounts into the repository
+- [DynamicSyncContext]: derived implementation that provides special handling for external groups.
+ 
+##### DefaultSyncContext
+
+All users/groups synchronized by this context will get the following properties set.
 These properties allow to run separate task for periodical update and make sure
 the authorizables can later on be identified as external users.
 
 - `rep:externalId` : This allows to identify the external users, know the associated IDP and distinguish them from others.
 - `rep:lastSynced` : Sync timestamp to mark the external user/group valid for the configurable time (to reduce expensive syncing). Once expired, they will be validated against the 3rd party system again.
 
+The [DefaultSyncContext] is exported as part of the 'basic' package space and
+may be used to provide custom implementations.
+
+##### DynamicSyncContext
+
+Extending from the [DefaultSyncContext] this implementation that provides special 
+handling  for external groups in case the [Dynamic Group Membership](#dynamic_membership) 
+option is enabled in the [Configuration](#configuration).
+
+In addition to the properties mentioned above this implementation will additionally create 
+a multivalued STRING property that caches the group principal names of the external 
+user accounts:
+
+- `rep:externalPrincipalNames` : Optional system-maintained property related to [Dynamic Group Membership](#dynamic_membership)
+
+#### SyncResult
+
+The [DefaultSyncResultImpl] is exported as part of the 'basic' package space 
+providing a simple `SyncResult` implementation based on a status and a [DefaultSyncedIdentity].
+
+
+#### SyncedIdentity
+
+The [DefaultSyncedIdentity] is exported as part of the 'basic' package space. It 
+maps the ID of a synchronized user/group account to the external identity references
+represented by [ExternalIdentityRef].
+
+
+<a name="dynamic_membership"/>
+### Dynamic Group Membership
+
+As of Oak 1.5.3 the default sync handler comes with an addition configuration 
+option that allows to enable dynamic group membership resolution for external users. 
+Enabling dynamic membership in the [DefaultSyncConfig] will change the way external
+groups are synchronized (see also [OAK-4101]). 
+
+The key benefits of dynamic membership resolution are:
+
+- avoiding duplicate user management effort wrt to membership handling both in the external IDP and the repository
+- ease principal resolution upon repository login
+
+#### SyncContext with Dynamic Membership
+
+With the default `SyncHandler` this configuration option will show the following 
+effects:
+
+- If enabled the handler will use an alternative [SyncContext] to synchronize external groups.
+- Instead of synchronizing groups into the user management, this [DynamicSyncContext]
+  will additionally set the property `rep:externalPrincipalNames` on the synchronized external user
+- `rep:externalPrincipalNames` is a system maintained multivalued property of type 
+  'STRING' storing the names of the `java.security.acl.Group`-principals a given 
+  external user is member of (both declared and inherited according to the configured
+  membership nesting depth)
+- External groups will no longer be synchronised into the repository's user management 
+  but will only be available as `Principal`s (see section _User Management_ below).
+  
+#### Effect of Dynamic Membership on other Security Modules
+  
+##### Principal Management
+
+The dynamic (principal) membership features comes with a dedicated `PrincipalConfiguration` 
+implementation (i.e. [ExternalPrincipalConfiguration]) that is in charge of securing  
+the `rep:externalPrincipalNames` properties (see also section [Validation](#validation) 
+and [Configuration](#configuration) below). 
+
+Additionally the [ExternalPrincipalConfiguration] provides a `PrincipalProvider` 
+implementation which makes external (group) principals available to the repository's 
+authentication and authorization using the `rep:externalPrincipalNames` as a 
+persistent cache to avoid expensive lookup on the IDP.
+This also makes external `Principal`s retrievable and searchable through the 
+Jackrabbit principal management API (see section [Principal Management](../principal.html)
+for a comprehensive description).
+
+Please note the following implementation detail wrt accessibility of group principals:
+A given external principal will be accessible though the principal management API 
+if it can be read from any of the `rep:externalPrincipalNames` properties 
+present using a dedicated query.
+
+##### User Management
+
+As described above the dynamic membership option will effectively disable the
+synchronization of the complete external group account information into the repository's
+user management feature but limit the synchronized information to the principal 
+names and the membership relation between a given `java.security.acl.Group` principal 
+and external user accounts.
+
+The user management API will consequently no longer be knowledgeable of external 
+group identities (exception: groups that have been synchronized before enabling 
+the feature will remain untouched and will be synchronized according to the 
+sync configuration).
+
+While this behavior does not affect default authentication and authorization modules 
+(see below) it will have an impact on applications that rely on full synchronization 
+of external identities. Those application won't be able to benefit from the dynamic 
+membership feature until dynamic groups can be created with the 
+Jackrabbit [User Management API](../user.html) (see [OAK-2687]).
+
+##### Authentication
+
+The authentication setup provided by Oak is not affected by the dynamic membership 
+handling as long as the configured `LoginModule` implementations rely on the 
+`PrincipalProvider` for principal resolution and the [ExternalPrincipalConfiguration]
+is properly registered with the `SecurityProvider` (see section [Configuration](#configuration) below).
+
+##### Authorization
+
+The authorization modules shipped with Oak only depend on `Principal`s (and not on
+user management functionality) and are therefore not affected by the dynamic 
+membership configuration.
+
+<a name="xml_import"/>
+#### XML Import
+
+The protected nature of the `rep:externalPrincipalNames` is also reflected during
+XML import of user accounts:
+
+External users with a `rep:externalPrincipalNames` property will get regularly imported.
+However, any non-system driven import will omit the `rep:externalPrincipalNames` 
+and additional remove the `rep:lastSynced` property in order to force a re-sync
+of the external user by the system upon the next login or when triggered through
+the JMX console. Depending on the _User Dynamic Membership_ configuration value on
+the target system the sync will then result in a full sync of group membership or 
+will re-create the `rep:externalPrincipalNames` property.
+
+<a name="validation"/>
+#### Validation
+
+As of Oak 1.5.3 a dedicated `Validator` implementation asserts that the protected,
+system-maintained property `rep:externalPrincipalNames` is only written by the 
+internal system session. 
+
+This prevents users to unintentionally or maliciously manipulating the information
+linking to the external identity provider in particular their external identity
+and the set of external group principals associated with their account.
+
+Additionally the validator asserts the consistency of the properties defined
+with external user/group accounts.
+
+| Code              | Message                                                  |
+|-------------------|----------------------------------------------------------|
+| 0070              | Attempt to create, modify or remove the system property rep:externalPrincipalNames |
+| 0071              | Attempt to write rep:externalPrincipalNames with a type other than Type.STRINGS |
+| 0072              | Property rep:externalPrincipalNames requires rep:externalId to be present on the Node. |
+| 0073              | Property rep:externalId cannot be removed if rep:externalPrincipalNames is present. |
 
 <a name="configuration"/>
 ### Configuration
 
 #### Configuration of the DefaultSyncHandler
 
-The default `SyncHandler` implementation is configured via [DefaultSyncConfig]:
+The default `SyncHandler` implementations are configured via [DefaultSyncConfig]:
 
 | Name                          | Property                      | Description                              |
 |-------------------------------|-------------------------------|------------------------------------------|
@@ -45,6 +209,7 @@ The default `SyncHandler` implementation
 | 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 Dynamic Membership       | `user.dynamicMembership`      | Enabling dynamic membership for external users. |
 | 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 |
@@ -53,7 +218,28 @@ The default `SyncHandler` implementation
 | Group property mapping        | `group.propertyMapping`       | List mapping definition of local properties from external ones. |
 | | | |
 
+#### Configuration of the ExternalPrincipalConfiguration
+
+Please note that the [ExternalPrincipalConfiguration] comes with a dedicated
+`RepositoryInitializer`, which requires the repository to be (re)initialized
+once the module `oak-auth-external` is installed.
+
+The recommended way to assert a proper init, is to add 
+'org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal.ExternalPrincipalConfiguration'
+as additional value to the `requiredServicePids` configuration option of the 
+`SecurityProviderRegistration` _("Apache Jackrabbit Oak SecurityProvider")_.
+
+See section [Introduction to Oak Security](../introduction.html) for further details on the `SecurityProviderRegistration`.
 
 <!-- references -->
+[SyncContext]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.html
+[DefaultSyncContext]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.html
+[DefaultSyncConfig]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.html
+[DefaultSyncResultImpl]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncResultImpl.html
+[DefaultSyncedIdentity]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncedIdentity.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
+[ExternalIdentityRef]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityRef.html
+[ExternalPrincipalConfiguration]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.html
+[DynamicSyncContext]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/DynamicSyncContext.html
+[OAK-4101]: https://issues.apache.org/jira/browse/OAK-4101
+[OAK-2687]: https://issues.apache.org/jira/browse/OAK-2687
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/principal/principalprovider.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/principal/principalprovider.md?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/principal/principalprovider.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/principal/principalprovider.md Tue May 17 16:18:29 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

Modified: jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/authentication/external/AbstractExternalTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/authentication/external/AbstractExternalTest.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/authentication/external/AbstractExternalTest.java (original)
+++ jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/authentication/external/AbstractExternalTest.java Tue May 17 16:18:29 2016
@@ -55,9 +55,14 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIDPManagerImpl;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncManagerImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal.ExternalPrincipalConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.CompositePrincipalConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
  * Base benchmark test for external authentication.
  *
@@ -88,9 +93,12 @@ abstract class AbstractExternalTest exte
 
     protected AbstractExternalTest(int numberofUsers, int numberofGroups, long expTime, boolean dynamicMembership) {
         idp = new TestIdentityProvider(numberofUsers, numberofGroups);
-        syncConfig.user().setMembershipNestingDepth(1).setExpirationTime(expTime).setPathPrefix(PATH_PREFIX);
-        syncConfig.group().setExpirationTime(expTime).setPathPrefix(PATH_PREFIX);
-        // TODO OAK-4101 : syncConfig.user().setDynamicMembership(dynamicMembership);
+        syncConfig.user()
+                .setMembershipNestingDepth(1)
+                .setDynamicMembership(dynamicMembership)
+                .setExpirationTime(expTime).setPathPrefix(PATH_PREFIX);
+        syncConfig.group()
+                .setExpirationTime(expTime).setPathPrefix(PATH_PREFIX);
     }
 
     protected abstract Configuration createConfiguration();
@@ -158,15 +166,14 @@ abstract class AbstractExternalTest exte
     private final class TestSecurityProvider extends SecurityProviderImpl {
         public TestSecurityProvider(@Nonnull ConfigurationParameters configuration) {
             super(configuration);
-            //  TODO: enable once OAK-4104 is commited
-            //            PrincipalConfiguration principalConfiguration = getConfiguration(PrincipalConfiguration.class);
-            //            if (!(principalConfiguration instanceof CompositePrincipalConfiguration)) {
-            //                throw new IllegalStateException();
-            //            } else {
-            //                PrincipalConfiguration defConfig = checkNotNull(((CompositePrincipalConfiguration) principalConfiguration).getDefaultConfig());
-            //                bindPrincipalConfiguration((new ExternalPrincipalConfiguration(this)));
-            //                bindPrincipalConfiguration(defConfig);
-            //            }
+            PrincipalConfiguration principalConfiguration = getConfiguration(PrincipalConfiguration.class);
+            if (!(principalConfiguration instanceof CompositePrincipalConfiguration)) {
+                throw new IllegalStateException();
+            } else {
+                PrincipalConfiguration defConfig = checkNotNull(((CompositePrincipalConfiguration) principalConfiguration).getDefaultConfig());
+                bindPrincipalConfiguration((new ExternalPrincipalConfiguration(this)));
+                bindPrincipalConfiguration(defConfig);
+            }
         }
     }