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/06/01 07:11:59 UTC

svn commit: r1746408 - in /jackrabbit/oak/trunk: oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/pr...

Author: angela
Date: Wed Jun  1 07:11:59 2016
New Revision: 1746408

URL: http://svn.apache.org/viewvc?rev=1746408&view=rev
Log:
OAK-4087 : Replace Sync of configured AutoMembership by Dynamic Principal Generation

Added:
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncHandlerMapping.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleAutoMembershipTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/PrincipalProviderAutoMembershipTest.java   (with props)
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md
      - copied, changed from r1746238, jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/dynamic.md
Removed:
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md
Modified:
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractPrincipalTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/usersync.md
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/principal/principalprovider.md
    jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/authentication/external/AbstractExternalTest.java

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java Wed Jun  1 07:11:59 2016
@@ -135,6 +135,11 @@ public class DynamicSyncContext extends
         }
     }
 
+    @Override
+    protected void applyMembership(@Nonnull Authorizable member, @Nonnull Set<String> groups) throws RepositoryException {
+        log.debug("Dynamic membership sync enabled => omit setting auto-membership for {} ", member.getID());
+    }
+
     /**
      * Recursively collect the principal names of the given declared group
      * references up to the given depth.

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java Wed Jun  1 07:11:59 2016
@@ -70,12 +70,12 @@ public class ExternalLoginModule extends
     /**
      * Name of the parameter that configures the name of the external identity provider.
      */
-    public static final String PARAM_IDP_NAME = "idp.name";
+    public static final String PARAM_IDP_NAME = SyncHandlerMapping.PARAM_IDP_NAME;
 
     /**
      * Name of the parameter that configures the name of the synchronization handler.
      */
-    public static final String PARAM_SYNC_HANDLER_NAME = "sync.handlerName";
+    public static final String PARAM_SYNC_HANDLER_NAME = SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME;
 
     private ExternalIdentityProviderManager idpManager;
 

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java Wed Jun  1 07:11:59 2016
@@ -17,12 +17,12 @@
 package org.apache.jackrabbit.oak.spi.security.authentication.external.impl;
 
 import java.util.Hashtable;
-
 import javax.jcr.Repository;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 import javax.security.auth.spi.LoginModule;
 
+import com.google.common.collect.ImmutableMap;
 import org.apache.felix.jaas.LoginModuleFactory;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -43,8 +43,6 @@ import org.osgi.service.component.Compon
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.ImmutableMap;
-
 /**
  * Implements a LoginModuleFactory that creates {@link ExternalLoginModule}s and allows to configure login modules
  * via OSGi config.
@@ -56,7 +54,7 @@ import com.google.common.collect.Immutab
         configurationFactory = true
 )
 @Service
-public class ExternalLoginModuleFactory implements LoginModuleFactory {
+public class ExternalLoginModuleFactory implements LoginModuleFactory, SyncHandlerMapping {
 
     private static final Logger log = LoggerFactory.getLogger(ExternalLoginModuleFactory.class);
 
@@ -92,14 +90,14 @@ public class ExternalLoginModuleFactory
             label = "Identity Provider Name",
             description = "Name of the identity provider (for example: 'ldap')."
     )
-    public static final String PARAM_IDP_NAME = ExternalLoginModule.PARAM_IDP_NAME;
+    public static final String PARAM_IDP_NAME = SyncHandlerMapping.PARAM_IDP_NAME;
 
     @Property(
             value = "default",
             label = "Sync Handler Name",
             description = "Name of the sync handler."
     )
-    public static final String PARAM_SYNC_HANDLER_NAME = ExternalLoginModule.PARAM_SYNC_HANDLER_NAME;
+    public static final String PARAM_SYNC_HANDLER_NAME = SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME;
 
     @Reference
     private SyncManager syncManager;
@@ -120,6 +118,7 @@ public class ExternalLoginModuleFactory
      */
     private Registration mbeanRegistration;
 
+    //----------------------------------------------------< SCR integration >---
     /**
      * Activates the LoginModuleFactory service
      * @param context the component context

Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncHandlerMapping.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncHandlerMapping.java?rev=1746408&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncHandlerMapping.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncHandlerMapping.java Wed Jun  1 07:11:59 2016
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * Marker interface identifying classes that map a given
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler SyncHandler}
+ * to an {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider ExternalIdentityProvider}
+ * where both are identified by their name.
+ *
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager#getSyncHandler(String)
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler#getName()
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProviderManager#getProvider(String)
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider#getName()
+ * @see ExternalLoginModuleFactory
+ */
+public interface SyncHandlerMapping {
+
+    /**
+     * Name of the parameter that configures the name of the external identity provider.
+     */
+    String PARAM_IDP_NAME = "idp.name";
+
+    /**
+     * Name of the parameter that configures the name of the synchronization handler.
+     */
+    String PARAM_SYNC_HANDLER_NAME = "sync.handlerName";
+
+}
\ No newline at end of file

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

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java Wed Jun  1 07:11:59 2016
@@ -19,12 +19,14 @@ package org.apache.jackrabbit.oak.spi.se
 import java.security.Principal;
 import java.security.acl.Group;
 import java.text.ParseException;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -54,6 +56,7 @@ import org.apache.jackrabbit.oak.api.Tre
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
 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.spi.security.principal.PrincipalImpl;
@@ -95,12 +98,18 @@ class ExternalGroupPrincipalProvider imp
 
     private final Root root;
     private final NamePathMapper namePathMapper;
+
     private final UserManager userManager;
+    private final AutoMembershipPrincipals autoMembershipPrincipals;
 
-    ExternalGroupPrincipalProvider(Root root, UserConfiguration uc, NamePathMapper namePathMapper) {
+    ExternalGroupPrincipalProvider(@Nonnull Root root, @Nonnull UserConfiguration uc,
+                                   @Nonnull NamePathMapper namePathMapper,
+                                   @Nonnull Map<String, String[]> autoMembershipMapping) {
         this.root = root;
         this.namePathMapper = namePathMapper;
+
         userManager = uc.getUserManager(root, namePathMapper);
+        autoMembershipPrincipals = new AutoMembershipPrincipals(autoMembershipMapping);
     }
 
     //--------------------------------------------------< PrincipalProvider >---
@@ -164,6 +173,15 @@ class ExternalGroupPrincipalProvider imp
     }
 
     //------------------------------------------------------------< private >---
