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 tr...@apache.org on 2014/04/15 19:19:21 UTC

svn commit: r1587638 - in /jackrabbit/oak/branches/1.0: oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/...

Author: tripod
Date: Tue Apr 15 17:19:20 2014
New Revision: 1587638

URL: http://svn.apache.org/r1587638
Log:
OAK-1711 Provide tools to manage externally synced users

Added:
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncResult.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncResultImpl.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncedIdentityImpl.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImpl.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java
    jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/add_user.sh   (with props)
    jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/del_user.sh   (with props)
Modified:
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityProvider.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
    jackrabbit/oak/branches/1.0/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java
    jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
    jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java

Modified: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityProvider.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityProvider.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityProvider.java Tue Apr 15 17:19:20 2014
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication.external;
 
+import java.util.Iterator;
+
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.jcr.Credentials;
@@ -78,4 +80,20 @@ public interface ExternalIdentityProvide
      */
     @CheckForNull
     ExternalGroup getGroup(@Nonnull String name) throws ExternalIdentityException;
+
+    /**
+     * List all external users.
+     * @return an iterator over all external users
+     * @throws ExternalIdentityException if an error occurs.
+     */
+    @Nonnull
+    Iterator<ExternalUser> listUsers() throws ExternalIdentityException;
+
+    /**
+     * List all external groups.
+     * @return an iterator over all external groups
+     * @throws ExternalIdentityException if an error occurs.
+     */
+    @Nonnull
+    Iterator<ExternalGroup> listGroups() throws ExternalIdentityException;
 }
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java Tue Apr 15 17:19:20 2014
@@ -25,23 +25,61 @@ import javax.annotation.Nonnull;
 public interface SyncContext {
 
     /**
+     * Defines if synchronization keeps missing external identities on synchronization of authorizables. Default
+     * is {@code false}.
+     * @return {@code true} if keep missing.
+     */
+    boolean isKeepMissing();
+
+    /**
+     * See {@link #isKeepMissing()}
+     */
+    @Nonnull
+    SyncContext setKeepMissing(boolean keep);
+
+    /**
+     * Defines if synchronization of users always will perform, i.e. ignores the last synced properties.
+     * @return {@code true} if forced syncing users
+     */
+    boolean isForceUserSync();
+
+    /**
+     * See {@link #isForceUserSync()}
+     */
+    @Nonnull
+    SyncContext setForceUserSync(boolean force);
+
+    /**
+     * Defines if synchronization of groups always will perform, i.e. ignores the last synced properties.
+     * @return {@code true} if forced syncing groups
+     */
+    boolean isForceGroupSync();
+
+    /**
+     * See {@link #isForceGroupSync()}
+     */
+    @Nonnull
+    SyncContext setForceGroupSync(boolean force);
+
+    /**
      * Synchronizes an external identity with the repository based on the respective configuration.
      *
      * @param identity the identity to sync.
-     * @return {@code true} if the given identity was synced; {@code false} for no change.
+     * @return the result of the operation
      * @throws SyncException if an error occurrs
      */
-    boolean sync(@Nonnull ExternalIdentity identity) throws SyncException;
+    SyncResult sync(@Nonnull ExternalIdentity identity) throws SyncException;
 
     /**
      * Synchronizes an authorizable with the corresponding external identity with the repository based on the respective
      * configuration.
      *
      * @param id the id of the authorizable
-     * @return {@code true} if the given identity was synced; {@code false} for no change.
+     * @return the result of the operation
      * @throws SyncException if an error occurrs
      */
-    boolean sync(@Nonnull String id) throws SyncException;
+    SyncResult sync(@Nonnull String id) throws SyncException;
+
 
     /**
      * Closes this context and releases any resources bound to it. Note that an implementation must not commit the
@@ -49,4 +87,5 @@ public interface SyncContext {
      * application.
      */
     void close();
+
 }
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java Tue Apr 15 17:19:20 2014
@@ -16,17 +16,22 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication.external;
 
+import java.util.Iterator;
+
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.jcr.RepositoryException;
+import javax.jcr.ValueFactory;
 
 import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.oak.api.Root;
+
+import com.google.common.base.Predicate;
 
 /**
  * SyncHandler is used to sync users and groups from an {@link ExternalIdentityProvider}.
  * The synchronization performed within the scope of a {@link SyncContext} which is acquired during the
- * {@link #createContext(ExternalIdentityProvider, org.apache.jackrabbit.api.security.user.UserManager, org.apache.jackrabbit.oak.api.Root)} call.
+ * {@link #createContext(ExternalIdentityProvider, org.apache.jackrabbit.api.security.user.UserManager, javax.jcr.ValueFactory)} call.
  *
  * The exact configuration is managed by the sync handler instance. The system may contain several sync handler
  * implementations with different configurations. those are managed by the {@link SyncManager}.
@@ -48,21 +53,31 @@ public interface SyncHandler {
      *
      * @param idp the external identity provider used for syncing
      * @param userManager user manager for managing authorizables
-     * @param root root of the current tree
+     * @param valueFactory the value factory to create values
      * @return the sync context
      * @throws SyncException if an error occurs
      */
     @Nonnull
     SyncContext createContext(@Nonnull ExternalIdentityProvider idp,
                               @Nonnull UserManager userManager,
-                              @Nonnull Root root) throws SyncException;
+                              @Nonnull ValueFactory valueFactory) throws SyncException;
 
     /**
      * Tries to find the identity with the given authorizable id or name.
      * @param userManager the user manager
      * @param id the id or name of the authorizable
      * @return a synced identity object or {@code null}
+     * @throws RepositoryException if an error occurs
      */
     @CheckForNull
     SyncedIdentity findIdentity(@Nonnull UserManager userManager, @Nonnull String id) throws RepositoryException;
+
+    /**
+     * Lists all externally synced identities.
+     * @param userManager the user manager
+     * @return an iterator over all authorizable that are externally synced.
+     * @throws RepositoryException if an error occurs
+     */
+    @Nonnull
+    Iterator<SyncedIdentity> listIdentities(@Nonnull UserManager userManager) throws RepositoryException;
 }
\ No newline at end of file

