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

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

Author: angela
Date: Tue May 17 16:18:29 2016
New Revision: 1744292

URL: http://svn.apache.org/viewvc?rev=1744292&view=rev
Log:
OAK-4101 : Consider separate external (group) principal management

Added:
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleDynamicMembershipTest.java
      - copied, changed from r1742077, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestSecurityProvider.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractPrincipalTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/EmptyPrincipalProviderTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java   (with props)
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.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/basic/DefaultSyncContextTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.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/basic/DefaultSyncConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java Tue May 17 16:18:29 2016
@@ -204,6 +204,8 @@ public class DefaultSyncConfig {
 
         private long membershipNestingDepth;
 
+        private boolean dynamicMembership;
+
         /**
          * Returns the duration in milliseconds until the group membership of a user is expired. If the
          * membership information is expired it is re-synced according to the maximum nesting depth.
@@ -251,6 +253,40 @@ public class DefaultSyncConfig {
             return this;
         }
 
+        /**
+         * Returns {@code true} if a dynamic group membership is enabled.
+         *
+         * Turning this option on may alter the behavior of other configuration
+         * options dealing with synchronization of group accounts and group membership.
+         * In particular it's an implementation detail if external groups may
+         * no longer be synchronized into the repository.
+         *
+         * @return {@code true} if dynamic group membership for external
+         * user identities is turn on; {@code false} otherwise.
+         */
+        @Nonnull
+        public boolean getDynamicMembership() {
+            return dynamicMembership;
+        }
+
+        /**
+         * Enable or disable the dynamic group membership. If turned external
+         * identities and their group membership will be synchronized such that the
+         * membership information is generated dynamically. External groups may
+         * or may not be synchronized into the repository if this option is turned
+         * on.
+         *
+         * @param dynamicMembership Boolean flag to enable or disable a dedicated
+         *                      dynamic group management.
+         * @return {@code this}
+         * @see #getDynamicMembership() for details.
+         */
+        @Nonnull
+        public User setDynamicMembership(boolean dynamicMembership) {
+            this.dynamicMembership = dynamicMembership;
+            return this;
+        }
+
     }
 
     /**

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java Tue May 17 16:18:29 2016
@@ -380,7 +380,7 @@ public class DefaultSyncContext implemen
                 principal,
                 PathUtils.concatRelativePaths(config.user().getPathPrefix(), externalUser.getIntermediatePath())
         );
-        user.setProperty(REP_EXTERNAL_ID, valueFactory.createValue(externalUser.getExternalId().getString()));
+        setExternalId(user, externalUser);
         return user;
     }
 
@@ -400,10 +400,24 @@ public class DefaultSyncContext implemen
                 principal,
                 PathUtils.concatRelativePaths(config.group().getPathPrefix(), externalGroup.getIntermediatePath())
         );
-        group.setProperty(REP_EXTERNAL_ID, valueFactory.createValue(externalGroup.getExternalId().getString()));
+        setExternalId(group, externalGroup);
         return group;
     }
 
+    /**
+     * Sets the {@link #REP_EXTERNAL_ID} as obtained from {@code externalIdentity}
+     * to the specified {@code authorizable} (user or group). The property is
+     * a single value of type {@link javax.jcr.PropertyType#STRING STRING}.
+     *
+     * @param authorizable The user or group that needs to get the {@link #REP_EXTERNAL_ID} property set.
+     * @param externalIdentity The {@link ExternalIdentity} from which to retrieve the value of the property.
+     * @throws RepositoryException If setting the property using {@link Authorizable#setProperty(String, Value)} fails.
+     */
+    private void setExternalId(@Nonnull Authorizable authorizable, @Nonnull ExternalIdentity externalIdentity) throws RepositoryException {
+        log.debug("Fallback: setting rep:externalId without adding the corresponding mixin type");
+        authorizable.setProperty(REP_EXTERNAL_ID, valueFactory.createValue(externalIdentity.getExternalId().getString()));
+    }
+
     @Nonnull
     protected DefaultSyncResultImpl syncUser(@Nonnull ExternalUser external, @Nonnull User user) throws RepositoryException {
         SyncResult.Status status;
@@ -728,7 +742,7 @@ public class DefaultSyncContext implemen
      * @return {@code true} if {@link ExternalIdentityRef#getProviderName()} refers
      * to the IDP associated with this context instance.
      */
-    private boolean isSameIDP(@Nonnull ExternalIdentityRef ref) {
+    protected boolean isSameIDP(@Nonnull ExternalIdentityRef ref) {
         return idp.getName().equals(ref.getProviderName());
     }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java Tue May 17 16:18:29 2016
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.0.1")
+@Version("1.1.0")
 @Export
 package org.apache.jackrabbit.oak.spi.security.authentication.external.basic;
 

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.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/DefaultSyncConfigImpl.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java Tue May 17 16:18:29 2016
@@ -152,6 +152,32 @@ public class DefaultSyncConfigImpl exten
     public static final String PARAM_USER_MEMBERSHIP_NESTING_DEPTH = "user.membershipNestingDepth";
 
     /**
+     * @see DefaultSyncConfig.User#getDynamicMembership()
+     */
+    public static final boolean PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT = false;
+
+    /**
+     * Configuration option to enable dynamic group membership. If enabled the
+     * implementation will no longer synchronized group accounts into the repository
+     * but instead will enable a dedicated principal management: This results in
+     * external users having their complete principal set as defined external IDP
+     * synchronized to the repository asserting proper population of the
+     * {@link javax.security.auth.Subject} upon login. Please note that the external
+     * groups are reflected through the built-in principal management and thus can
+     * be retrieved for authorization purposes. However, the information is no
+     * longer reflected through the Jackrabbit user management API.
+     *
+     * @see DefaultSyncConfig.User#getDynamicMembership()
+     */
+    @Property(
+            label = "User Dynamic Membership",
+            description = "If enabled membership of external identities (user) is no longer fully reflected " +
+                    "within the repositories user management.",
+            boolValue = PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT
+    )
+    public static final String PARAM_USER_DYNAMIC_MEMBERSHIP = "user.dynamicMembership";
+
+    /**
      * @see DefaultSyncConfig.Group#getExpirationTime()
      */
     public static final String PARAM_GROUP_EXPIRATION_TIME_DEFAULT = "1d";
@@ -229,6 +255,7 @@ public class DefaultSyncConfigImpl exten
         cfg.user()
                 .setMembershipExpirationTime(getMilliSeconds(params, PARAM_USER_MEMBERSHIP_EXPIRATION_TIME, PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT, ONE_HOUR))
                 .setMembershipNestingDepth(params.getConfigValue(PARAM_USER_MEMBERSHIP_NESTING_DEPTH, PARAM_USER_MEMBERSHIP_NESTING_DEPTH_DEFAULT))
+                .setDynamicMembership(params.getConfigValue(PARAM_USER_DYNAMIC_MEMBERSHIP, PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT))
                 .setExpirationTime(getMilliSeconds(params, PARAM_USER_EXPIRATION_TIME, PARAM_USER_EXPIRATION_TIME_DEFAULT, ONE_HOUR))
                 .setPathPrefix(params.getConfigValue(PARAM_USER_PATH_PREFIX, PARAM_USER_PATH_PREFIX_DEFAULT))
                 .setAutoMembership(params.getConfigValue(PARAM_USER_AUTO_MEMBERSHIP, PARAM_USER_AUTO_MEMBERSHIP_DEFAULT))

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.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/DefaultSyncHandler.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java Tue May 17 16:18:29 2016
@@ -103,7 +103,11 @@ public class DefaultSyncHandler implemen
     @Override
     public SyncContext createContext(@Nonnull ExternalIdentityProvider idp, @Nonnull UserManager userManager,
                                      @Nonnull ValueFactory valueFactory) throws SyncException {
-        return new DefaultSyncContext(config, idp, userManager, valueFactory);
+        if (config.user().getDynamicMembership()) {
+            return new DynamicSyncContext(config, idp, userManager, valueFactory);
+        } else {
+            return new DefaultSyncContext(config, idp, userManager, valueFactory);
+        }
     }
 
     /**

Added: 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=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java Tue May 17 16:18:29 2016
@@ -0,0 +1,162 @@
+/*
+ * 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;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncResultImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncedIdentity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extension of the {@code DefaultSyncContext} that doesn't synchronize group
+ * membership of new external users into the user management of the repository.
+ * Instead it will only synchronize the principal names up to the configured depths.
+ * In combination with the a dedicated {@code PrincipalConfiguration} this allows
+ * to benefit from the repository's authorization model (which is solely
+ * based on principals) i.e. full compatibility with the default approach without
+ * the complication of synchronizing user management information into the repository,
+ * when user management is effectively take care of by the third party system.
+ *
+ * With the {@link org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler}
+ * this feature can be turned on using
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig.User#setDynamicMembership(boolean)}
+ *
+ * Note: users and groups that have been synchronized before the dynamic membership
+ * feature has been enabled will continue to be synchronized in the default way
+ * and this context doesn't take effect.
+ *
+ * @since Oak 1.5.3
+ */
+public class DynamicSyncContext extends DefaultSyncContext {
+
+    private static final Logger log = LoggerFactory.getLogger(DynamicSyncContext.class);
+
+    public DynamicSyncContext(@Nonnull DefaultSyncConfig config,
+                              @Nonnull ExternalIdentityProvider idp,
+                              @Nonnull UserManager userManager,
+                              @Nonnull ValueFactory valueFactory) {
+        super(config, idp, userManager, valueFactory);
+    }
+
+    //--------------------------------------------------------< SyncContext >---
+    @Nonnull
+    @Override
+    public SyncResult sync(@Nonnull ExternalIdentity identity) throws SyncException {
+        if (identity instanceof ExternalUser) {
+            return super.sync(identity);
+        } else if (identity instanceof ExternalGroup) {
+            try {
+                Group group = getAuthorizable(identity, Group.class);
+                if (group != null) {
+                    // group has been synchronized before -> continue updating for consistency.
+                    return syncGroup((ExternalGroup) identity, group);
+                } else {
+                    // external group has never been synchronized before:
+                    // don't sync external groups into the repository internal user management
+                    // but limit synchronized information to group-principals stored
+                    // separately with each external user such that the subject gets
+                    // properly populated upon login
+                    ExternalIdentityRef ref = identity.getExternalId();
+
+                    log.debug("ExternalGroup {}: Not synchronized as authorizable Group into the repository.", ref.getString());
+
+                    SyncResult.Status status = (isSameIDP(ref)) ? SyncResult.Status.NOP : SyncResult.Status.FOREIGN;
+                    return new DefaultSyncResultImpl(new DefaultSyncedIdentity(identity.getId(), ref, true, -1), status);
+                }
+            } catch (RepositoryException e) {
+                throw new SyncException(e);
+            }
+        } else {
+            throw new IllegalArgumentException("identity must be user or group but was: " + identity);
+        }
+    }
+
+    //-------------------------------------------------< DefaultSyncContext >---
+    @Override
+    protected void syncMembership(@Nonnull ExternalIdentity external, @Nonnull Authorizable auth, long depth) throws RepositoryException {
+        if (auth.isGroup()) {
+            return;
+        }
+
+        if (auth.hasProperty(REP_LAST_SYNCED) && !auth.hasProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES)) {
+            // user has been synchronized before dynamic membership has been turned on
+            super.syncMembership(external, auth, depth);
+        } else {
+            // retrieve membership of the given external user (up to the configured
+            // depth) and add (or replace) the rep:externalPrincipalNames property
+            // with the accurate collection of principal names.
+            try {
+                Value[] vs;
+                if (depth <= 0) {
+                    vs = new Value[0];
+                } else {
+                    Set<String> principalsNames = new HashSet<String>();
+                    collectPrincipalNames(principalsNames, external.getDeclaredGroups(), depth);
+                    vs = createValues(principalsNames);
+                }
+                auth.setProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, vs);
+            } catch (ExternalIdentityException e) {
+                log.error("Failed to synchronize membership information for external identity " + external.getId(), e);
+            }
+        }
+    }
+
+    /**
+     * Recursively collect the principal names of the given declared group
+     * references up to the given depth.
+     *
+     * @param principalNames The set used to collect the names of the group principals.
+     * @param declaredGroupIdRefs The declared group references for a user or a group.
+     * @param depth Configured membership nesting; the recursion will be stopped once depths is < 1.
+     * @throws ExternalIdentityException If an error occurs while resolving the the external group references.
+     */
+    private void collectPrincipalNames(@Nonnull Set<String> principalNames, @Nonnull Iterable<ExternalIdentityRef> declaredGroupIdRefs, long depth) throws ExternalIdentityException {
+        for (ExternalIdentityRef ref : declaredGroupIdRefs) {
+            // get group
+            ExternalIdentity extId = idp.getIdentity(ref);
+            if (extId instanceof ExternalGroup) {
+                principalNames.add(extId.getPrincipalName());
+                // recursively apply further membership until the configured depth is reached
+                if (depth > 1) {
+                    collectPrincipalNames(principalNames, extId.getDeclaredGroups(), depth - 1);
+                }
+            } else {
+                log.debug("Not an external group ({}) => ignore.", extId);
+            }
+        }
+    }
+}
\ 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/DynamicSyncContext.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.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/ExternalIdentityConstants.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java Tue May 17 16:18:29 2016
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
+
+/**
+ * Constants used by the external identity management.
+ *
+ * @since Oak 1.5.3
+ */
+public interface ExternalIdentityConstants {
+
+    /**
+     * Name of the property storing the external identifier.
+     * This property is of type {@link org.apache.jackrabbit.oak.api.Type#STRING}
+     * and mandatory for external identities that have been synchronized into
+     * the repository.
+     *
+     * @see DefaultSyncContext#REP_EXTERNAL_ID
+     */
+    String REP_EXTERNAL_ID = DefaultSyncContext.REP_EXTERNAL_ID;
+
+    /**
+     * Name of the property storing the date of the last synchronization of an
+     * external identity.
+     * This property is of type {@link org.apache.jackrabbit.oak.api.Type#DATE}
+     *
+     * @see DefaultSyncContext#REP_LAST_SYNCED
+     */
+    String REP_LAST_SYNCED = DefaultSyncContext.REP_LAST_SYNCED;
+
+    /**
+     * Name of the property storing the principal names of the external groups
+     * a given external identity (user) is member. Not that the set depends on
+     * the configured nesting {@link DefaultSyncConfig.User#getMembershipNestingDepth() depth}.
+     * The existence of this property is optional and will only be created if
+     * {@link DefaultSyncConfig.User#getDynamicMembership()} is turned on.
+     *
+     * This property is of type {@link org.apache.jackrabbit.oak.api.Type#STRINGS}.
+     * Please note, that for security reasons is system maintained and protected
+     * on the Oak level and cannot be manipulated by regular {@code ContentSession}
+     * objects irrespective of the effective permissions.
+     */
+    String REP_EXTERNAL_PRINCIPAL_NAMES = "rep:externalPrincipalNames";
+
+    /**
+     * The set of served property names defined by this interface.
+     */
+    Set<String> RESERVED_PROPERTY_NAMES = ImmutableSet.of(REP_EXTERNAL_ID, REP_EXTERNAL_PRINCIPAL_NAMES);
+
+}
\ 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/ExternalIdentityConstants.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 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=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java Tue May 17 16:18:29 2016
@@ -0,0 +1,412 @@
+/*
+ * 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.security.acl.Group;
+import java.text.ParseException;
+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 javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.Query;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.QueryEngine;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+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;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the {@code PrincipalProvider} interface that exposes
+ * 'external' principals of type {@link java.security.acl.Group}. 'External'
+ * refers to the fact that these principals are defined and managed by an
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider}.
+ *
+ * For performance reasons this implementation doesn't lookup principals on the IDP
+ * but relies on a persisted cache inside the repository where the names of these
+ * external principals are synchronized to based on a configurable expiration time.
+ *
+ * Currently, the implementation respects the {@code rep:externalPrincipalNames}
+ * properties, where group membership of external users gets synchronized if
+ * {@link DefaultSyncConfig.User#getDynamicMembership() dynamic membership} has
+ * been enabled.
+ *
+ * Please note that in contrast to the default principal provider implementation
+ * shipped with Oak the group principals known and exposed by this provider are
+ * not backed by an authorizable group and thus cannot be retrieved using
+ * Jackrabbit user management API.
+ *
+ * @since Oak 1.5.3
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DynamicSyncContext
+ */
+class ExternalGroupPrincipalProvider implements PrincipalProvider, ExternalIdentityConstants {
+
+    private static final Logger log = LoggerFactory.getLogger(ExternalGroupPrincipalProvider.class);
+
+    private static final String BINDING_PRINCIPAL_NAMES = "principalNames";
+
+    private final Root root;
+    private final NamePathMapper namePathMapper;
+    private final UserManager userManager;
+
+    ExternalGroupPrincipalProvider(Root root, UserConfiguration uc, NamePathMapper namePathMapper) {
+        this.root = root;
+        this.namePathMapper = namePathMapper;
+        userManager = uc.getUserManager(root, namePathMapper);
+    }
+
+    //--------------------------------------------------< PrincipalProvider >---
+    @Override
+    public Principal getPrincipal(@Nonnull String principalName) {
+        Result result = findPrincipals(principalName, true);
+        if (result != null && result.getRows().iterator().hasNext()) {
+            return new ExternalGroupPrincipal(principalName);
+        } else {
+            return null;
+        }
+    }
+
+    @Nonnull
+    @Override
+    public Set<Group> getGroupMembership(@Nonnull Principal principal) {
+        if (!(principal instanceof Group)) {
+            try {
+                if (principal instanceof ItemBasedPrincipal) {
+                    Tree t = root.getTree(((ItemBasedPrincipal) principal).getPath());
+                    return getGroupPrincipals(t);
+                } else {
+                    return getGroupPrincipals(userManager.getAuthorizable(principal));
+                }
+            } catch (RepositoryException e) {
+                log.debug(e.getMessage());
+            }
+
+        }
+        return ImmutableSet.of();
+    }
+
+    @Nonnull
+    @Override
+    public Set<? extends Principal> getPrincipals(@Nonnull String userID) {
+        try {
+            return getGroupPrincipals(userManager.getAuthorizable(userID));
+        } catch (RepositoryException e) {
+            log.debug(e.getMessage());
+            return ImmutableSet.of();
+        }
+    }
+
+    @Nonnull
+    @Override
+    public Iterator<? extends Principal> findPrincipals(@Nullable String nameHint, int searchType) {
+        if (PrincipalManager.SEARCH_TYPE_NOT_GROUP != searchType) {
+            Result result = findPrincipals(Strings.nullToEmpty(nameHint), false);
+            if (result != null) {
+                return Iterators.filter(new GroupPrincipalIterator(nameHint, result), Predicates.notNull());
+            }
+        }
+
+        return Iterators.emptyIterator();
+    }
+
+    @Nonnull
+    @Override
+    public Iterator<? extends Principal> findPrincipals(int searchType) {
+        return findPrincipals(null, searchType);
+    }
+
+    //------------------------------------------------------------< private >---
+
+    private Set<Group> getGroupPrincipals(@CheckForNull Authorizable authorizable) throws RepositoryException {
+        if (authorizable != null && !authorizable.isGroup()) {
+            Tree userTree = root.getTree(authorizable.getPath());
+            return getGroupPrincipals(userTree);
+        } else {
+            return ImmutableSet.of();
+        }
+    }
+
+    private Set<Group> getGroupPrincipals(@Nonnull Tree userTree) {
+        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) {
+                Set<Group> groupPrincipals = Sets.newHashSet();
+                for (String principalName : ps.getValue(Type.STRINGS)) {
+                    groupPrincipals.add(new ExternalGroupPrincipal(principalName));
+                }
+                return groupPrincipals;
+            }
+        }
+        // group principals cannot be retrieved
+        return ImmutableSet.of();
+    }
+
+    /**
+     * Runs an Oak {@link org.apache.jackrabbit.oak.query.Query} searching for
+     * {@link #REP_EXTERNAL_PRINCIPAL_NAMES} properties that match the given
+     * name or name hint.
+     *
+     * @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.
+     * @return The query result.
+     */
+    @CheckForNull
+    private Result findPrincipals(@Nonnull String nameHint, boolean exactMatch) {
+        try {
+            Map<String, ? extends PropertyValue> bindings = buildBinding(nameHint, exactMatch);
+            String op = (exactMatch) ? " = " : " LIKE ";
+            String statement = "SELECT '" + REP_EXTERNAL_PRINCIPAL_NAMES + "' FROM [rep:User] WHERE PROPERTY(["
+                    + REP_EXTERNAL_PRINCIPAL_NAMES + "], '" + PropertyType.TYPENAME_STRING + "')"
+                    + op + "$" + BINDING_PRINCIPAL_NAMES + QueryEngine.INTERNAL_SQL2_QUERY;
+            return root.getQueryEngine().executeQuery(statement, Query.JCR_SQL2, bindings, namePathMapper.getSessionLocalMappings());
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Build the map used for the query bindings.
+     *
+     * @param nameHint The name hint
+     * @param exactMatch boolean flag indicating if the query should search for exact matching.
+     * @return the bindings
+     */
+    @Nonnull
+    private static Map<String, ? extends PropertyValue> buildBinding(@Nonnull String nameHint, boolean exactMatch) {
+        String val = nameHint;
+        if (!exactMatch) {
+            // not-exact query matching required => add leading and trailing %
+            if (nameHint.isEmpty()) {
+                val = "%";
+            } else {
+                StringBuilder sb = new StringBuilder();
+                sb.append('%');
+                sb.append(nameHint.replace("%", "\\%").replace("_", "\\_"));
+                sb.append('%');
+                val = sb.toString();
+            }
+        }
+        return Collections.singletonMap(BINDING_PRINCIPAL_NAMES, PropertyValues.newString(val));
+    }
+
+    //------------------------------------------------------< inner classes >---
+
+    /**
+     * Implementation of the {@link Group} interface representing external group
+     * identities that are <strong>not</strong> represented as authorizable group
+     * in the repository's user management.
+     */
+    private final class ExternalGroupPrincipal extends PrincipalImpl implements java.security.acl.Group {
+
+        public ExternalGroupPrincipal(String principalName) {
+            super(principalName);
+
+        }
+
+        @Override
+        public boolean addMember(Principal user) {
+            if (isMember(user)) {
+                return false;
+            } else {
+                throw new UnsupportedOperationException("Adding members to external group principals is not supported.");
+            }
+        }
+
+        @Override
+        public boolean removeMember(Principal user) {
+            if (!isMember(user)) {
+                return false;
+            } else {
+                throw new UnsupportedOperationException("Removing members from external group principals is not supported.");
+            }
+        }
+
+        @Override
+        public boolean isMember(Principal member) {
+            if (member instanceof Group) {
+                return false;
+            }
+            try {
+                String name = getName();
+                if (member instanceof ItemBasedPrincipal) {
+                    Tree tree = root.getTree(((ItemBasedPrincipal) member).getPath());
+                    if (UserUtil.isType(tree, AuthorizableType.USER)) {
+                        PropertyState ps = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
+                        return (ps != null && Iterables.contains(ps.getValue(Type.STRINGS), name));
+                    }
+                } else {
+                    Authorizable a = userManager.getAuthorizable(member);
+                    if (a != null && !a.isGroup()) {
+                        Value[] vs = a.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
+                        if (vs != null) {
+                            for (Value v : vs) {
+                                if (name.equals(v.getString())) {
+                                    return true;
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (RepositoryException e) {
+                log.debug(e.getMessage());
+            }
+            return false;
+        }
+
+        @Override
+        public Enumeration<? extends Principal> members() {
+            Result result = findPrincipals(getName(), true);
+            if (result != null) {
+                return Iterators.asEnumeration(new MemberIterator(result));
+            } else {
+                return Iterators.asEnumeration(Iterators.<Principal>emptyIterator());
+            }
+        }
+    }
+
+    /**
+     * {@link Group} principal iterator converting the query results of
+     * {@link #findPrincipals(String, int)} and {@link #findPrincipals(int)}.
+     * Since each result row provides the values of the {@code PropertyState},
+     * which matched the query, this iterator needs to filter the individual
+     * property values.
+     *
+     * Additional the iterator keeps track of principal names that have already
+     * been served and will not return duplicates.
+     *
+     * @see #findPrincipals(String, int)
+     * @see #findPrincipals(int)
+     */
+    private final class GroupPrincipalIterator extends AbstractLazyIterator<Principal> {
+
+        private final Set<String> processed = new HashSet<String>();
+
+        private final String queryString;
+        private final Iterator<? extends ResultRow> rows;
+
+        private Iterator<String> propValues = Iterators.emptyIterator();
+
+        private GroupPrincipalIterator(@Nullable String queryString, @Nonnull Result queryResult) {
+            this.queryString = queryString;
+            rows = queryResult.getRows().iterator();
+        }
+
+        @Override
+        protected Principal getNext() {
+            if (!propValues.hasNext()) {
+                if (rows.hasNext()) {
+                    propValues = rows.next().getValue(REP_EXTERNAL_PRINCIPAL_NAMES).getValue(Type.STRINGS).iterator();
+                } else {
+                    propValues = Iterators.emptyIterator();
+                }
+            }
+            while (propValues.hasNext()) {
+                String principalName = propValues.next();
+                if (principalName != null && !processed.contains(principalName) && matchesQuery(principalName) ) {
+                    processed.add(principalName);
+                    return new ExternalGroupPrincipal(principalName);
+                }
+            }
+            return null;
+        }
+
+        private boolean matchesQuery(@Nonnull String principalName) {
+            if (queryString == null) {
+                return true;
+            } else {
+                return principalName.contains(queryString);
+            }
+        }
+    }
+
+    /**
+     * {@code Principal} iterator representing the members of a given
+     * {@link ExternalGroupPrincipal}. The members are collected through an
+     * Oak {@link org.apache.jackrabbit.oak.query.Query Query}.
+     *
+     * Note that the query result is subject to permission evaluation for
+     * the editing {@link Root} based on the accessibility of the individual
+     * {@link #REP_EXTERNAL_PRINCIPAL_NAMES} properties that contain the
+     * exact name of the external group principal.
+     *
+     * @see ExternalGroupPrincipal#members()
+     */
+    private final class MemberIterator extends AbstractLazyIterator<Principal> {
+
+        /**
+         * The query results containing the path of the user accounts
+         * (i.e. members) that contain the target group principal in the
+         * {@link #REP_EXTERNAL_PRINCIPAL_NAMES} property values.
+         */
+        private final Iterator<? extends ResultRow> rows;
+
+        private MemberIterator(@Nonnull Result queryResult) {
+            rows = queryResult.getRows().iterator();
+        }
+
+        @Override
+        protected Principal getNext() {
+            while (rows.hasNext()) {
+                String userPath = rows.next().getPath();
+                try {
+                    Authorizable authorizable = userManager.getAuthorizableByPath(userPath);
+                    if (authorizable != null) {
+                        return authorizable.getPrincipal();
+                    }
+                } catch (RepositoryException e) {
+                    log.debug("{}", e.getMessage());
+                }
+            }
+            return null;
+        }
+    }
+}
\ 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/principal/ExternalGroupPrincipalProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.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/ExternalIdentityImporter.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java Tue May 17 16:18:29 2016
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Session;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemPrincipal;
+import org.apache.jackrabbit.oak.spi.xml.PropInfo;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedPropertyImporter;
+import org.apache.jackrabbit.oak.spi.xml.ReferenceChangeTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the {@link ProtectedPropertyImporter} interface to properly
+ * handle reserved system maintained properties defined by this module.
+ *
+ * @since Oak 1.5.3
+ */
+class ExternalIdentityImporter implements ProtectedPropertyImporter, ExternalIdentityConstants {
+
+    private static final Logger log = LoggerFactory.getLogger(ExternalIdentityImporter.class);
+
+    private boolean isSystemSession;
+
+    //----------------------------------------------< ProtectedItemImporter >---
+    @Override
+    public boolean init(@Nonnull Session session, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper, boolean isWorkspaceImport, int uuidBehavior, @Nonnull ReferenceChangeTracker referenceTracker, @Nonnull SecurityProvider securityProvider) {
+        isSystemSession = root.getContentSession().getAuthInfo().getPrincipals().contains(SystemPrincipal.INSTANCE);
+        return true;
+    }
+
+    @Override
+    public void processReferences() {
+        // nothing to
+    }
+
+    //------------------------------------------< ProtectedPropertyImporter >---
+    /**
+     * Due to the fact that the reserved external-identity properties are
+     * not protected from a JCR (item definition) point of view, the handling
+     * of the system maintained properties needs to be postpone to the {@link #propertiesCompleted} call.
+     *
+     * @param parent The affected parent node.
+     * @param protectedPropInfo The {@code PropInfo} to be imported.
+     * @param def The property definition determined by the importer that
+     * calls this method.
+     * @return Always returns false.
+     */
+    @Override
+    public boolean handlePropInfo(@Nonnull Tree parent, @Nonnull PropInfo protectedPropInfo, @Nonnull PropertyDefinition def) {
+        return false;
+    }
+
+    /**
+     * Prevent 'rep:externalPrincipalNames' properties from being imported with a
+     * non-system session.
+     * Note: in order to make sure those properties are synchronized again upon
+     * the next login, 'rep:lastSynced' property gets removed as well.
+     *
+     * @param protectedParent The protected parent tree.
+     */
+    @Override
+    public void propertiesCompleted(@Nonnull Tree protectedParent) {
+        if (!isSystemSession) {
+            if (protectedParent.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
+                log.debug("Found reserved property " + REP_EXTERNAL_PRINCIPAL_NAMES + " managed by the system => Removed from imported scope.");
+                protectedParent.removeProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
+                // force creation of rep:externalPrincipalNames by removing the
+                // rep:lastSynced property as well.
+                protectedParent.removeProperty(REP_LAST_SYNCED);
+            }
+        }
+    }
+}
\ 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/principal/ExternalIdentityImporter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.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/ExternalIdentityRepositoryInitializer.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java Tue May 17 16:18:29 2016
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider;
+import org.apache.jackrabbit.oak.plugins.tree.RootFactory;
+import org.apache.jackrabbit.oak.spi.commit.CompositeEditorProvider;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of the {@code RepositoryInitializer} interface responsible for
+ * setting up query indices for the system maintained, protected properties defined
+ * by this module:
+ *
+ * <ul>
+ *     <li>Index Definition <i>externalPrincipalNames</i>: Indexing
+ *     {@link ExternalIdentityConstants#REP_EXTERNAL_PRINCIPAL_NAMES} properties.
+ *     This index is used by the {@link ExternalGroupPrincipalProvider} to lookup
+ *     and find principals stored in this property.</li>
+ * </ul>
+ *
+ * @since Oak 1.5.3
+ */
+class ExternalIdentityRepositoryInitializer implements RepositoryInitializer {
+
+    private static final Logger log = LoggerFactory.getLogger(ExternalIdentityRepositoryInitializer.class);
+
+    ExternalIdentityRepositoryInitializer() {
+    }
+
+    @Override
+    public void initialize(@Nonnull NodeBuilder builder) {
+        NodeState base = builder.getNodeState();
+        NodeStore store = new MemoryNodeStore(base);
+
+        String errorMsg = "Failed to initialize external identity content.";
+        try {
+
+            Root root = RootFactory.createSystemRoot(store,
+                    new EditorHook(new CompositeEditorProvider(new NamespaceEditorProvider(), new TypeEditorProvider())),
+                    null, null, null, null);
+
+            // create index definition for "rep:externalId" and "rep:externalPrincipalNames"
+            NodeUtil rootTree = checkNotNull(new NodeUtil(root.getTree("/")));
+            NodeUtil index = rootTree.getOrAddChild(IndexConstants.INDEX_DEFINITIONS_NAME, JcrConstants.NT_UNSTRUCTURED);
+
+            if (!index.hasChild("externalPrincipalNames")) {
+                NodeUtil definition = IndexUtils.createIndexDefinition(index, "externalPrincipalNames", false,
+                        new String[]{ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES}, null);
+                definition.setString("info", "Oak index used by the principal management provided by the external authentication module.");
+            }
+
+            if (root.hasPendingChanges()) {
+                root.commit();
+            }
+        } catch (RepositoryException e) {
+            log.error(errorMsg, e);
+            throw new RuntimeException(e);
+        } catch (CommitFailedException e) {
+            log.error(errorMsg, e);
+            throw new RuntimeException(e);
+        }
+
+        NodeState target = store.getRoot();
+        target.compareAgainstBaseState(base, new ApplyDiff(builder));
+    }
+}
\ 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/principal/ExternalIdentityRepositoryInitializer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.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/ExternalIdentityValidatorProvider.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java Tue May 17 16:18:29 2016
@@ -0,0 +1,121 @@
+/*
+ * 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.Set;
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.DefaultValidator;
+import org.apache.jackrabbit.oak.spi.commit.Validator;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemPrincipal;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * {@code ValidatorProvider} used to assure that the system maintained properties
+ * associated with external identities are only written by system sessions and
+ * are consistent.
+ *
+ * @since Oak 1.5.3
+ */
+class ExternalIdentityValidatorProvider extends ValidatorProvider implements ExternalIdentityConstants {
+
+    private final boolean isSystem;
+
+    ExternalIdentityValidatorProvider(@Nonnull Set<Principal> principals) {
+        isSystem = principals.contains(SystemPrincipal.INSTANCE);
+
+    }
+
+    private void checkAddModifyProperties(@Nonnull NodeState parent, @Nonnull String name, @Nonnull PropertyState propertyState) throws CommitFailedException {
+        if (REP_EXTERNAL_PRINCIPAL_NAMES.equals(name)) {
+            if (!isSystem) {
+                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, "Attempt to create, modify or remove the system property " + name);
+            }
+            Type<?> type = propertyState.getType();
+            if (!Type.STRINGS.equals(type) || !propertyState.isArray()) {
+                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 71, "Property rep:externalPrincipalNames must be multi-valued of type STRING.");
+            }
+            if (!parent.hasProperty(REP_EXTERNAL_ID)) {
+                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 72, "Property rep:externalPrincipalNames requires rep:externalId to be present on the Node.");
+            }
+        }
+    }
+
+    private void checkRemoveProperties(@Nonnull NodeState parent, @Nonnull String name) throws CommitFailedException {
+        if (RESERVED_PROPERTY_NAMES.contains(name)) {
+            if (REP_EXTERNAL_ID.equals(name) && parent.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
+                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 73, "Property rep:externalId cannot be removed as long as rep:externalPrincipalNames is present.");
+            }
+            if (REP_EXTERNAL_PRINCIPAL_NAMES.equals(name) && !isSystem) {
+                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, "Attempt to create, modify or remove the system property " + name);
+            }
+        }
+    }
+
+    @Override
+    protected Validator getRootValidator(@Nonnull NodeState before, @Nonnull NodeState after,
+                                         @Nonnull CommitInfo info) {
+        return new ExternalIdentityValidator(after);
+    }
+
+    private final class ExternalIdentityValidator extends DefaultValidator {
+
+        private final NodeState parent;
+
+        private ExternalIdentityValidator(@Nonnull NodeState parent) {
+            this.parent = parent;
+        }
+
+        @Override
+        public void propertyAdded(PropertyState after) throws CommitFailedException {
+            checkAddModifyProperties(parent, after.getName(), after);
+        }
+
+        @Override
+        public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException {
+            checkAddModifyProperties(parent, before.getName(), after);
+        }
+
+        @Override
+        public void propertyDeleted(PropertyState before) throws CommitFailedException {
+            checkRemoveProperties(parent, before.getName());
+        }
+
+        @Override
+        public Validator childNodeAdded(String name, NodeState after) throws CommitFailedException {
+            return new ExternalIdentityValidator(after);
+        }
+
+        @Override
+        public Validator childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException {
+            return new ExternalIdentityValidator(after);
+        }
+
+        @Override
+        public Validator childNodeDeleted(String name, NodeState before) throws CommitFailedException {
+            // removal of the parent node containing a reserved property must be possible
+            return null;
+        }
+    }
+}
\ 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/principal/ExternalIdentityValidatorProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 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=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java Tue May 17 16:18:29 2016
@@ -0,0 +1,238 @@
+/*
+ * 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.security.acl.Group;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.commit.MoveTracker;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationBase;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
+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.principal.PrincipalConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalManagerImpl;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Implementation of the {@code PrincipalConfiguration} interface that provides
+ * principal management for {@link Group principals} associated with
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity external identities}
+ * managed outside of the scope of the repository by an
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider}.
+ *
+ * @since Oak 1.5.3
+ * @see <a href="https://issues.apache.org/jira/browse/OAK-4101">OAK-4101</a>
+ */
+@Component(
+        metatype = true,
+        label = "Apache Jackrabbit Oak External PrincipalConfiguration",
+        immediate = true
+)
+@Service({PrincipalConfiguration.class, SecurityConfiguration.class})
+public class ExternalPrincipalConfiguration extends ConfigurationBase implements PrincipalConfiguration {
+
+    private SyncConfigTracker syncConfigTracker;
+
+    @SuppressWarnings("UnusedDeclaration")
+    public ExternalPrincipalConfiguration() {
+        super();
+    }
+
+    public ExternalPrincipalConfiguration(SecurityProvider securityProvider) {
+        super(securityProvider, securityProvider.getParameters(NAME));
+    }
+
+    //---------------------------------------------< PrincipalConfiguration >---
+    @Nonnull
+    @Override
+    public PrincipalManager getPrincipalManager(Root root, NamePathMapper namePathMapper) {
+        return new PrincipalManagerImpl(getPrincipalProvider(root, namePathMapper));
+    }
+
+    @Nonnull
+    @Override
+    public PrincipalProvider getPrincipalProvider(Root root, NamePathMapper namePathMapper) {
+        if (dynamicMembershipEnabled()) {
+            UserConfiguration uc = getSecurityProvider().getConfiguration(UserConfiguration.class);
+            return new ExternalGroupPrincipalProvider(root, uc, namePathMapper);
+        } else {
+            return EmptyPrincipalProvider.INSTANCE;
+        }
+    }
+
+    //----------------------------------------------< SecurityConfiguration >---
+    @Nonnull
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Nonnull
+    @Override
+    public RepositoryInitializer getRepositoryInitializer() {
+        return new ExternalIdentityRepositoryInitializer();
+    }
+
+    @Nonnull
+    @Override
+    public List<? extends ValidatorProvider> getValidators(@Nonnull String workspaceName, @Nonnull Set<Principal> principals, @Nonnull MoveTracker moveTracker) {
+        return ImmutableList.of(new ExternalIdentityValidatorProvider(principals));
+    }
+
+    @Nonnull
+    @Override
+    public List<ProtectedItemImporter> getProtectedItemImporters() {
+        return ImmutableList.<ProtectedItemImporter>of(new ExternalIdentityImporter());
+    }
+
+    //----------------------------------------------------< SCR integration >---
+    @SuppressWarnings("UnusedDeclaration")
+    @Activate
+    private void activate(BundleContext bundleContext, Map<String, Object> properties) {
+        setParameters(ConfigurationParameters.of(properties));
+        syncConfigTracker = new SyncConfigTracker(bundleContext);
+        syncConfigTracker.open();
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    @Deactivate
+    private void deactivate() {
+        if (syncConfigTracker != null) {
+            syncConfigTracker.close();
+        }
+    }
+
+    //------------------------------------------------------------< private >---
+
+    private boolean dynamicMembershipEnabled() {
+        return syncConfigTracker != null && syncConfigTracker.isEnabled;
+    }
+
+    /**
+     * Implementation of the {@code PrincipalProvider} interface that never
+     * returns any principals.
+     */
+    private final static class EmptyPrincipalProvider implements PrincipalProvider {
+
+        private static final PrincipalProvider INSTANCE = new EmptyPrincipalProvider();
+
+        private EmptyPrincipalProvider() {}
+
+        @Override
+        public Principal getPrincipal(@Nonnull String principalName) {
+            return null;
+        }
+
+        @Nonnull
+        @Override
+        public Set<Group> getGroupMembership(@Nonnull Principal principal) {
+            return ImmutableSet.of();
+        }
+
+        @Nonnull
+        @Override
+        public Set<? extends Principal> getPrincipals(@Nonnull String userID) {
+            return ImmutableSet.of();
+        }
+
+        @Nonnull
+        @Override
+        public Iterator<? extends Principal> findPrincipals(@Nullable String nameHint, int searchType) {
+            return Iterators.emptyIterator();
+        }
+
+        @Nonnull
+        @Override
+        public Iterator<? extends Principal> findPrincipals(int searchType) {
+            return Iterators.emptyIterator();
+        }
+    }
+
+    /**
+     * {@code ServiceTracker} to detect any {@link SyncHandler} that has
+     * dynamic membership enabled.
+     */
+    private static final class SyncConfigTracker extends ServiceTracker {
+
+        private Set<ServiceReference> enablingRefs = new HashSet<ServiceReference>();
+        private boolean isEnabled = false;
+
+        public SyncConfigTracker(BundleContext context) {
+            super(context, SyncHandler.class.getName(), null);
+        }
+
+        @Override
+        public Object addingService(ServiceReference reference) {
+            if (hasDynamicMembership(reference)) {
+                enablingRefs.add(reference);
+                isEnabled = true;
+            }
+            return super.addingService(reference);
+        }
+
+        @Override
+        public void modifiedService(ServiceReference reference, Object service) {
+            if (hasDynamicMembership(reference)) {
+                enablingRefs.add(reference);
+                isEnabled = true;
+            } else {
+                enablingRefs.remove(reference);
+                isEnabled = !enablingRefs.isEmpty();
+            }
+            super.modifiedService(reference, service);
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) {
+            enablingRefs.remove(reference);
+            isEnabled = !enablingRefs.isEmpty();
+            super.removedService(reference, service);
+        }
+
+        private static boolean hasDynamicMembership(ServiceReference reference) {
+            return PropertiesUtil.toBoolean(reference.getProperty(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP), DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT);
+        }
+    }
+}
\ 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/principal/ExternalPrincipalConfiguration.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java Tue May 17 16:18:29 2016
@@ -16,13 +16,17 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication.external;
 
+import java.security.PrivilegedExceptionAction;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.RepositoryException;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicates;
@@ -31,6 +35,10 @@ import com.google.common.collect.Sets;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
 import org.junit.After;
 import org.junit.Before;
@@ -50,6 +58,9 @@ public abstract class AbstractExternalAu
 
     private Set<String> ids;
 
+    private ContentSession systemSession;
+    private Root systemRoot;
+
     @Before
     public void before() throws Exception {
         super.before();
@@ -67,6 +78,10 @@ public abstract class AbstractExternalAu
             destroyIDP();
             idp = null;
 
+            if (systemSession != null) {
+                systemSession.close();
+            }
+
             // discard any pending changes
             root.refresh();
 
@@ -106,6 +121,13 @@ public abstract class AbstractExternalAu
         }), Predicates.notNull());
     }
 
+    @Override
+    protected SecurityProvider getSecurityProvider() {
+        if (securityProvider == null) {
+            securityProvider = new TestSecurityProvider(getSecurityConfigParameters());
+        }
+        return securityProvider;
+    }
 
     protected ExternalIdentityProvider createIDP() {
         return new TestIdentityProvider();
@@ -127,4 +149,17 @@ public abstract class AbstractExternalAu
         syncConfig.user().setMembershipNestingDepth(1);
         return syncConfig;
     }
+
+    protected Root getSystemRoot() throws Exception {
+        if (systemRoot == null) {
+            systemSession = Subject.doAs(SystemSubject.INSTANCE, new PrivilegedExceptionAction<ContentSession>() {
+                @Override
+                public ContentSession run() throws LoginException, NoSuchWorkspaceException {
+                    return getContentRepository().login(null, null);
+                }
+            });
+            systemRoot = systemSession.getLatestRoot();
+        }
+        return systemRoot;
+    }
 }
\ No newline at end of file