+    @CheckForNull
+    private String getIdpName(@Nonnull Tree userTree) {
+        PropertyState ps = userTree.getProperty(REP_EXTERNAL_ID);
+        if (ps != null) {
+            return ExternalIdentityRef.fromString(ps.getValue(Type.STRING)).getProviderName();
+        } else {
+            return null;
+        }
+    }
 
     private Set<Group> getGroupPrincipals(@CheckForNull Authorizable authorizable) throws RepositoryException {
         if (authorizable != null && !authorizable.isGroup()) {
@@ -178,10 +196,14 @@ class ExternalGroupPrincipalProvider imp
         if (userTree.exists() && UserUtil.isType(userTree, AuthorizableType.USER) && userTree.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
             PropertyState ps = userTree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
             if (ps != null) {
+                // we have an 'external' user that has been synchronized with the dynamic-membership option
                 Set<Group> groupPrincipals = Sets.newHashSet();
                 for (String principalName : ps.getValue(Type.STRINGS)) {
                     groupPrincipals.add(new ExternalGroupPrincipal(principalName));
                 }
+
+                // add existing group principals as defined with the _autoMembership_ option.
+                groupPrincipals.addAll(autoMembershipPrincipals.get(getIdpName(userTree)));
                 return groupPrincipals;
             }
         }
@@ -194,6 +216,10 @@ class ExternalGroupPrincipalProvider imp
      * {@link #REP_EXTERNAL_PRINCIPAL_NAMES} properties that match the given
      * name or name hint.
      *
+     * NOTE: ignore any principals listed in the {@link DefaultSyncConfig.User#autoMembership}
+     * because they are expected to exist in the system and thus will be found
+     * by another principal provider instance.
+     *
      * @param nameHint The principal name or name hint to be searched for.
      * @param exactMatch boolean flag indicating if the query should search for
      *                   exact matching.
@@ -409,4 +435,54 @@ class ExternalGroupPrincipalProvider imp
             return null;
         }
     }
+
+    private final class AutoMembershipPrincipals {
+
+        private final Map<String, String[]> autoMembershipMapping;
+        private final Map<String, Set<Group>> principalMap;
+
+        private AutoMembershipPrincipals(@Nonnull Map<String, String[]> autoMembershipMapping) {
+            this.autoMembershipMapping = autoMembershipMapping;
+            this.principalMap = new ConcurrentHashMap<String, Set<Group>>(autoMembershipMapping.size());
+        }
+
+        @Nonnull
+        private Collection<Group> get(@CheckForNull String idpName) {
+            if (idpName == null) {
+                return ImmutableSet.of();
+            }
+
+            Set<Group> principals;
+            if (!principalMap.containsKey(idpName)) {
+                String[] vs = autoMembershipMapping.get(idpName);
+                if (vs == null) {
+                    principals = ImmutableSet.of();
+                } else {
+                    ImmutableSet.Builder<Group> builder = ImmutableSet.builder();
+                    for (String groupId : autoMembershipMapping.get(idpName)) {
+                        try {
+                            Authorizable gr = userManager.getAuthorizable(groupId);
+                            if (gr != null && gr.isGroup()) {
+                                Principal grPrincipal = gr.getPrincipal();
+                                if (grPrincipal instanceof Group) {
+                                    builder.add((Group) grPrincipal);
+                                } else {
+                                    log.warn("Principal of group {} is not of type java.security.acl.Group -> Ignoring", groupId);
+                                }
+                            } else {
+                                log.warn("Configured auto-membership group {} does not exist -> Ignoring", groupId);
+                            }
+                        } catch (RepositoryException e) {
+                            log.debug("Failed to retrieved 'auto-membership' group with id {}", groupId, e);
+                        }
+                    }
+                    principals = builder.build();
+                }
+                principalMap.put(idpName, principals);
+            } else {
+                principals = principalMap.get(idpName);
+            }
+            return principals;
+        }
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java Wed Jun  1 07:11:59 2016
@@ -18,6 +18,8 @@ package org.apache.jackrabbit.oak.spi.se
 
 import java.security.Principal;
 import java.security.acl.Group;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -26,8 +28,11 @@ import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -46,6 +51,8 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfigImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModuleFactory;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncHandlerMapping;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalManagerImpl;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
@@ -54,6 +61,8 @@ import org.apache.jackrabbit.oak.spi.xml
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Implementation of the {@code PrincipalConfiguration} interface that provides
@@ -73,7 +82,10 @@ import org.osgi.util.tracker.ServiceTrac
 @Service({PrincipalConfiguration.class, SecurityConfiguration.class})
 public class ExternalPrincipalConfiguration extends ConfigurationBase implements PrincipalConfiguration {
 
+    private static final Logger log = LoggerFactory.getLogger(ExternalPrincipalConfiguration.class);
+
     private SyncConfigTracker syncConfigTracker;
+    private SyncHandlerMappingTracker syncHandlerMappingTracker;
 
     @SuppressWarnings("UnusedDeclaration")
     public ExternalPrincipalConfiguration() {
@@ -96,7 +108,7 @@ public class ExternalPrincipalConfigurat
     public PrincipalProvider getPrincipalProvider(Root root, NamePathMapper namePathMapper) {
         if (dynamicMembershipEnabled()) {
             UserConfiguration uc = getSecurityProvider().getConfiguration(UserConfiguration.class);
-            return new ExternalGroupPrincipalProvider(root, uc, namePathMapper);
+            return new ExternalGroupPrincipalProvider(root, uc, namePathMapper, syncConfigTracker.getAutoMembership());
         } else {
             return EmptyPrincipalProvider.INSTANCE;
         }
@@ -132,8 +144,13 @@ public class ExternalPrincipalConfigurat
     @Activate
     private void activate(BundleContext bundleContext, Map<String, Object> properties) {
         setParameters(ConfigurationParameters.of(properties));
-        syncConfigTracker = new SyncConfigTracker(bundleContext);
+        syncHandlerMappingTracker = new SyncHandlerMappingTracker(bundleContext);
+        syncHandlerMappingTracker.open();
+
+        syncConfigTracker = new SyncConfigTracker(bundleContext, syncHandlerMappingTracker);
         syncConfigTracker.open();
+
+
     }
 
     @SuppressWarnings("UnusedDeclaration")
@@ -142,6 +159,9 @@ public class ExternalPrincipalConfigurat
         if (syncConfigTracker != null) {
             syncConfigTracker.close();
         }
+        if (syncHandlerMappingTracker != null) {
+            syncHandlerMappingTracker.close();
+        }
     }
 
     //------------------------------------------------------------< private >---
@@ -196,11 +216,14 @@ public class ExternalPrincipalConfigurat
      */
     private static final class SyncConfigTracker extends ServiceTracker {
 
+        private final SyncHandlerMappingTracker mappingTracker;
+
         private Set<ServiceReference> enablingRefs = new HashSet<ServiceReference>();
         private boolean isEnabled = false;
 
-        public SyncConfigTracker(BundleContext context) {
+        public SyncConfigTracker(@Nonnull BundleContext context, @Nonnull SyncHandlerMappingTracker mappingTracker) {
             super(context, SyncHandler.class.getName(), null);
+            this.mappingTracker = mappingTracker;
         }
 
         @Override
@@ -234,5 +257,83 @@ public class ExternalPrincipalConfigurat
         private static boolean hasDynamicMembership(ServiceReference reference) {
             return PropertiesUtil.toBoolean(reference.getProperty(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP), DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT);
         }
+
+        private Map<String, String[]> getAutoMembership() {
+            Map<String, String[]> autoMembership = new HashMap<String, String[]>();
+            for (ServiceReference ref : enablingRefs) {
+                String syncHandlerName = PropertiesUtil.toString(ref.getProperty(DefaultSyncConfigImpl.PARAM_NAME), DefaultSyncConfigImpl.PARAM_NAME_DEFAULT);
+                String[] membership = PropertiesUtil.toStringArray(ref.getProperty(DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP), new String[0]);
+
+                for (String idpName : mappingTracker.getIdpNames(syncHandlerName)) {
+                    String[] previous = autoMembership.put(idpName, membership);
+                    if (previous != null) {
+                        String msg = (Arrays.equals(previous, membership)) ? "Duplicate" : "Colliding";
+                        log.debug(msg + " auto-membership configuration for IDP '{}'; replacing previous values {} by {} defined by SyncHandler '{}'",
+                                idpName, Arrays.toString(previous), Arrays.toString(membership), syncHandlerName);
+                    }
+                }
+            }
+            return autoMembership;
+        }
+    }
+
+    /**
+     * {@code ServiceTracker} to detect any {@link SyncHandler} that has
+     * dynamic membership enabled.
+     */
+    private static final class SyncHandlerMappingTracker extends ServiceTracker {
+
+        private Map<ServiceReference, String[]> referenceMap = new HashMap<ServiceReference, String[]>();
+
+        public SyncHandlerMappingTracker(@Nonnull BundleContext context) {
+            super(context, SyncHandlerMapping.class.getName(), null);
+        }
+
+        @Override
+        public Object addingService(ServiceReference reference) {
+            addMapping(reference);
+            return super.addingService(reference);
+        }
+
+        @Override
+        public void modifiedService(ServiceReference reference, Object service) {
+            addMapping(reference);
+            super.modifiedService(reference, service);
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) {
+            referenceMap.remove(reference);
+            super.removedService(reference, service);
+        }
+
+        private void addMapping(ServiceReference reference) {
+            String idpName = PropertiesUtil.toString(reference.getProperty(ExternalLoginModuleFactory.PARAM_IDP_NAME), null);
+            String syncHandlerName = PropertiesUtil.toString(reference.getProperty(ExternalLoginModuleFactory.PARAM_SYNC_HANDLER_NAME), null);
+
+            if (idpName != null && syncHandlerName != null) {
+                referenceMap.put(reference, new String[]{syncHandlerName, idpName});
+            } else {
+                log.warn("Ignoring SyncHandlerMapping with incomplete mapping of IDP '{}' and SyncHandler '{}'", idpName, syncHandlerName);
+            }
+        }
+
+        private Iterable<String> getIdpNames(@Nonnull final String syncHandlerName) {
+            return Iterables.filter(Iterables.transform(referenceMap.values(), new Function<String[], String>() {
+                        @Nullable
+                        @Override
+                        public String apply(@Nullable String[] input) {
+                            if (input != null && input.length == 2) {
+                                if (syncHandlerName.equals(input[0])) {
+                                    return input[1];
+                                } // else: different sync-handler
+                            } else {
+                                log.warn("Unexpected value of reference map. Expected String[] with length = 2");
+                            }
+                            return null;
+                        }
+                    }
+            ), Predicates.notNull());
+        }
     }
 }
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleAutoMembershipTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleAutoMembershipTest.java?rev=1746408&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleAutoMembershipTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleAutoMembershipTest.java Wed Jun  1 07:11:59 2016
@@ -0,0 +1,438 @@
+/*
+ * 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;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.ValueFactory;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+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.authentication.external.impl.ExternalLoginModule;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncHandlerMapping;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Rule;
+import org.junit.Test;
+
+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 ExternalLoginModuleAutoMembershipTest extends ExternalLoginModuleTestBase {
+
+    private static final String NON_EXISTING_NAME = "nonExisting";
+
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+
+    private Root r;
+    private UserManager userManager;
+    private ValueFactory valueFactory;
+
+    private ExternalSetup setup1;
+    private ExternalSetup setup2;
+    private ExternalSetup setup3;
+    private ExternalSetup setup4;
+    private ExternalSetup setup5;
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        r = getSystemRoot();
+        userManager = getUserManager(r);
+        valueFactory = getValueFactory(r);
+
+        syncConfig.user().setDynamicMembership(true);
+
+        // register the ExternalPrincipal configuration in order to have it's
+        // activate method invoked.
+        context.registerInjectActivateService(externalPrincipalConfiguration);
+
+        // first configuration based on test base-setup with
+        // - dynamic membership = true
+        // - auto-membership = 'gr_default' and 'nonExisting'
+        syncConfig.user().setDynamicMembership(true);
+        setup1 = new ExternalSetup(idp, syncConfig, WhiteboardUtils.getService(whiteboard, SyncHandler.class), "gr" + UUID.randomUUID());
+
+        // second configuration with different IDP ('idp2') and
+        // - dynamic membership = true
+        // - auto-membership = 'gr_name2' and 'nonExisting'
+        DefaultSyncConfig sc2 = new DefaultSyncConfig();
+        sc2.setName("name2").user().setDynamicMembership(true);
+        setup2 = new ExternalSetup(new TestIdentityProvider("idp2"), sc2);
+
+        // third configuration with different IDP  ('idp3') and
+        // - dynamic membership = false
+        // - auto-membership = 'gr_name3' and 'nonExisting'
+        DefaultSyncConfig sc3 = new DefaultSyncConfig();
+        sc3.setName("name3");
+        setup3 = new ExternalSetup(new TestIdentityProvider("idp3"), sc3);
+
+        // forth configuration based on different IDP ('idp4') but re-using
+        // sync-handler configuration (sc2)
+        setup4 = new ExternalSetup(new TestIdentityProvider("idp4"), sc2);
+
+        // fifth configuration with different IDP ('idp5') and
+        // - dynamic membership = true
+        // - auto-membership => nothing configured
+        DefaultSyncConfig sc5 = new DefaultSyncConfig();
+        sc5.setName("name5").user().setDynamicMembership(true);
+        setup5 = new ExternalSetup(new TestIdentityProvider("idp5"), sc5, new DefaultSyncHandler(sc5), null);
+    }
+
+    @Override
+    public void after() throws Exception {
+        try {
+            syncConfig.user().setAutoMembership().setExpirationTime(0);
+
+            setup1.close();
+            setup2.close();
+            setup3.close();
+            setup4.close();
+        } finally {
+            super.after();
+        }
+    }
+
+    @Override
+    protected Configuration getConfiguration() {
+        return new Configuration() {
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(String s) {
+                AppConfigurationEntry[] entries = new AppConfigurationEntry[5];
+                int i = 0;
+                for (ExternalSetup setup : new ExternalSetup[] {setup1, setup2, setup3, setup4, setup5}) {
+                    entries[i++] = setup.asConfigurationEntry();
+                }
+                return entries;
+            }
+        };
+    }
+
+    private static void registerSyncHandlerMapping(@Nonnull OsgiContext ctx, @Nonnull ExternalSetup setup) {
+        String syncHandlerName = setup.sc.getName();
+        Map props = ImmutableMap.of(
+                DefaultSyncConfigImpl.PARAM_NAME, syncHandlerName,
+                DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, setup.sc.user().getDynamicMembership(),
+                DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP, setup.sc.user().getAutoMembership());
+        ctx.registerService(SyncHandler.class, setup.sh, props);
+
+        Map mappingProps = ImmutableMap.of(
+                SyncHandlerMapping.PARAM_IDP_NAME, setup.idp.getName(),
+                SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME, syncHandlerName);
+        ctx.registerService(SyncHandlerMapping.class, new SyncHandlerMapping() {}, mappingProps);
+    }
+
+    @Test
+    public void testLoginSyncAutoMembershipSetup1() throws Exception {
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, new char[0]));
+
+            // the login must set the existing auto-membership principals to the subject
+            Set<Principal> principals = cs.getAuthInfo().getPrincipals();
+            assertTrue(principals.contains(setup1.gr.getPrincipal()));
+
+            assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
+            assertFalse(principals.contains(setup2.gr.getPrincipal()));
+            assertFalse(principals.contains(setup3.gr.getPrincipal()));
+
+            // however, the existing auto-membership group must _not_ have changed
+            // and the test user must not be a stored member of this group.
+            root.refresh();
+            UserManager uMgr = getUserManager(root);
+
+            User user = uMgr.getAuthorizable(USER_ID, User.class);
+            Group gr = uMgr.getAuthorizable(setup1.gr.getID(), Group.class);
+
+            assertFalse(gr.isDeclaredMember(user));
+            assertFalse(gr.isMember(user));
+        } finally {
+            options.clear();
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testLoginAfterSyncSetup1() throws Exception {
+        setup1.sync(USER_ID, false);
+
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, new char[0]));
+
+            // the login must set the configured + existing auto-membership principals
+            // to the subject; non-existing auto-membership entries must be ignored.
+            Set<Principal> principals = cs.getAuthInfo().getPrincipals();
+            assertTrue(principals.contains(setup1.gr.getPrincipal()));
+
+            assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
+            assertFalse(principals.contains(setup2.gr.getPrincipal()));
+            assertFalse(principals.contains(setup3.gr.getPrincipal()));
+
+            // however, the existing auto-membership group must _not_ have changed
+            // and the test user must not be a stored member of this group.
+            root.refresh();
+            UserManager uMgr = getUserManager(root);
+
+            User user = uMgr.getAuthorizable(USER_ID, User.class);
+            Group gr = uMgr.getAuthorizable(setup1.gr.getID(), Group.class);
+
+            assertFalse(gr.isDeclaredMember(user));
+            assertFalse(gr.isMember(user));
+        } finally {
+            options.clear();
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testLoginAfterSyncSetup2() throws Exception {
+        setup2.sync(USER_ID, false);
+
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, new char[0]));
+
+            // the login must set the existing auto-membership principals to the subject
+            Set<Principal> principals = cs.getAuthInfo().getPrincipals();
+            assertTrue(principals.contains(setup2.gr.getPrincipal()));
+
+            assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
+            assertFalse(principals.contains(setup1.gr.getPrincipal()));
+            assertFalse(principals.contains(setup3.gr.getPrincipal()));
+
+            // however, the existing auto-membership group must _not_ have changed
+            // and the test user must not be a stored member of this group.
+            root.refresh();
+            UserManager uMgr = getUserManager(root);
+
+            User user = uMgr.getAuthorizable(USER_ID, User.class);
+            Group gr = uMgr.getAuthorizable(setup2.gr.getID(), Group.class);
+
+            assertFalse(gr.isDeclaredMember(user));
+            assertFalse(gr.isMember(user));
+        } finally {
+            options.clear();
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testLoginAfterSyncSetup3() throws Exception {
+        setup3.sync(USER_ID, false);
+
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, new char[0]));
+
+            // the login must set the existing auto-membership principals to the subject
+            Set<Principal> principals = cs.getAuthInfo().getPrincipals();
+            assertTrue(principals.contains(setup3.gr.getPrincipal()));
+
+            assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
+            assertFalse(principals.contains(setup1.gr.getPrincipal()));
+            assertFalse(principals.contains(setup2.gr.getPrincipal()));
+
+            // however, the existing auto-membership group must _not_ have changed
+            // and the test user must not be a stored member of this group.
+            root.refresh();
+            UserManager uMgr = getUserManager(root);
+
+            User user = uMgr.getAuthorizable(USER_ID, User.class);
+            Group gr = uMgr.getAuthorizable(setup3.gr.getID(), Group.class);
+
+            assertTrue(gr.isDeclaredMember(user));
+            assertTrue(gr.isMember(user));
+        } finally {
+            options.clear();
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testLoginAfterSyncSetup4() throws Exception {
+        setup4.sync(USER_ID, false);
+
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, new char[0]));
+
+            // the login must set the existing auto-membership principals to the subject
+            Set<Principal> principals = cs.getAuthInfo().getPrincipals();
+            assertTrue(principals.contains(setup4.gr.getPrincipal()));
+            assertTrue(principals.contains(setup2.gr.getPrincipal()));
+
+            assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
+            assertFalse(principals.contains(setup1.gr.getPrincipal()));
+            assertFalse(principals.contains(setup3.gr.getPrincipal()));
+
+            // however, the existing auto-membership group must _not_ have changed
+            // and the test user must not be a stored member of this group.
+            root.refresh();
+            UserManager uMgr = getUserManager(root);
+
+            User user = uMgr.getAuthorizable(USER_ID, User.class);
+            Group gr = uMgr.getAuthorizable(setup4.gr.getID(), Group.class);
+
+            assertFalse(gr.isDeclaredMember(user));
+            assertFalse(gr.isMember(user));
+        } finally {
+            options.clear();
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testLoginAfterSyncSetup5() throws Exception {
+        setup5.sync(USER_ID, false);
+
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, new char[0]));
+
+            // the login must not set any auto-membership principals to the subject
+            // as auto-membership is not configured on this setup.
+            Set<Principal> principals = cs.getAuthInfo().getPrincipals();
+
+            Set<Principal> expected = ImmutableSet.of(EveryonePrincipal.getInstance(), userManager.getAuthorizable(USER_ID).getPrincipal());
+            assertEquals(expected, principals);
+
+            assertFalse(principals.contains(new PrincipalImpl(NON_EXISTING_NAME)));
+            assertFalse(principals.contains(setup1.gr.getPrincipal()));
+            assertFalse(principals.contains(setup2.gr.getPrincipal()));
+            assertFalse(principals.contains(setup3.gr.getPrincipal()));
+            assertFalse(principals.contains(setup4.gr.getPrincipal()));
+        } finally {
+            options.clear();
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    private final class ExternalSetup {
+
+        private final ExternalIdentityProvider idp;
+        private final Registration idpRegistration;
+
+        private final DefaultSyncConfig sc;
+        private final SyncHandler sh;
+        private final Registration shRegistration;
+
+        private final Group gr;
+
+        private SyncContext ctx;
+
+        private ExternalSetup(@Nonnull ExternalIdentityProvider idp, @Nonnull DefaultSyncConfig sc) throws Exception {
+            this(idp, sc, new DefaultSyncHandler(sc), "gr_" + sc.getName());
+        }
+
+        private ExternalSetup(@Nonnull ExternalIdentityProvider idp, @Nonnull DefaultSyncConfig sc, @Nonnull SyncHandler sh, @CheckForNull String groupId) throws Exception {
+            this.idp = idp;
+            this.sc = sc;
+            this.sh = sh;
+
+            if (groupId != null) {
+                Group g = userManager.getAuthorizable(groupId, Group.class);
+                if (g != null) {
+                    gr = g;
+                } else {
+                    gr = userManager.createGroup(groupId);
+                }
+                r.commit();
+
+                sc.user().setAutoMembership(gr.getID(), NON_EXISTING_NAME).setExpirationTime(Long.MAX_VALUE);
+            } else {
+                gr = null;
+            }
+
+            idpRegistration = whiteboard.register(ExternalIdentityProvider.class, idp, Collections.<String, Object>emptyMap());
+            shRegistration = whiteboard.register(SyncHandler.class, sh, ImmutableMap.of(
+                            DefaultSyncConfigImpl.PARAM_NAME, sh.getName(),
+                            DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, sc.user().getDynamicMembership(),
+                            DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP, sc.user().getAutoMembership()));
+            registerSyncHandlerMapping(context, this);
+        }
+
+        private void sync(@Nonnull String id, boolean isGroup) throws Exception {
+            ctx = sh.createContext(idp, userManager, valueFactory);
+            ExternalIdentity exIdentity = (isGroup) ? idp.getGroup(id) : idp.getUser(id);
+            assertNotNull(exIdentity);
+
+            SyncResult res = ctx.sync(exIdentity);
+            assertEquals(idp.getName(), res.getIdentity().getExternalIdRef().getProviderName());
+            assertSame(SyncResult.Status.ADD, res.getStatus());
+            r.commit();
+        }
+
+        private void close() {
+            if (ctx != null) {
+                ctx.close();
+            }
+            if (idpRegistration != null) {
+                idpRegistration.unregister();
+            }
+            if (shRegistration != null) {
+                shRegistration.unregister();
+            }
+        }
+
+        private AppConfigurationEntry asConfigurationEntry() {
+            return new AppConfigurationEntry(
+                    ExternalLoginModule.class.getName(),
+                    AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT,
+                    ImmutableMap.<String, String>of(
+                            SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME, sh.getName(),
+                            SyncHandlerMapping.PARAM_IDP_NAME, idp.getName()
+                    ));
+        }
+    }
+}
\ No newline at end of file

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

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java Wed Jun  1 07:11:59 2016
@@ -57,10 +57,10 @@ public abstract class ExternalLoginModul
 
         testIdpReg = whiteboard.register(ExternalIdentityProvider.class, idp, Collections.<String, Object>emptyMap());
 
-        options.put(ExternalLoginModule.PARAM_SYNC_HANDLER_NAME, "default");
-        options.put(ExternalLoginModule.PARAM_IDP_NAME, idp.getName());
-
         setSyncConfig(syncConfig);
+
+        options.put(ExternalLoginModule.PARAM_SYNC_HANDLER_NAME, syncConfig.getName());
+        options.put(ExternalLoginModule.PARAM_IDP_NAME, idp.getName());
     }
 
     @After

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java Wed Jun  1 07:11:59 2016
@@ -37,10 +37,20 @@ public class TestIdentityProvider implem
 
     public static final String ID_EXCEPTION = "throw!";
 
+    public static final String DEFAULT_IDP_NAME = "test";
+
     private final Map<String, ExternalGroup> externalGroups = new HashMap<String, ExternalGroup>();
     private final Map<String, ExternalUser> externalUsers = new HashMap<String, ExternalUser>();
 
+    private final String idpName;
+
     public TestIdentityProvider() {
+        this(DEFAULT_IDP_NAME);
+    }
+
+    public TestIdentityProvider(@Nonnull String idpName) {
+        this.idpName = idpName;
+
         addGroup(new TestGroup("aa", getName()));
         addGroup(new TestGroup("aaa", getName()));
         addGroup(new TestGroup("a", getName()).withGroups("aa", "aaa"));
@@ -82,7 +92,7 @@ public class TestIdentityProvider implem
     @Nonnull
     @Override
     public String getName() {
-        return "test";
+        return idpName;
     }
 
     @Override
@@ -151,11 +161,11 @@ public class TestIdentityProvider implem
         private final Map<String, Object> props = new HashMap<String, Object>();
 
         public TestIdentity() {
-            this("externalId", "principalName", "test");
+            this("externalId", "principalName", DEFAULT_IDP_NAME);
         }
 
         public TestIdentity(@Nonnull String userId) {
-            this(userId, userId, "test");
+            this(userId, userId, DEFAULT_IDP_NAME);
         }
 
         public TestIdentity(@Nonnull String userId, @Nonnull String principalName, @Nonnull String idpName) {

Modified: 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/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java?rev=1746408&r1=1746407&r2=1746408&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/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java Wed Jun  1 07:11:59 2016
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.spi.se
 
 import java.util.HashSet;
 import java.util.Set;
+import java.util.UUID;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.jcr.RepositoryException;
@@ -31,6 +32,7 @@ import com.google.common.collect.Iterabl
 import com.google.common.collect.Sets;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Root;
@@ -418,6 +420,21 @@ public class DynamicSyncContextTest exte
         assertFalse(r.hasPendingChanges());
     }
 
+    @Test
+    public void testAutoMembership() throws Exception {
+        Group gr = userManager.createGroup("group" + UUID.randomUUID());
+        r.commit();
+
+        syncConfig.user().setAutoMembership(gr.getID(), "non-existing-group");
+
+        SyncResult result = syncContext.sync(idp.getUser(USER_ID));
+        assertSame(SyncResult.Status.ADD, result.getStatus());
+
+        User u = userManager.getAuthorizable(USER_ID, User.class);
+        assertFalse(gr.isDeclaredMember(u));
+        assertFalse(gr.isMember(u));
+    }
+
     private static final class TestUserWithGroupRefs extends TestIdentityProvider.TestIdentity implements ExternalUser {
 
         private Iterable<ExternalIdentityRef> declaredGroupRefs;

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractPrincipalTest.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/AbstractPrincipalTest.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractPrincipalTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractPrincipalTest.java Wed Jun  1 07:11:59 2016
@@ -17,9 +17,11 @@
 package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
 
 import java.security.Principal;
+import java.util.Set;
 import java.util.UUID;
 import javax.annotation.Nonnull;
 
+import com.google.common.collect.ImmutableMap;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
@@ -63,7 +65,9 @@ public abstract class AbstractPrincipalT
 
     @Nonnull
     PrincipalProvider createPrincipalProvider() {
-        return new ExternalGroupPrincipalProvider(root, getSecurityProvider().getConfiguration(UserConfiguration.class), NamePathMapper.DEFAULT);
+        Set<String> autoMembership = syncConfig.user().getAutoMembership();
+        return new ExternalGroupPrincipalProvider(root, getSecurityProvider().getConfiguration(UserConfiguration.class),
+                NamePathMapper.DEFAULT, ImmutableMap.of(idp.getName(), autoMembership.toArray(new String[autoMembership.size()])));
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.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/ExternalGroupPrincipalProviderTest.java?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java Wed Jun  1 07:11:59 2016
@@ -54,7 +54,7 @@ import static org.junit.Assert.fail;
 
 public class ExternalGroupPrincipalProviderTest extends AbstractPrincipalTest {
 
-    private void syncWithMembership(@Nonnull ExternalUser externalUser, long depth) throws Exception {
+    void syncWithMembership(@Nonnull ExternalUser externalUser, long depth) throws Exception {
         DefaultSyncConfig sc = new DefaultSyncConfig();
         sc.user().setMembershipNestingDepth(depth);
 
@@ -69,7 +69,11 @@ public class ExternalGroupPrincipalProvi
         root.refresh();
     }
 
-    private Set<Principal> getDeclaredGroupPrincipals(@Nonnull String userId) throws ExternalIdentityException {
+    Set<Principal> getExpectedGroupPrincipals(@Nonnull String userId) throws Exception {
+        return getDeclaredGroupPrincipals(userId);
+    }
+
+    Set<Principal> getDeclaredGroupPrincipals(@Nonnull String userId) throws Exception {
         Set<Principal> principals = ImmutableSet.copyOf(Iterables.transform(idp.getUser(userId).getDeclaredGroups(), new Function<ExternalIdentityRef, Principal>() {
             @Nullable
             @Override
@@ -77,16 +81,24 @@ public class ExternalGroupPrincipalProvi
                 try {
                     return new PrincipalImpl(idp.getIdentity(input).getPrincipalName());
                 } catch (ExternalIdentityException e) {
-                    fail(e.getMessage());
-                    return null;
+                    throw new RuntimeException(e);
                 }
-            }
-
-            ;
+            };
         }));
         return principals;
     }
 
+    void collectExpectedPrincipals(Set<Principal> grPrincipals, @Nonnull Iterable<ExternalIdentityRef> declaredGroups, long depth) throws Exception {
+        if (depth <= 0) {
+            return;
+        }
+        for (ExternalIdentityRef ref : declaredGroups) {
+            ExternalIdentity ei = idp.getIdentity(ref);
+            grPrincipals.add(new PrincipalImpl(ei.getPrincipalName()));
+            collectExpectedPrincipals(grPrincipals, ei.getDeclaredGroups(), depth - 1);
+        }
+    }
+
     @Test
     public void testGetPrincipalLocalUser() throws Exception {
         assertNull(principalProvider.getPrincipal(getTestUser().getPrincipal().getName()));
@@ -234,7 +246,7 @@ public class ExternalGroupPrincipalProvi
         Authorizable user = getUserManager(root).getAuthorizable(USER_ID);
         assertNotNull(user);
 
-        Set<Principal> expected = getDeclaredGroupPrincipals(USER_ID);
+        Set<Principal> expected = getExpectedGroupPrincipals(USER_ID);
 
         Set<? extends Principal> principals = principalProvider.getGroupMembership(user.getPrincipal());
         assertEquals(expected, principals);
@@ -275,17 +287,6 @@ public class ExternalGroupPrincipalProvi
         assertEquals(expectedGrPrincipals, principals);
     }
 
-    private void collectExpectedPrincipals(Set<Principal> grPrincipals, @Nonnull Iterable<ExternalIdentityRef> declaredGroups, long depth) throws ExternalIdentityException {
-        if (depth <= 0) {
-            return;
-        }
-        for (ExternalIdentityRef ref : declaredGroups) {
-            ExternalIdentity ei = idp.getIdentity(ref);
-            grPrincipals.add(new PrincipalImpl(ei.getPrincipalName()));
-            collectExpectedPrincipals(grPrincipals, ei.getDeclaredGroups(), depth - 1);
-        }
-    }
-
     @Test
     public void testGetGroupMembershipExternalGroup() throws Exception {
         Authorizable group = getUserManager(root).getAuthorizable("secondGroup");
@@ -314,7 +315,7 @@ public class ExternalGroupPrincipalProvi
     @Test
     public void testGetPrincipalsExternalUser() throws Exception {
         Set<? extends Principal> principals = principalProvider.getPrincipals(USER_ID);
-        assertEquals(getDeclaredGroupPrincipals(USER_ID), principals);
+        assertEquals(getExpectedGroupPrincipals(USER_ID), principals);
     }
 
     @Test

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/PrincipalProviderAutoMembershipTest.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/PrincipalProviderAutoMembershipTest.java?rev=1746408&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/PrincipalProviderAutoMembershipTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/PrincipalProviderAutoMembershipTest.java Wed Jun  1 07:11:59 2016
@@ -0,0 +1,141 @@
+/*
+ * 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.security.Principal;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+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.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Extension of the {@link ExternalGroupPrincipalProviderTest} with 'automembership'
+ * configured in the {@link DefaultSyncConfig}.
+ */
+public class PrincipalProviderAutoMembershipTest extends ExternalGroupPrincipalProviderTest {
+
+    private static final String AUTO_MEMBERSHIP_GROUP_ID = "testGroup" + UUID.randomUUID();
+    private static final String AUTO_MEMBERSHIP_GROUP_PRINCIPAL_NAME = "p" + AUTO_MEMBERSHIP_GROUP_ID;
+    private static final String NON_EXISTING_GROUP_ID = "nonExistingGroup";
+
+    private Group autoMembershipGroup;
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        autoMembershipGroup = getUserManager(root).createGroup(AUTO_MEMBERSHIP_GROUP_ID, new PrincipalImpl(AUTO_MEMBERSHIP_GROUP_PRINCIPAL_NAME), null);
+        root.commit();
+    }
+
+    @Override
+    protected DefaultSyncConfig createSyncConfig() {
+        DefaultSyncConfig syncConfig = super.createSyncConfig();
+        syncConfig.user().setAutoMembership(AUTO_MEMBERSHIP_GROUP_ID, NON_EXISTING_GROUP_ID);
+
+        return syncConfig;
+    }
+
+    @Override
+    Set<Principal> getExpectedGroupPrincipals(@Nonnull String userId) throws Exception {
+        return ImmutableSet.<Principal>builder()
+                .addAll(super.getExpectedGroupPrincipals(userId))
+                .add(autoMembershipGroup.getPrincipal()).build();
+    }
+
+    @Override
+    void collectExpectedPrincipals(Set<Principal> grPrincipals, @Nonnull Iterable<ExternalIdentityRef> declaredGroups, long depth) throws Exception {
+        super.collectExpectedPrincipals(grPrincipals, declaredGroups, depth);
+        grPrincipals.add(autoMembershipGroup.getPrincipal());
+    }
+
+    @Test
+    public void testGetAutoMembershipPrincipal() throws Exception {
+        assertNull(principalProvider.getPrincipal(autoMembershipGroup.getPrincipal().getName()));
+        assertNull(principalProvider.getPrincipal(AUTO_MEMBERSHIP_GROUP_PRINCIPAL_NAME));
+        assertNull(principalProvider.getPrincipal(AUTO_MEMBERSHIP_GROUP_ID));
+        assertNull(principalProvider.getPrincipal(NON_EXISTING_GROUP_ID));
+    }
+
+    @Test
+    public void testGetGroupPrincipals() throws Exception {
+        ExternalUser externalUser = idp.getUser(USER_ID);
+        syncWithMembership(externalUser, 1);
+
+        Set<Principal> expected = getExpectedGroupPrincipals(USER_ID);
+
+        Authorizable user = getUserManager(root).getAuthorizable(USER_ID);
+
+        Set<java.security.acl.Group> result = principalProvider.getGroupMembership(user.getPrincipal());
+        assertTrue(result.contains(autoMembershipGroup.getPrincipal()));
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testGetPrincipals() throws Exception {
+        ExternalUser externalUser = idp.getUser(USER_ID);
+        syncWithMembership(externalUser, 1);
+
+        Set<Principal> expected = getExpectedGroupPrincipals(USER_ID);
+
+        Set<? extends Principal> result = principalProvider.getPrincipals(USER_ID);
+        assertTrue(result.contains(autoMembershipGroup.getPrincipal()));
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testFindPrincipalsByHint() throws Exception {
+        List<String> hints = ImmutableList.of(
+                AUTO_MEMBERSHIP_GROUP_PRINCIPAL_NAME,
+                AUTO_MEMBERSHIP_GROUP_ID,
+                AUTO_MEMBERSHIP_GROUP_PRINCIPAL_NAME.substring(1, 6));
+
+        for (String hint : hints) {
+            Iterator<? extends Principal> res = principalProvider.findPrincipals(hint, PrincipalManager.SEARCH_TYPE_GROUP);
+
+            assertFalse(Iterators.contains(res, autoMembershipGroup.getPrincipal()));
+            assertFalse(Iterators.contains(res, new PrincipalImpl(NON_EXISTING_GROUP_ID)));
+        }
+    }
+
+    @Test
+    public void testFindPrincipalsByTypeGroup() throws Exception {
+        Iterator<? extends Principal> res = principalProvider.findPrincipals(PrincipalManager.SEARCH_TYPE_GROUP);
+
+        assertFalse(Iterators.contains(res, autoMembershipGroup.getPrincipal()));
+        assertFalse(Iterators.contains(res, new PrincipalImpl(NON_EXISTING_GROUP_ID)));
+    }
+}
\ 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/PrincipalProviderAutoMembershipTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Copied: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md (from r1746238, 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/external/defaultusersync.md?p2=jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md&p1=jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md&r1=1746238&r2=1746408&rev=1746408&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/external/defaultusersync.md Wed Jun  1 07:11:59 2016
@@ -84,81 +84,10 @@ represented by [ExternalIdentityRef].
 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.
+groups are synchronized (see also [OAK-4101]).
+ 
+The details and effects on other security related modules are described in 
+section [Dynamic Membership](dynamic.html). 
 
 <a name="xml_import"/>
 #### XML Import

Added: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/dynamic.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/dynamic.md?rev=1746408&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/dynamic.md (added)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/dynamic.md Wed Jun  1 07:11:59 2016
@@ -0,0 +1,135 @@
+<!--
+   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.
+-->
+
+User and Group Synchronization : Dynamic Membership
+---------------------------------------------------
+
+As of Oak 1.5.3 the default sync handler comes with an additional configuration 
+option (see section [Configuration](defaultusersync.html#configuration) 
+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 [OAK-4101]) and how automatic group membership 
+is being handled (see [OAK-4087])
+
+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
+- avoid storing/updating auto-membership which is assigned to all external users
+- ease principal resolution upon repository login
+
+#### SyncContext with Dynamic Membership
+
+With the default `SyncHandler` this configuration option will show the following 
+effects:
+
+##### External Groups
+
+- If enabled the handler will use an alternative [SyncContext] to synchronize external groups ([DynamicSyncContext]).
+- 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).
+
+##### Automatic Membership
+
+- If enabled automatic membership assignment for existing, local groups will not longer be written to the repository
+- Instead the [ExternalPrincipalConfiguration] will keep track of the mapping 
+  between registered [SyncHandler]s (i.e. auto-membership configuration) and [ExternalIdentityProvider]s.
+  This allows to determine auto-membership based on the `rep:externalId` stored with the user accounts.
+- The `PrincipalProvider` associated with this dedicated principal configuration 
+  will expand the collection of `Principal`s generated for the following calls 
+  with the automatically assigned principals:
+    - `PrincipalProvider.getGroupMembership(Principal)`
+    - `PrincipalProvider.getPrincipals(String)`
+- Configured auto-membership groupIds that cannot be resolved to an existing
+  `o.a.j.api.security.user.Group` will be ignored in accordance to the default behavior.
+- Consequently, the `PrincipalProvider` relies on other `PrincipalProvider` 
+  implementations to _own_ these group principals and will not expose them
+  upon other calls (e.g.  `PrincipalProvider.getPrincipal(String)`.
+- Any changes to the auto-membership configuration will be immediately reflected 
+  to new instances of the `PrincipalProvider`.
+  
+#### 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](defaultusersync.html#validation) 
+and [Configuration](defaultusersync.html#configuration)). 
+
+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](defaultusersync.html#configuration)).
+
+##### 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.
+
+<!-- references -->
+[SyncHandler]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.html
+[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
+[ExternalIdentityProvider]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityProvider.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
+[OAK-4087]: https://issues.apache.org/jira/browse/OAK-4087
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/externalloginmodule.md Wed Jun  1 07:11:59 2016
@@ -64,7 +64,7 @@ if the user is not yet present in the lo
 check the credentials with the external system during the `login()` method.
 
 The details of the default user/group synchronization mechanism are described in section
-[User and Group Synchronization : The Default Implementation](defaultusersync.html)
+[User and Group Synchronization : The Default Implementation](external/defaultusersync.html)
 
 ##### Supported Credentials
 

Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/usersync.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/usersync.md?rev=1746408&r1=1746407&r2=1746408&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/usersync.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/usersync.md Wed Jun  1 07:11:59 2016
@@ -51,7 +51,7 @@ for the following tasks:
 Oak 1.0 provides a default implementation of the user synchronization API that allow
 to plug additional `SyncHandler` implementations. 
 
-Default implementation is described in section [User and Group Synchronization : The Default Implementation](defaultusersync.html).
+Default implementation is described in section [User and Group Synchronization : The Default Implementation](external/defaultusersync.html).
 
 ### Pluggability
 

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=1746408&r1=1746407&r2=1746408&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 Wed Jun  1 07:11:59 2016
@@ -79,7 +79,7 @@ principals on the IDP but relies on a pe
 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)
+See section [User and Group Synchronization : The Default Implementation](../authentication/external/defaultusersync.html)
 for additional details.
 
 Since Oak 1.5.3

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=1746408&r1=1746407&r2=1746408&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 Wed Jun  1 07:11:59 2016
@@ -57,6 +57,7 @@ import org.apache.jackrabbit.oak.spi.sec
 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.authentication.external.impl.ExternalIDPManagerImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncHandlerMapping;
 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;
@@ -66,7 +67,6 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
 import org.apache.sling.testing.mock.osgi.context.OsgiContextImpl;
-import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -189,8 +189,15 @@ abstract class AbstractExternalTest exte
 
                         // now register the sync-handler with the dynamic membership config
                         // in order to enable dynamic membership with the external principal configuration
-                        Map props = ImmutableMap.of(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, syncConfig.user().getDynamicMembership());
+                        Map props = ImmutableMap.of(
+                                DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP, syncConfig.user().getDynamicMembership(),
+                                DefaultSyncConfigImpl.PARAM_GROUP_AUTO_MEMBERSHIP, syncConfig.user().getAutoMembership());
                         context.registerService(SyncHandler.class, WhiteboardUtils.getService(whiteboard, SyncHandler.class), props);
+
+                        Map shMappingProps = ImmutableMap.of(
+                                SyncHandlerMapping.PARAM_IDP_NAME, idp.getName(),
+                                SyncHandlerMapping.PARAM_SYNC_HANDLER_NAME, syncConfig.getName());
+                        context.registerService(SyncHandlerMapping.class, new SyncHandlerMapping() {}, shMappingProps);
                     }
 
                     SecurityProvider sp = new TestSecurityProvider(ConfigurationParameters.EMPTY);