Added: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncResult.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncResult.java?rev=1587638&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncResult.java (added)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncResult.java Tue Apr 15 17:19:20 2014
@@ -0,0 +1,87 @@
+/*
+ * 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 javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Defines the result of a sync operation
+ */
+public interface SyncResult {
+
+    /**
+     * The synchronized identity
+     * @return the identity
+     */
+    @CheckForNull
+    SyncedIdentity getIdentity();
+
+    /**
+     * The status of the sync operation
+     * @return the status
+     */
+    @Nonnull
+    Status getStatus();
+
+    /**
+     * Result codes for sync operation.
+     */
+    enum Status {
+
+        /**
+         * No update
+         */
+        NOP,
+
+        /**
+         * authorizable added
+         */
+        ADD,
+
+        /**
+         * authorizable updated
+         */
+        UPDATE,
+
+        /**
+         * authorizable deleted
+         */
+        DELETE,
+
+        /**
+         * nothing changed. no such authorizable found.
+         */
+        NO_SUCH_AUTHORIZABLE,
+
+        /**
+         * nothing changed. no such identity found.
+         */
+        NO_SUCH_IDENTITY,
+
+        /**
+         * nothing changed. corresponding identity missing
+         */
+        MISSING,
+
+        /**
+         * nothing changed. idp name not correct
+         */
+        FOREIGN
+    }
+
+}
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.0/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/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java Tue Apr 15 17:19:20 2014
@@ -46,9 +46,7 @@ import org.apache.jackrabbit.api.securit
 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.Root;
-import org.apache.jackrabbit.oak.namepath.NamePathMapper;
-import org.apache.jackrabbit.oak.plugins.value.ValueFactoryImpl;
+import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
@@ -59,6 +57,7 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncContext;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncedIdentity;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
 import org.slf4j.Logger;
@@ -133,9 +132,9 @@ public class DefaultSyncHandler implemen
      */
     @Nonnull
     @Override
