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);



Reply via email to