-    public SyncContext createContext(@Nonnull ExternalIdentityProvider idp, @Nonnull UserManager userManager, @Nonnull Root root)
-            throws SyncException {
-        return new ContextImpl(idp, userManager, root);
+    public SyncContext createContext(@Nonnull ExternalIdentityProvider idp, @Nonnull UserManager userManager,
+                                     @Nonnull ValueFactory valueFactory) throws SyncException {
+        return new ContextImpl(idp, userManager, valueFactory);
     }
 
     /**
@@ -144,17 +143,54 @@ public class DefaultSyncHandler implemen
     @Override
     public SyncedIdentity findIdentity(@Nonnull UserManager userManager, @Nonnull String id)
             throws RepositoryException {
-        Authorizable auth = userManager.getAuthorizable(id);
-        if (auth == null) {
+        return createSyncedIdentity(userManager.getAuthorizable(id));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<SyncedIdentity> listIdentities(@Nonnull UserManager userManager) throws RepositoryException {
+        final Iterator<Authorizable> iter = userManager.findAuthorizables("jcr:primaryType", null);
+        return new AbstractLazyIterator<SyncedIdentity>() {
+
+            @Override
+            protected SyncedIdentity getNext() {
+                while (iter.hasNext()) {
+                    try {
+                        SyncedIdentity id = createSyncedIdentity(iter.next());
+                        if (id != null) {
+                            return id;
+                        }
+                    } catch (RepositoryException e) {
+                        log.error("Error while fetching authorizables", e);
+                        break;
+                    }
+                }
+                return null;
+            }
+        };
+    }
+
+    /**
+     * Creates a synced identity from the given authorizable.
+     * @param auth the authorizable
+     * @return the id
+     * @throws RepositoryException if an error occurrs
+     */
+    @CheckForNull
+    private static SyncedIdentityImpl createSyncedIdentity(@Nullable Authorizable auth) throws RepositoryException {
+        ExternalIdentityRef ref = auth == null ? null : getIdentityRef(auth);
+        if (ref == null) {
             return null;
+        } else {
+            Value[] lmValues = auth.getProperty(REP_LAST_SYNCED);
+            long lastModified = -1;
+            if (lmValues != null && lmValues.length > 0) {
+                lastModified = lmValues[0].getLong();
+            }
+            return new SyncedIdentityImpl(auth.getID(), ref, auth.isGroup(), lastModified);
         }
-        ExternalIdentityRef ref = getIdentityRef(auth);
-        Value[] lmValues = auth.getProperty(REP_LAST_SYNCED);
-        long lastModified = -1;
-        if (lmValues != null && lmValues.length > 0) {
-            lastModified = lmValues[0].getLong();
-        }
-        return new SyncedIdentityImpl(id, ref, auth.isGroup(), lastModified);
     }
 
     /**
@@ -168,14 +204,20 @@ public class DefaultSyncHandler implemen
 
         private final ValueFactory valueFactory;
 
+        private boolean keepMissing;
+
+        private boolean forceUserSync;
+
+        private boolean forceGroupSync;
+
         // we use the same wall clock for the entire context
         private final long now;
         private final Value nowValue;
 
-        private ContextImpl(ExternalIdentityProvider idp, UserManager userManager, Root root) {
+        private ContextImpl(ExternalIdentityProvider idp, UserManager userManager, ValueFactory valueFactory) {
             this.idp = idp;
             this.userManager = userManager;
-            valueFactory = new ValueFactoryImpl(root, NamePathMapper.DEFAULT);
+            this.valueFactory = valueFactory;
 
             // initialize 'now'
             final Calendar nowCal = Calendar.getInstance();
@@ -195,16 +237,65 @@ public class DefaultSyncHandler implemen
          * {@inheritDoc}
          */
         @Override
-        public boolean sync(@Nonnull ExternalIdentity identity) throws SyncException {
+        public boolean isKeepMissing() {
+            return keepMissing;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public SyncContext setKeepMissing(boolean keepMissing) {
+            this.keepMissing = keepMissing;
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isForceUserSync() {
+            return forceUserSync;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public SyncContext setForceUserSync(boolean forceUserSync) {
+            this.forceUserSync = forceUserSync;
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isForceGroupSync() {
+            return forceGroupSync;
+        }
+
+        public SyncContext setForceGroupSync(boolean forceGroupSync) {
+            this.forceGroupSync = forceGroupSync;
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public SyncResult sync(@Nonnull ExternalIdentity identity) throws SyncException {
             try {
                 DebugTimer timer = new DebugTimer();
-                boolean ret;
+                SyncResultImpl ret;
+                boolean created = false;
                 if (identity instanceof ExternalUser) {
                     User user = getAuthorizable(identity, User.class);
                     timer.mark("find");
                     if (user == null) {
                         user = createUser((ExternalUser) identity);
                         timer.mark("create");
+                        created = true;
                     }
                     ret = syncUser((ExternalUser) identity, user);
                     timer.mark("sync");
@@ -214,6 +305,7 @@ public class DefaultSyncHandler implemen
                     if (group == null) {
                         group = createGroup((ExternalGroup) identity);
                         timer.mark("create");
+                        created = true;
                     }
                     ret = syncGroup((ExternalGroup) identity, group);
                     timer.mark("sync");
@@ -223,6 +315,9 @@ public class DefaultSyncHandler implemen
                 if (log.isDebugEnabled()) {
                     log.debug("sync({}) -> {} {}", identity.getExternalId().getString(), identity.getId(), timer.getString());
                 }
+                if (created) {
+                    ret.setStatus(SyncResult.Status.ADD);
+                }
                 return ret;
             } catch (RepositoryException e) {
                 throw new SyncException(e);
@@ -233,19 +328,19 @@ public class DefaultSyncHandler implemen
          * {@inheritDoc}
          */
         @Override
-        public boolean sync(@Nonnull String id) throws SyncException {
+        public SyncResult sync(@Nonnull String id) throws SyncException {
             try {
                 DebugTimer timer = new DebugTimer();
-                boolean ret = false;
+                SyncResultImpl ret;
                 // find authorizable
                 Authorizable auth = userManager.getAuthorizable(id);
                 if (auth == null) {
-                    return false;
+                    return new SyncResultImpl(new SyncedIdentityImpl(id, null, false, -1), SyncResult.Status.NO_SUCH_AUTHORIZABLE);
                 }
                 // check if we need to deal with this authorizable
                 ExternalIdentityRef ref = getIdentityRef(auth);
                 if (ref == null || !idp.getName().equals(ref.getProviderName())) {
-                    return false;
+                    return new SyncResultImpl(new SyncedIdentityImpl(id, null, false, -1), SyncResult.Status.FOREIGN);
                 }
 
                 if (auth instanceof Group) {
@@ -253,13 +348,18 @@ public class DefaultSyncHandler implemen
                     ExternalGroup external = idp.getGroup(id);
                     timer.mark("retrieve");
                     if (external == null) {
+                        SyncedIdentityImpl syncId = createSyncedIdentity(auth);
                         if (group.getDeclaredMembers().hasNext()) {
                             log.info("won't remove local group with members: {}", id);
-                        } else {
+                            ret = new SyncResultImpl(syncId, SyncResult.Status.NOP);
+                        } else if (!keepMissing) {
                             auth.remove();
                             log.debug("removing authorizable '{}' that no longer exists on IDP {}", id, idp.getName());
                             timer.mark("remove");
-                            ret = true;
+                            ret = new SyncResultImpl(syncId, SyncResult.Status.DELETE);
+                        } else {
+                            ret = new SyncResultImpl(syncId, SyncResult.Status.MISSING);
+                            log.info("external identity missing for {}, but purge == false.", id);
                         }
                     } else {
                         ret = syncGroup(external, group);
@@ -269,10 +369,16 @@ public class DefaultSyncHandler implemen
                     ExternalUser external = idp.getUser(id);
                     timer.mark("retrieve");
                     if (external == null) {
-                        auth.remove();
-                        log.debug("removing authorizable '{}' that no longer exists on IDP {}", id, idp.getName());
-                        timer.mark("remove");
-                        ret = true;
+                        SyncedIdentityImpl syncId = createSyncedIdentity(auth);
+                        if (!keepMissing) {
+                            auth.remove();
+                            log.debug("removing authorizable '{}' that no longer exists on IDP {}", id, idp.getName());
+                            timer.mark("remove");
+                            ret = new SyncResultImpl(syncId, SyncResult.Status.DELETE);
+                        } else {
+                            ret = new SyncResultImpl(syncId, SyncResult.Status.MISSING);
+                            log.info("external identity missing for {}, but purge == false.", id);
+                        }
                     } else {
                         ret = syncUser(external, (User) auth);
                         timer.mark("sync");
@@ -357,11 +463,11 @@ public class DefaultSyncHandler implemen
         }
 
 
-        private boolean syncUser(@Nonnull ExternalUser external, @Nonnull User user) throws RepositoryException {
+        private SyncResultImpl syncUser(@Nonnull ExternalUser external, @Nonnull User user) throws RepositoryException {
             // first check if user is expired
-            // todo: add "forceSync" property for potential background sync
-            if (!isExpired(user, config.user().getExpirationTime(), "Properties")) {
-                return false;
+            if (!forceUserSync && !isExpired(user, config.user().getExpirationTime(), "Properties")) {
+                SyncedIdentityImpl syncId = createSyncedIdentity(user);
+                return new SyncResultImpl(syncId, SyncResult.Status.NOP);
             }
 
             // synchronize the properties
@@ -377,14 +483,15 @@ public class DefaultSyncHandler implemen
 
             // finally "touch" the sync property
             user.setProperty(REP_LAST_SYNCED, nowValue);
-            return true;
+            SyncedIdentityImpl syncId = createSyncedIdentity(user);
+            return new SyncResultImpl(syncId, SyncResult.Status.UPDATE);
         }
 
-        private boolean syncGroup(ExternalGroup external, Group group) throws RepositoryException {
+        private SyncResultImpl syncGroup(ExternalGroup external, Group group) throws RepositoryException {
             // first check if user is expired
-            // todo: add "forceSync" property for potential background sync
-            if (!isExpired(group, config.group().getExpirationTime(), "Properties")) {
-                return false;
+            if (!forceGroupSync && !isExpired(group, config.group().getExpirationTime(), "Properties")) {
+                SyncedIdentityImpl syncId = createSyncedIdentity(group);
+                return new SyncResultImpl(syncId, SyncResult.Status.NOP);
             }
 
             // synchronize the properties
@@ -395,7 +502,8 @@ public class DefaultSyncHandler implemen
 
             // finally "touch" the sync property
             group.setProperty(REP_LAST_SYNCED, nowValue);
-            return true;
+            SyncedIdentityImpl syncId = createSyncedIdentity(group);
+            return new SyncResultImpl(syncId, SyncResult.Status.UPDATE);
         }
 
         /**
@@ -694,42 +802,4 @@ public class DefaultSyncHandler implemen
         return result.length() == 0 ? null : result.toString();
     }
 
-    private static class SyncedIdentityImpl implements SyncedIdentity {
-
-        private final String id;
-
-        private final ExternalIdentityRef ref;
-
-        private final boolean isGroup;
-
-        private final long lastSynced;
-
-        private SyncedIdentityImpl(String id, ExternalIdentityRef ref, boolean isGroup, long lastSynced) {
-            this.id = id;
-            this.ref = ref;
-            this.isGroup = isGroup;
-            this.lastSynced = lastSynced;
-        }
-
-        @Nonnull
-        @Override
-        public String getId() {
-            return id;
-        }
-
-        @Override
-        public ExternalIdentityRef getExternalIdRef() {
-            return ref;
-        }
-
-        @Override
-        public boolean isGroup() {
-            return false;
-        }
-
-        @Override
-        public long lastSynced() {
-            return lastSynced;
-        }
-    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.0/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/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java Tue Apr 15 17:19:20 2014
@@ -33,6 +33,8 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.oak.api.AuthInfo;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.value.ValueFactoryImpl;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule;
 import org.apache.jackrabbit.oak.spi.security.authentication.AuthInfoImpl;
@@ -300,7 +302,7 @@ public class ExternalLoginModule extends
             SyncContext context = null;
             try {
                 DebugTimer timer = new DebugTimer();
-                context = syncHandler.createContext(idp, userManager, root);
+                context = syncHandler.createContext(idp, userManager, new ValueFactoryImpl(root, NamePathMapper.DEFAULT));
                 context.sync(user);
                 timer.mark("sync");
                 root.commit();
@@ -337,7 +339,7 @@ public class ExternalLoginModule extends
                 throw new SyncException("Cannot synchronize user. userManager == null");
             }
             DebugTimer timer = new DebugTimer();
-            context = syncHandler.createContext(idp, userManager, root);
+            context = syncHandler.createContext(idp, userManager, new ValueFactoryImpl(root, NamePathMapper.DEFAULT));
             context.sync(id);
             timer.mark("sync");
             root.commit();

Modified: jackrabbit/oak/branches/1.0/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/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java Tue Apr 15 17:19:20 2014
@@ -16,17 +16,35 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication.external.impl;
 
-import java.util.Map;
+import java.util.Hashtable;
 
+import javax.jcr.Repository;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
 import javax.security.auth.spi.LoginModule;
 
 import org.apache.felix.jaas.LoginModuleFactory;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProviderManager;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.jmx.SyncMBeanImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.jmx.SynchronizationMBean;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.osgi.service.component.ComponentContext;
+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
@@ -41,6 +59,11 @@ import org.apache.jackrabbit.oak.spi.sec
 @Service
 public class ExternalLoginModuleFactory implements LoginModuleFactory {
 
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(ExternalLoginModuleFactory.class);
+
     @Property(
             intValue = 50,
             label = "JAAS Ranking",
@@ -79,18 +102,62 @@ public class ExternalLoginModuleFactory 
     )
     public static final String PARAM_SYNC_HANDLER_NAME = ExternalLoginModule.PARAM_SYNC_HANDLER_NAME;
 
+    @Reference
+    SyncManager syncManager;
+
+    @Reference
+    ExternalIdentityProviderManager idpManager;
+
+    @Reference
+    SecurityProvider securityProvider;
+
+    @Reference
+    Repository repository;
+
     /**
      * default configuration for the login modules
      */
     private ConfigurationParameters osgiConfig;
 
     /**
+     * whiteboard registration handle of the manager mbean
+     */
+    private Registration mbeanRegistration;
+
+    /**
      * Activates the LoginModuleFactory service
-     * @param properties the OSGi config
+     * @param context the component context
      */
     @Activate
-    protected void activate(Map<String, Object> properties) {
-        osgiConfig = ConfigurationParameters.of(properties);
+    private void activate(ComponentContext context) {
+        //noinspection unchecked
+        osgiConfig = ConfigurationParameters.of(context.getProperties());
+        String idpName = osgiConfig.getConfigValue(PARAM_IDP_NAME, "");
+        String sncName = osgiConfig.getConfigValue(PARAM_SYNC_HANDLER_NAME, "");
+
+        Whiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext());
+        try {
+            SyncMBeanImpl bean = new SyncMBeanImpl(repository, syncManager, sncName, idpManager, idpName);
+            Hashtable<String, String> table = new Hashtable<String, String>();
+            table.put("type", "UserManagement");
+            table.put("name", "External Identity Synchronization Management");
+            table.put("handler", ObjectName.quote(sncName));
+            table.put("idp", ObjectName.quote(idpName));
+            mbeanRegistration = whiteboard.register(SynchronizationMBean.class, bean, ImmutableMap.of(
+                    "jmx.objectname",
+                    new ObjectName("org.apache.jackrabbit.oak", table))
+            );
+        } catch (MalformedObjectNameException e) {
+            log.error("Unable to register SynchronizationMBean.", e);
+        }
+    }
+
+    @Deactivate
+    private void deactivate() {
+        if (mbeanRegistration != null) {
+            mbeanRegistration.unregister();
+            mbeanRegistration = null;
+        }
     }
 
     /**

Modified: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java Tue Apr 15 17:19:20 2014
@@ -17,17 +17,11 @@
 
 package org.apache.jackrabbit.oak.spi.security.authentication.external.impl;
 
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
 import javax.annotation.Nonnull;
 
 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.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.ReferencePolicy;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;

Added: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncResultImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncResultImpl.java?rev=1587638&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncResultImpl.java (added)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncResultImpl.java Tue Apr 15 17:19:20 2014
@@ -0,0 +1,46 @@
+/*
+ * 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 org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
+
+/**
+* {@code SyncResultImpl}...
+*/
+public class SyncResultImpl implements SyncResult {
+
+    private final SyncedIdentityImpl id;
+
+    private Status status = Status.NOP;
+
+    public SyncResultImpl(SyncedIdentityImpl id, Status status) {
+        this.id = id;
+        this.status = status;
+    }
+
+    public SyncedIdentityImpl getIdentity() {
+        return id;
+    }
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncedIdentityImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncedIdentityImpl.java?rev=1587638&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncedIdentityImpl.java (added)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncedIdentityImpl.java Tue Apr 15 17:19:20 2014
@@ -0,0 +1,64 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncedIdentity;
+
+/**
+* {@code SyncedIdentityImpl}...
+*/
+public class SyncedIdentityImpl implements SyncedIdentity {
+
+    private final String id;
+
+    private final ExternalIdentityRef ref;
+
+    private final boolean isGroup;
+
+    private final long lastSynced;
+
+    public SyncedIdentityImpl(String id, ExternalIdentityRef ref, boolean isGroup, long lastSynced) {
+        this.id = id;
+        this.ref = ref;
+        this.isGroup = isGroup;
+        this.lastSynced = lastSynced;
+    }
+
+    @Nonnull
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public ExternalIdentityRef getExternalIdRef() {
+        return ref;
+    }
+
+    @Override
+    public boolean isGroup() {
+        return isGroup;
+    }
+
+    @Override
+    public long lastSynced() {
+        return lastSynced;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImpl.java?rev=1587638&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImpl.java (added)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImpl.java Tue Apr 15 17:19:20 2014
@@ -0,0 +1,449 @@
+/*
+ * 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.jmx;
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.commons.json.JsonUtil;
+import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
+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.ExternalIdentityProviderManager;
+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.SyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncedIdentity;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncResultImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncedIdentityImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@code SyncMBeanImpl}...
+ */
+public class SyncMBeanImpl implements SynchronizationMBean {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(SyncMBeanImpl.class);
+
+    private final Repository repository;
+
+    private final SyncManager syncManager;
+
+    private final String syncName;
+
+    private final ExternalIdentityProviderManager idpManager;
+
+    private final String idpName;
+
+    public SyncMBeanImpl(Repository repository, SyncManager syncManager, String syncName,
+                         ExternalIdentityProviderManager idpManager, String idpName) {
+        this.repository = repository;
+        this.syncManager = syncManager;
+        this.syncName = syncName;
+        this.idpManager = idpManager;
+        this.idpName = idpName;
+    }
+
+    @Nonnull
+    private Delegatee getDelegatee() {
+        SyncHandler handler = syncManager.getSyncHandler(syncName);
+        if (handler == null) {
+            log.error("No sync manager available for name {}.", syncName);
+            throw new IllegalArgumentException("No sync manager available for name " + syncName);
+        }
+        ExternalIdentityProvider idp = idpManager.getProvider(idpName);
+        if (idp == null) {
+            log.error("No idp available for name", idpName);
+            throw new IllegalArgumentException("No idp manager available for name " + idpName);
+        }
+        try {
+            return new Delegatee(handler, idp);
+        } catch (SyncException e) {
+            throw new IllegalArgumentException("Unable to create delegatee", e);
+        }
+    }
+
+    private class Delegatee {
+
+        private SyncHandler handler;
+
+        private ExternalIdentityProvider idp;
+
+        private SyncContext context;
+
+        private UserManager userMgr;
+
+        private Session systemSession;
+
+        private Delegatee(SyncHandler handler, ExternalIdentityProvider idp) throws SyncException {
+            this.handler = handler;
+            this.idp = idp;
+            try {
+                systemSession = Subject.doAs(SystemSubject.INSTANCE, new PrivilegedExceptionAction<Session>() {
+                    @Override
+                    public Session run() throws LoginException, RepositoryException {
+                        if (repository instanceof JackrabbitRepository) {
+                            // this is to bypass GuestCredentials injection in the "AbstractSlingRepository2"
+                            return ((JackrabbitRepository) repository).login(null, null, null);
+                        } else {
+                            return repository.login(null, null);
+                        }
+                    }
+                });
+            } catch (PrivilegedActionException e) {
+                throw new SyncException(e);
+            }
+            try {
+                context = handler.createContext(idp, userMgr = ((JackrabbitSession) systemSession).getUserManager(), systemSession.getValueFactory());
+            } catch (Exception e) {
+                systemSession.logout();
+                throw new SyncException(e);
+            }
+
+            log.info("Created delegatee for SyncMBean with session: {} {}", systemSession, systemSession.getUserID());
+        }
+
+        private void close() {
+            if (context != null) {
+                context.close();
+                context = null;
+            }
+            if (systemSession != null) {
+                systemSession.logout();
+            }
+        }
+
+        /**
+         * @see SynchronizationMBean#syncUsers(String[], boolean)
+         */
+        @Nonnull
+        public String[] syncUsers(@Nonnull String[] userIds, boolean purge) {
+            context.setKeepMissing(!purge)
+                    .setForceGroupSync(true)
+                    .setForceUserSync(true);
+            List<String> result = new ArrayList<String>();
+            for (String userId: userIds) {
+                try {
+                    SyncResult r = context.sync(userId);
+                    systemSession.save();
+                    result.add(getJSONString(r));
+                } catch (SyncException e) {
+                    log.warn("Error while syncing user {}", userId, e);
+                } catch (RepositoryException e) {
+                    log.warn("Error while syncing user {}", userId, e);
+                }
+            }
+            return result.toArray(new String[result.size()]);
+        }
+
+        /**
+         * @see SynchronizationMBean#syncAllUsers(boolean)
+         */
+        @Nonnull
+        public String[] syncAllUsers(boolean purge) {
+            try {
+                List<String> list = new ArrayList<String>();
+                context.setKeepMissing(!purge)
+                        .setForceGroupSync(true)
+                        .setForceUserSync(true);
+                Iterator<SyncedIdentity> iter = handler.listIdentities(userMgr);
+                while (iter.hasNext()) {
+                    SyncedIdentity id = iter.next();
+                    if (isMyIDP(id)) {
+                        try {
+                            SyncResult r = context.sync(id.getId());
+                            systemSession.save();
+                            list.add(getJSONString(r));
+                        } catch (SyncException e) {
+                            list.add(getJSONString(id, e));
+                        } catch (RepositoryException e) {
+                            list.add(getJSONString(id, e));
+                        }
+                    }
+                }
+                return list.toArray(new String[list.size()]);
+            } catch (RepositoryException e) {
+                throw new IllegalStateException("Error retrieving users for syncing", e);
+            }
+        }
+
+        /**
+         * @see SynchronizationMBean#syncExternalUsers(String[])
+         */
+        @Nonnull
+        public String[] syncExternalUsers(@Nonnull String[] externalIds) {
+            List<String> list = new ArrayList<String>();
+            context.setForceGroupSync(true).setForceUserSync(true);
+            for (String externalId: externalIds) {
+                ExternalIdentityRef ref = ExternalIdentityRef.fromString(externalId);
+                try {
+                    ExternalIdentity id = idp.getIdentity(ref);
+                    if (id != null) {
+                        SyncResult r = context.sync(id);
+                        systemSession.save();
+                        list.add(getJSONString(r));
+                    } else {
+                        SyncResult r = new SyncResultImpl(
+                                new SyncedIdentityImpl("", ref, false, -1),
+                                SyncResult.Status.NO_SUCH_IDENTITY
+                        );
+                        list.add(getJSONString(r));
+                    }
+                } catch (ExternalIdentityException e) {
+                    log.warn("error while fetching the external identity {}", externalId, e);
+                    list.add(getJSONString(ref, e));
+                } catch (SyncException e) {
+                    list.add(getJSONString(ref, e));
+                } catch (RepositoryException e) {
+                    list.add(getJSONString(ref, e));
+                }
+            }
+            return list.toArray(new String[list.size()]);
+        }
+
+        /**
+         * @see SynchronizationMBean#syncAllExternalUsers()
+         */
+        @Nonnull
+        public String[] syncAllExternalUsers() {
+            List<String> list = new ArrayList<String>();
+            context.setForceGroupSync(true).setForceUserSync(true);
+            try {
+                Iterator<ExternalUser> iter = idp.listUsers();
+                while (iter.hasNext()) {
+                    ExternalUser user = iter.next();
+                    try {
+                        SyncResult r = context.sync(user);
+                        systemSession.save();
+                        list.add(getJSONString(r));
+                    } catch (SyncException e) {
+                        list.add(getJSONString(user.getExternalId(), e));
+                    } catch (RepositoryException e) {
+                        list.add(getJSONString(user.getExternalId(), e));
+                    }
+                }
+                return list.toArray(new String[list.size()]);
+            } catch (ExternalIdentityException e) {
+                throw new IllegalArgumentException("Unable to retrieve external users", e);
+            }
+        }
+
+        /**
+         * @see SynchronizationMBean#listOrphanedUsers()
+         */
+        @Nonnull
+        public String[] listOrphanedUsers() {
+            List<String> list = new ArrayList<String>();
+            try {
+                Iterator<SyncedIdentity> iter = handler.listIdentities(userMgr);
+                while (iter.hasNext()) {
+                    SyncedIdentity id = iter.next();
+                    if (isMyIDP(id)) {
+                        ExternalIdentity extId = idp.getIdentity(id.getExternalIdRef());
+                        if (extId == null) {
+                            list.add(id.getId());
+                        }
+                    }
+                }
+            } catch (RepositoryException e) {
+                log.error("Error while listing orphaned users", e);
+            } catch (ExternalIdentityException e) {
+                log.error("Error while fetching external identity", e);
+            }
+            return list.toArray(new String[list.size()]);
+        }
+
+        /**
+         * @see SynchronizationMBean#purgeOrphanedUsers()
+         */
+        @Nonnull
+        public String[] purgeOrphanedUsers() {
+            context.setKeepMissing(false);
+            List<String> result = new ArrayList<String>();
+            for (String userId: listOrphanedUsers()) {
+                try {
+                    SyncResult r = context.sync(userId);
+                    systemSession.save();
+                    result.add(getJSONString(r));
+                } catch (SyncException e) {
+                    log.warn("Error while syncing user {}", userId, e);
+                } catch (RepositoryException e) {
+                    log.warn("Error while syncing user {}", userId, e);
+                }
+            }
+            return result.toArray(new String[result.size()]);
+        }
+
+        private boolean isMyIDP(SyncedIdentity id) {
+            String idpName = id.getExternalIdRef() == null
+                    ? null
+                    : id.getExternalIdRef().getProviderName();
+            return (idpName == null || idpName.length() ==0 || idpName.equals(idp.getName()));
+        }
+    }
+
+    private static String getJSONString(SyncResult r) {
+        String op = "";
+        switch (r.getStatus()) {
+            case NOP:
+                op = "nop";
+                break;
+            case ADD:
+                op = "add";
+                break;
+            case UPDATE:
+                op = "upd";
+                break;
+            case DELETE:
+                op = "del";
+                break;
+            case NO_SUCH_AUTHORIZABLE:
+                op = "nsa";
+                break;
+            case NO_SUCH_IDENTITY:
+                op = "nsi";
+                break;
+            case MISSING:
+                op = "mis";
+                break;
+            case FOREIGN:
+                op = "for";
+                break;
+        }
+        String uid = JsonUtil.getJsonString(r.getIdentity().getId());
+        String eid = r.getIdentity().getExternalIdRef() == null
+                ? "\"\""
+                : JsonUtil.getJsonString(r.getIdentity().getExternalIdRef().getString());
+        return String.format("{op:\"%s\",uid:%s,eid:%s}", op, uid, eid);
+    }
+
+    private static String getJSONString(SyncedIdentity id, Exception e) {
+        String uid = JsonUtil.getJsonString(id.getId());
+        String eid = id.getExternalIdRef() == null
+                ? "\"\""
+                : JsonUtil.getJsonString(id.getExternalIdRef().getString());
+        String msg = JsonUtil.getJsonString(e.toString());
+        return String.format("{op:\"ERR\",uid:%s,eid:%s,msg:%s}", uid, eid, msg);
+    }
+
+    private static String getJSONString(ExternalIdentityRef ref, Exception e) {
+        String eid = JsonUtil.getJsonString(ref.getString());
+        String msg = JsonUtil.getJsonString(e.toString());
+        return String.format("{op:\"ERR\",uid:\"\",eid:%s,msg:%s}", eid, msg);
+    }
+
+    //---------------------------------------------------------------------------------------< SynchronizationMBean >---
+    @Nonnull
+    @Override
+    public String getSyncHandlerName() {
+        return syncName;
+    }
+
+    @Nonnull
+    @Override
+    public String getIDPName() {
+        return idpName;
+    }
+
+    @Nonnull
+    @Override
+    public String[] syncUsers(@Nonnull String[] userIds, boolean purge) {
+        Delegatee delegatee = getDelegatee();
+        try {
+            return delegatee.syncUsers(userIds, purge);
+        } finally {
+            delegatee.close();
+        }
+    }
+
+    @Nonnull
+    @Override
+    public String[] syncAllUsers(boolean purge) {
+        Delegatee delegatee = getDelegatee();
+        try {
+            return delegatee.syncAllUsers(purge);
+        } finally {
+            delegatee.close();
+        }
+    }
+
+    @Nonnull
+    @Override
+    public String[] syncExternalUsers(@Nonnull String[] externalIds) {
+        Delegatee delegatee = getDelegatee();
+        try {
+            return delegatee.syncExternalUsers(externalIds);
+        } finally {
+            delegatee.close();
+        }
+    }
+
+    @Nonnull
+    @Override
+    public String[] syncAllExternalUsers() {
+        Delegatee delegatee = getDelegatee();
+        try {
+            return delegatee.syncAllExternalUsers();
+        } finally {
+            delegatee.close();
+        }
+    }
+
+    @Nonnull
+    @Override
+    public String[] listOrphanedUsers() {
+        Delegatee delegatee = getDelegatee();
+        try {
+            return delegatee.listOrphanedUsers();
+        } finally {
+            delegatee.close();
+        }
+    }
+
+    @Nonnull
+    @Override
+    public String[] purgeOrphanedUsers() {
+        Delegatee delegatee = getDelegatee();
+        try {
+            return delegatee.purgeOrphanedUsers();
+        } finally {
+            delegatee.close();
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java?rev=1587638&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java (added)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java Tue Apr 15 17:19:20 2014
@@ -0,0 +1,119 @@
+/*
+ * 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.jmx;
+
+import javax.annotation.Nonnull;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * Provides utilities to manage synchronized external identities.
+ * The operations return a single or array of messages of the operations performed. for simplicity the messages are
+ * JSON serialized strings:
+ * <xmp>
+ * {
+ *     "op": "upd",
+ *     "uid": "bob",
+ *     "eid": "cn=bob,o=apache"
+ * }
+ * </xmp>
+ *
+ * With the following operations:
+ * <ul>
+ * <li>nop: nothing changed</li>
+ * <li>upd: entry updated</li>
+ * <li>add: entry added</li>
+ * <li>del: entry deleted</li>
+ * <li>err: operation failed. in this case, the 'msg' property will contain a reason</li>
+ * </ul>
+ *
+ * Note that this interface is not exported via OSGi as it is not intended to use outside of JMX (yet).
+ */
+@ProviderType
+public interface SynchronizationMBean {
+
+    /**
+     * Returns the name of the {@link org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler}
+     * that this MBean operates on.
+     *
+     * @return the name of the sync handler.
+     */
+    @Nonnull
+    String getSyncHandlerName();
+
+    /**
+     * Returns the name of the {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider}
+     * that this MBean operates on.
+     *
+     * @return the name of the IDP.
+     */
+    @Nonnull
+    String getIDPName();
+
+    /**
+     * Synchronizes the local users with the given user ids.
+     * @param userIds the user ids
+     * @param purge if {@code true} users that don't exist in the IDP are deleted.
+     * @return result messages.
+     */
+    @Nonnull
+    String[] syncUsers(@Nonnull String[] userIds, boolean purge);
+
+    /**
+     * Synchronizes all local users with the given user ids. Note that this can be an expensive operation since all
+     * potential users need to be examined.
+     *
+     * @param purge if {@code true} users that don't exist in the IDP are deleted.
+     * @return result messages.
+     */
+    @Nonnull
+    String[] syncAllUsers(boolean purge);
+
+    /**
+     * Synchronizes the external users with the given external ids.
+     * @param externalIds the external id
+     * @return result messages.
+     */
+    @Nonnull
+    String[] syncExternalUsers(@Nonnull String[] externalIds);
+
+    /**
+     * Synchronizes all the external users, i.e. basically imports the entire IDP. Note that this can be an expensive
+     * operation.
+     *
+     * @return result messages.
+     */
+    @Nonnull
+    String[] syncAllExternalUsers();
+
+    /**
+     * Returns a list of orphaned users, i.e. users that don't exist anymore on the IDP. Note that this can be an
+     * expensive operation since all potential users need to be examined.
+     * @return a list of the user ids of orphaned users.
+     */
+    @Nonnull
+    String[] listOrphanedUsers();
+
+    /**
+     * Purges all orphaned users. this is similar to invoke {@link #syncUsers(String[], boolean)} with the list of
+     * orphaned users. Note tha this can eb an expensive operation since all potential users need to be examined.
+     *
+     * @return result messages.
+     */
+    @Nonnull
+    String[] purgeOrphanedUsers();
+}
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java Tue Apr 15 17:19:20 2014
@@ -14,8 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("0.17")
-@Export(optional = "provide:=true")
+@Version("1.0")
+@Export
 package org.apache.jackrabbit.oak.spi.security.authentication.external;
 
 import aQute.bnd.annotation.Version;

Modified: jackrabbit/oak/branches/1.0/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java Tue Apr 15 17:19:20 2014
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.spi.se
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -28,8 +29,8 @@ import javax.security.auth.login.LoginEx
 
 public class TestIdentityProvider implements ExternalIdentityProvider {
 
-    private final Map<String, TestGroup> externalGroups = new HashMap<String, TestGroup>();
-    private final Map<String, TestUser> externalUsers = new HashMap<String, TestUser>();
+    private final Map<String, ExternalGroup> externalGroups = new HashMap<String, ExternalGroup>();
+    private final Map<String, ExternalUser> externalUsers = new HashMap<String, ExternalUser>();
 
 
     public TestIdentityProvider() {
@@ -96,6 +97,16 @@ public class TestIdentityProvider implem
         return externalGroups.get(name);
     }
 
+    @Override
+    public Iterator<ExternalUser> listUsers() throws ExternalIdentityException {
+        return externalUsers.values().iterator();
+    }
+
+    @Override
+    public Iterator<ExternalGroup> listGroups() throws ExternalIdentityException {
+        return externalGroups.values().iterator();
+    }
+
     private static class TestIdentity implements ExternalIdentity {
 
         private final String userId;

Modified: jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java Tue Apr 15 17:19:20 2014
@@ -18,6 +18,9 @@ package org.apache.jackrabbit.oak.securi
 
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
@@ -53,6 +56,7 @@ import org.apache.felix.scr.annotations.
 import org.apache.felix.scr.annotations.ConfigurationPolicy;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
@@ -287,6 +291,82 @@ public class LdapIdentityProvider implem
         }
     }
 
+    @Override
+    public Iterator<ExternalUser> listUsers() throws ExternalIdentityException {
+        DebugTimer timer = new DebugTimer();
+        LdapConnection connection = connect();
+        timer.mark("connect");
+        try {
+            final List<Entry> entries = getEntries(connection, config.getUserConfig());
+            timer.mark("lookup");
+            if (log.isDebugEnabled()) {
+                log.debug("listUsers() {}", timer.getString());
+            }
+            return new AbstractLazyIterator<ExternalUser>() {
+
+                private final Iterator<Entry> iter = entries.iterator();
+
+                @Override
+                protected ExternalUser getNext() {
+                    while (iter.hasNext()) {
+                        try {
+                            return createUser(iter.next(), null);
+                        } catch (LdapInvalidAttributeValueException e) {
+                            log.warn("Error while creating external user object", e);
+                        }
+                    }
+                    return null;
+                }
+            };
+        } catch (LdapException e) {
+            log.error("Error during ldap lookup. " + timer.getString(), e);
+            throw new ExternalIdentityException("Error during ldap lookup.", e);
+        } catch (CursorException e) {
+            log.error("Error during ldap lookup. " + timer.getString(), e);
+            throw new ExternalIdentityException("Error during ldap lookup.", e);
+        } finally {
+            disconnect(connection);
+        }
+    }
+
+    @Override
+    public Iterator<ExternalGroup> listGroups() throws ExternalIdentityException {
+        DebugTimer timer = new DebugTimer();
+        LdapConnection connection = connect();
+        timer.mark("connect");
+        try {
+            final List<Entry> entries = getEntries(connection, config.getGroupConfig());
+            timer.mark("lookup");
+            if (log.isDebugEnabled()) {
+                log.debug("listGroups() {}", timer.getString());
+            }
+            return new AbstractLazyIterator<ExternalGroup>() {
+
+                private final Iterator<Entry> iter = entries.iterator();
+
+                @Override
+                protected ExternalGroup getNext() {
+                    while (iter.hasNext()) {
+                        try {
+                            return createGroup(iter.next(), null);
+                        } catch (LdapInvalidAttributeValueException e) {
+                            log.warn("Error while creating external user object", e);
+                        }
+                    }
+                    return null;
+                }
+            };
+        } catch (LdapException e) {
+            log.error("Error during ldap lookup. " + timer.getString(), e);
+            throw new ExternalIdentityException("Error during ldap lookup.", e);
+        } catch (CursorException e) {
+            log.error("Error during ldap lookup. " + timer.getString(), e);
+            throw new ExternalIdentityException("Error during ldap lookup.", e);
+        } finally {
+            disconnect(connection);
+        }
+    }
+
     private Entry getEntry(LdapConnection connection, LdapProviderConfig.Identity idConfig, String id)
             throws CursorException, LdapException {
         String searchFilter = idConfig.getSearchFilter(id);
@@ -329,6 +409,65 @@ public class LdapIdentityProvider implem
         return null;
     }
 
+    /**
+     * currently fetch all entries so that we can close the connection afterwards. maybe switch to an iterator approach
+     * later.
+     */
+    private List<Entry> getEntries(LdapConnection connection, LdapProviderConfig.Identity idConfig)
+            throws CursorException, LdapException {
+        StringBuilder filter = new StringBuilder();
+        int num = 0;
+        for (String objectClass: idConfig.getObjectClasses()) {
+            num++;
+            filter.append("(objectclass=")
+                    .append(LdapProviderConfig.encodeFilterValue(objectClass))
+                    .append(')');
+        }
+        String extraFilter = idConfig.getExtraFilter();
+        if (extraFilter != null && extraFilter.length() > 0) {
+            num++;
+            filter.append(extraFilter);
+        }
+        String searchFilter = num > 1
+                ? "(&" + filter + ")"
+                : filter.toString();
+
+        // Create the SearchRequest object
+        SearchRequest req = new SearchRequestImpl();
+        req.setScope(SearchScope.SUBTREE);
+        req.addAttributes(SchemaConstants.ALL_USER_ATTRIBUTES);
+        req.setTimeLimit((int) config.getSearchTimeout());
+        req.setBase(new Dn(idConfig.getBaseDN()));
+        req.setFilter(searchFilter);
+
+        // Process the request
+        List<Entry> result = new LinkedList<Entry>();
+        SearchCursor searchCursor = null;
+        try {
+            searchCursor = connection.search(req);
+            while (searchCursor.next()) {
+                Response response = searchCursor.get();
+
+                // process the SearchResultEntry
+                if (response instanceof SearchResultEntry) {
+                    Entry resultEntry = ((SearchResultEntry) response).getEntry();
+                    result.add(resultEntry);
+                    if (log.isDebugEnabled()) {
+                        log.debug("search below {} with {} found {}", idConfig.getBaseDN(), searchFilter, resultEntry.getDn());
+                    }
+                }
+            }
+        } finally {
+            if (searchCursor != null) {
+                searchCursor.close();
+            }
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("search below {} with {} found {} entries.", idConfig.getBaseDN(), searchFilter, result.size());
+        }
+        return result;
+    }
+
     private ExternalUser createUser(Entry e, String id)
             throws LdapInvalidAttributeValueException {
         ExternalIdentityRef ref = new ExternalIdentityRef(e.getDn().getName(), this.getName());

Modified: jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java?rev=1587638&r1=1587637&r2=1587638&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java Tue Apr 15 17:19:20 2014
@@ -862,7 +862,7 @@ public class LdapProviderConfig {
      * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
      * @return Escaped version of <code>value</code>
      */
-    private static String encodeFilterValue(String value) {
+    public static String encodeFilterValue(String value) {
         StringBuilder sb = null;
         for (int i = 0; i < value.length(); i++) {
             char ch = value.charAt(i);

Added: jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/add_user.sh
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/add_user.sh?rev=1587638&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/add_user.sh (added)
+++ jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/add_user.sh Tue Apr 15 17:19:20 2014
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# 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.
+#
+if [ -z "$1" ]; then
+    echo "usage: $0 userid"
+    exit
+fi
+ldapadd -h localhost -p 10389 -D uid=admin,ou=system -w secret <<EOF
+dn: cn=Test User $1,ou=people,o=sevenSeas,dc=com
+objectclass: top
+objectclass: inetOrgPerson
+objectclass: person
+objectclass: organizationalPerson
+cn: Test User $1
+sn: User $1
+description: User to test stuff
+givenname: Test
+mail: $10@mail.com
+uid: $1
+userpassword:: e3NoYX1uVTRlSTcxYmNuQkdxZU8wdDl0WHZZMXU1b1E9
+EOF

Propchange: jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/add_user.sh
------------------------------------------------------------------------------
    svn:executable = *

Added: jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/del_user.sh
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/del_user.sh?rev=1587638&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/del_user.sh (added)
+++ jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/del_user.sh Tue Apr 15 17:19:20 2014
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# 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.
+#
+if [ -z "$1" ]; then
+    echo "usage: $0 userid"
+    exit
+fi
+ldapdelete -v -h localhost -p 10389 -D uid=admin,ou=system -w secret "cn=Test User $1,ou=people,o=sevenSeas,dc=com"

Propchange: jackrabbit/oak/branches/1.0/oak-auth-ldap/src/test/scripts/del_user.sh
------------------------------------------------------------------------------
    svn:executable = *