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 dj...@apache.org on 2016/03/18 11:00:35 UTC

svn commit: r1735564 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/ oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/us...

Author: dj
Date: Fri Mar 18 10:00:35 2016
New Revision: 1735564

URL: http://svn.apache.org/viewvc?rev=1735564&view=rev
Log:
OAK-4003 - Add support for Group-Membership actions

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractGroupAction.java
      - copied, changed from r1735040, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractAuthorizableAction.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupAction.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java   (with props)
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/groupaction.md
      - copied, changed from r1735040, jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/authorizableaction.md
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsBestEffortTest.java   (with props)
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsTest.java
      - copied, changed from r1735040, jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/GroupImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/GroupImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/GroupImpl.java?rev=1735564&r1=1735563&r2=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/GroupImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/GroupImpl.java Fri Mar 18 10:00:35 2016
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.securi
 import java.security.Principal;
 import java.util.Iterator;
 import java.util.Set;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.jcr.RepositoryException;
@@ -116,7 +117,13 @@ class GroupImpl extends AuthorizableImpl
             }
         }
 
-        return getMembershipProvider().addMember(getTree(), authorizableImpl.getTree());
+        boolean success = getMembershipProvider().addMember(getTree(), authorizableImpl.getTree());
+
+        if (success) {
+            getUserManager().onGroupUpdate(this, false, authorizable);
+        }
+
+        return success;
     }
 
     @Override
@@ -154,7 +161,14 @@ class GroupImpl extends AuthorizableImpl
             return false;
         } else {
             Tree memberTree = ((AuthorizableImpl) authorizable).getTree();
-            return getMembershipProvider().removeMember(getTree(), memberTree);
+
+            boolean success = getMembershipProvider().removeMember(getTree(), memberTree);
+
+            if (success) {
+                getUserManager().onGroupUpdate(this, true, authorizable);
+            }
+
+            return success;
         }
     }
 
@@ -246,6 +260,7 @@ class GroupImpl extends AuthorizableImpl
      */
     private final Set<String> updateMembers(boolean isRemove, @Nonnull String... memberIds) throws RepositoryException {
         Set<String> idSet = Sets.newLinkedHashSet(Lists.newArrayList(memberIds));
+        Set<String> processedIds = Sets.newLinkedHashSet();
         int importBehavior = UserUtil.getImportBehavior(getUserManager().getConfig());
 
         Iterator<String> idIterator = idSet.iterator();
@@ -282,8 +297,12 @@ class GroupImpl extends AuthorizableImpl
             }
             if (success) {
                 idIterator.remove();
+                processedIds.add(memberId);
             }
         }
+
+        getUserManager().onGroupUpdate(this, isRemove, false, processedIds, idSet);
+
         return idSet;
     }
 

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java?rev=1735564&r1=1735563&r2=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java Fri Mar 18 10:00:35 2016
@@ -35,6 +35,7 @@ import javax.jcr.Session;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.PropertyDefinition;
 
+import com.google.common.collect.Sets;
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
@@ -639,9 +640,17 @@ class UserImporter implements ProtectedP
                 Tree groupTree = root.getTree(gr.getPath());
 
                 MembershipProvider membershipProvider = userManager.getMembershipProvider();
+                Set<String> memberContentIds = Sets.newLinkedHashSet();
+                Set<String> failedContentIds = Sets.newLinkedHashSet();
                 for (String member : nonExisting) {
-                    membershipProvider.addMember(groupTree, member);
+                    boolean success = membershipProvider.addMember(groupTree, member);
+                    if (success) {
+                        memberContentIds.add(member);
+                    } else {
+                        failedContentIds.add(member);
+                    }
                 }
+                userManager.onGroupUpdate(gr, false, true, memberContentIds, failedContentIds);
             }
         }
     }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java?rev=1735564&r1=1735563&r2=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java Fri Mar 18 10:00:35 2016
@@ -20,6 +20,8 @@ import java.io.UnsupportedEncodingExcept
 import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
@@ -28,6 +30,7 @@ import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
@@ -52,6 +55,7 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction;
 import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider;
 import org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider;
+import org.apache.jackrabbit.oak.spi.security.user.action.GroupAction;
 import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
 import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
 import org.apache.jackrabbit.oak.util.NodeUtil;
@@ -309,6 +313,52 @@ public class UserManagerImpl implements
         }
     }
 
+    /**
+     * Upon a group being updated (single {@code Authorizable} successfully added or removed),
+     * call available {@code GroupAction}s and execute the method specific to removal or addition.
+     * {@code GroupAction}s may then validate or modify the changes.
+     *
+     * @param group    The target group.
+     * @param isRemove Indicates whether the member is removed or added.
+     * @param member   The member successfully removed or added.
+     * @throws RepositoryException If an error occurs.
+     */
+    void onGroupUpdate(@Nonnull Group group, boolean isRemove, @Nonnull Authorizable member) throws RepositoryException {
+        for (GroupAction action : selectGroupActions()) {
+            if (isRemove) {
+                action.onMemberRemoved(group, member, root, namePathMapper);
+            } else {
+                action.onMemberAdded(group, member, root, namePathMapper);
+            }
+        }
+    }
+
+    /**
+     * Upon a group being updated (multiple {@code memberIds} added or removed),
+     * call available {@code GroupAction}s and execute the method specific to removal or addition.
+     * {@code GroupAction}s may then validate or modify the changes.
+     *
+     * @param group       The target group.
+     * @param isRemove    Indicates whether the member is removed or added.
+     * @param isContentId Indicates whether member ids are expressed as content-ids (UUID) or member-ids.
+     * @param memberIds   The IDs of all members successfully removed or added.
+     * @param failedIds   The IDs of all members whose addition or removal failed.
+     * @throws RepositoryException If an error occurs.
+     */
+    void onGroupUpdate(@Nonnull Group group, boolean isRemove, boolean isContentId, @Nonnull Set<String> memberIds, @Nonnull Set<String> failedIds) throws RepositoryException {
+        for (GroupAction action : selectGroupActions()) {
+            if (isRemove) {
+                action.onMembersRemoved(group, memberIds, failedIds, root, namePathMapper);
+            } else {
+                if (isContentId) {
+                    action.onMembersAddedContentId(group, memberIds, failedIds, root, namePathMapper);
+                } else {
+                    action.onMembersAdded(group, memberIds, failedIds, root, namePathMapper);
+                }
+            }
+        }
+    }
+
     //--------------------------------------------------------------------------
     @CheckForNull
     Authorizable getAuthorizable(@CheckForNull Tree tree) throws RepositoryException {
@@ -439,4 +489,20 @@ public class UserManagerImpl implements
         }
         return queryManager;
     }
+
+    /**
+     * Select only {@code GroupAction}s from the available {@code AuthorizableAction}s.
+     *
+     * @return A {@code List} of {@code GroupAction}s. List may be empty.
+     */
+    @Nonnull
+    private List<GroupAction> selectGroupActions() {
+        List<GroupAction> actions = Lists.newArrayList();
+        for (AuthorizableAction action : actionProvider.getAuthorizableActions(securityProvider)) {
+            if (action instanceof GroupAction) {
+                actions.add((GroupAction) action);
+            }
+        }
+        return actions;
+    }
 }
\ No newline at end of file

Copied: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractGroupAction.java (from r1735040, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractAuthorizableAction.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractGroupAction.java?p2=jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractGroupAction.java&p1=jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractAuthorizableAction.java&r1=1735040&r2=1735564&rev=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractAuthorizableAction.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/AbstractGroupAction.java Fri Mar 18 10:00:35 2016
@@ -16,31 +16,28 @@
  */
 package org.apache.jackrabbit.oak.spi.security.user.action;
 
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.jcr.RepositoryException;
-
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
-import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
-import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
 
 /**
- * Abstract implementation of the {@code AuthorizableAction} interface that
+ * Abstract implementation of the {@code GroupAction} interface that
  * doesn't perform any action. This is a convenience implementation allowing
  * subclasses to only implement methods that need extra attention.
  */
-public abstract class AbstractAuthorizableAction implements AuthorizableAction {
+public abstract class AbstractGroupAction extends AbstractAuthorizableAction implements GroupAction {
+
+    //-------------------------------------------------< GroupAction >---
 
-    //-------------------------------------------------< AuthorizableAction >---
     /**
      * Doesn't perform any action.
      */
     @Override
-    public void init(SecurityProvider securityProvider, ConfigurationParameters config) {
+    public void onMemberAdded(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
         // nothing to do
     }
 
@@ -48,7 +45,7 @@ public abstract class AbstractAuthorizab
      * Doesn't perform any action.
      */
     @Override
-    public void onCreate(@Nonnull Group group, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+    public void onMembersAdded(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
         // nothing to do
     }
 
@@ -56,7 +53,7 @@ public abstract class AbstractAuthorizab
      * Doesn't perform any action.
      */
     @Override
-    public void onCreate(@Nonnull User user, @Nullable String password, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+    public void onMembersAddedContentId(Group group, Iterable<String> memberContentIds, Iterable<String> failedIds, Root root, NamePathMapper namePathMapper) throws RepositoryException {
         // nothing to do
     }
 
@@ -64,7 +61,7 @@ public abstract class AbstractAuthorizab
      * Doesn't perform any action.
      */
     @Override
-    public void onRemove(@Nonnull Authorizable authorizable, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+    public void onMemberRemoved(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
         // nothing to do
     }
 
@@ -72,7 +69,7 @@ public abstract class AbstractAuthorizab
      * Doesn't perform any action.
      */
     @Override
-    public void onPasswordChange(@Nonnull User user, @Nullable String newPassword, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+    public void onMembersRemoved(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
         // nothing to do
     }
 }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupAction.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupAction.java?rev=1735564&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupAction.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupAction.java Fri Mar 18 10:00:35 2016
@@ -0,0 +1,115 @@
+/*
+ * 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.user.action;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * The {@code GroupAction} interface allows for implementations to be informed about and react to the following
+ * changes to a {@link Group}'s members:
+ *
+ * <ul>
+ * <li>{@link #onMemberAdded(Group, Authorizable, Root, NamePathMapper)}</li>
+ * <li>{@link #onMembersAdded(Group, Iterable, Iterable, Root, NamePathMapper)}</li>
+ * <li>{@link #onMembersAddedContentId(Group, Iterable, Iterable, Root, NamePathMapper)} </li>
+ * <li>{@link #onMemberRemoved(Group, Authorizable, Root, NamePathMapper)}</li>
+ * <li>{@link #onMembersRemoved(Group, Iterable, Iterable, Root, NamePathMapper)}</li>
+ * </ul>
+ *
+ * <p>
+ * Please consult the parent interface {@link AuthorizableAction} for details on persisting changes,
+ * configuring actions and the API through which actions are invoked.
+ * </p>
+ *
+ * <p>
+ * For convenience, an {@link AbstractGroupAction} is provided.
+ * </p>
+ * @since OAK 1.6
+ */
+public interface GroupAction extends AuthorizableAction {
+
+    /**
+     * A specific {@link Authorizable} was added as a member of the {@link Group}.
+     * Implementations may perform specific modifications or validations.
+     *
+     * @param group          The {@link Group} to which the {@link Authorizable} was added.
+     * @param member         The {@link Authorizable} added.
+     * @param root           The root associated with the user manager.
+     * @param namePathMapper
+     * @throws RepositoryException If an error occurs.
+     */
+    void onMemberAdded(Group group, Authorizable member, Root root, NamePathMapper namePathMapper) throws RepositoryException;
+
+    /**
+     * Multiple members were added to the {@link Group}. The members are provided as an iterable
+     * of their string-based IDs, as some members may no longer or not yet exist.
+     * Implementations may perform specific modifications or validations.
+     *
+     * @param group          The {@link Group} to which the members were added.
+     * @param memberIds      An {@link Iterable} of the member IDs.
+     * @param root           The root associated with the user manager.
+     * @param namePathMapper
+     * @throws RepositoryException If an error occurs.
+     */
+    void onMembersAdded(Group group, Iterable<String> memberIds, Iterable<String> failedIds, Root root, NamePathMapper namePathMapper) throws RepositoryException;
+
+    /**
+     * Multiple members were added to the {@link Group} during XML group import.
+     * The members are provided as an iterable of their string-based content IDs (UUIDs), as these
+     * members do not exist yet (group imported before users). Implementations may track such content ids
+     * for later processing once the user identified by the content id is added.
+     * <p/>
+     * Implementations may perform specific modifications or validations.
+     *
+     * @param group            The {@link Group} to which the members were added.
+     * @param memberContentIds An {@link Iterable} of the member content IDs (UUIDs).
+     * @param root             The root associated with the user manager.
+     * @param namePathMapper
+     * @throws RepositoryException If an error occurs.
+     */
+    void onMembersAddedContentId(Group group, Iterable<String> memberContentIds, Iterable<String> failedIds, Root root, NamePathMapper namePathMapper) throws RepositoryException;
+
+    /**
+     * A specific {@link Authorizable} was removed from the {@link Group}.
+     * Implementations may perform specific modifications or validations.
+     *
+     * @param group          The {@link Group} from which the {@link Authorizable} was removed.
+     * @param member         The {@link Authorizable} removed.
+     * @param root           The root associated with the user manager.
+     * @param namePathMapper
+     * @throws RepositoryException If an error occurs.
+     */
+    void onMemberRemoved(Group group, Authorizable member, Root root, NamePathMapper namePathMapper) throws RepositoryException;
+
+    /**
+     * Multiple members were removed from the {@link Group}. The members are provided as an iterable
+     * of their string-based IDs, as some members may no longer or not yet exist.
+     * Implementations may perform specific modifications or validations.
+     *
+     * @param group          The {@link Group} from which the members were removed.
+     * @param memberIds      An {@link Iterable} of the member IDs.
+     * @param root           The root associated with the user manager.
+     * @param namePathMapper
+     * @throws RepositoryException If an error occurs.
+     */
+    void onMembersRemoved(Group group, Iterable<String> memberIds, Iterable<String> failedIds, Root root, NamePathMapper namePathMapper) throws RepositoryException;
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupAction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java?rev=1735564&r1=1735563&r2=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java Fri Mar 18 10:00:35 2016
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.0.2")
+@Version("1.1.0")
 @Export(optional = "provide:=true")
 package org.apache.jackrabbit.oak.spi.security.user.action;
 

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java?rev=1735564&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java Fri Mar 18 10:00:35 2016
@@ -0,0 +1,53 @@
+/*
+ * 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.user.action;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class GroupActionBestEffortTest extends GroupActionTest {
+
+    @Test
+    public void testMembersAddedNonExisting() throws Exception {
+        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+
+        testGroup.addMembers(nonExisting.toArray(new String[nonExisting.size()]));
+        assertTrue(Iterables.elementsEqual(nonExisting, groupAction.memberIds));
+        assertFalse(groupAction.failedIds.iterator().hasNext());
+    }
+
+    @Test
+    public void testMembersRemovedNonExisting() throws Exception {
+        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+
+        testGroup.removeMembers(nonExisting.toArray(new String[nonExisting.size()]));
+        assertFalse(groupAction.memberIds.iterator().hasNext());
+        assertTrue(Iterables.elementsEqual(nonExisting, groupAction.failedIds));
+    }
+
+    @Override
+    String getImportBehavior() {
+        return ImportBehavior.NAME_BESTEFFORT;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionBestEffortTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java?rev=1735564&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java Fri Mar 18 10:00:35 2016
@@ -0,0 +1,255 @@
+/*
+ * 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.user.action;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.security.SecurityProviderImpl;
+import org.apache.jackrabbit.oak.security.user.UserConfigurationImpl;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.RepositoryException;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class GroupActionTest extends AbstractSecurityTest {
+
+    private static final String TEST_GROUP_ID = "testGroup";
+    private static final String TEST_USER_PREFIX = "testUser";
+
+    TestGroupAction groupAction = new TestGroupAction();
+    Group testGroup;
+    private User testUser01;
+    private User testUser02;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        testGroup = getUserManager(root).createGroup(TEST_GROUP_ID);
+        root.commit();
+    }
+
+    @After
+    public void after() throws Exception {
+        if (testGroup != null) {
+            testGroup.remove();
+            root.commit();
+        }
+
+        if (testUser01 != null) {
+            testUser01.remove();
+            root.commit();
+        }
+
+        if (testUser02 != null) {
+            testUser02.remove();
+            root.commit();
+        }
+
+        root = null;
+        super.after();
+    }
+
+    @Test
+    public void testMemberAdded() throws Exception {
+        testUser01 = getUserManager(root).createUser(TEST_USER_PREFIX + "01", "");
+
+        testGroup.addMember(testUser01);
+        assertTrue(groupAction.onMemberAddedCalled);
+        assertEquals(testGroup, groupAction.group);
+        assertEquals(testUser01, groupAction.member);
+    }
+
+    @Test
+    public void testMemberRemoved() throws Exception {
+        testUser01 = getUserManager(root).createUser(TEST_USER_PREFIX + "01", "");
+        testGroup.addMember(testUser01);
+        root.commit();
+
+        testGroup.removeMember(testUser01);
+        assertTrue(groupAction.onMemberRemovedCalled);
+        assertEquals(testGroup, groupAction.group);
+        assertEquals(testUser01, groupAction.member);
+    }
+
+    @Test
+    public void testMembersAdded() throws Exception {
+        testUser01 = getUserManager(root).createUser(TEST_USER_PREFIX + "01", "");
+        testUser02 = getUserManager(root).createUser(TEST_USER_PREFIX + "02", "");
+        testGroup.addMember(testUser02);
+
+        List<String> memberIds = ImmutableList.of(testUser01.getID());
+        List<String> failedIds = ImmutableList.of(testUser02.getID(), testGroup.getID());
+        Iterable<String> ids = Iterables.concat(memberIds, failedIds);
+
+        testGroup.addMembers(Iterables.toArray(ids, String.class));
+        assertTrue(groupAction.onMembersAddedCalled);
+        assertEquals(testGroup, groupAction.group);
+        assertTrue(Iterables.elementsEqual(memberIds, groupAction.memberIds));
+        assertTrue(Iterables.elementsEqual(failedIds, groupAction.failedIds));
+    }
+
+    @Test
+    public void testMembersAddedNonExisting() throws Exception {
+        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+
+        testGroup.addMembers(nonExisting.toArray(new String[nonExisting.size()]));
+        assertFalse(groupAction.memberIds.iterator().hasNext());
+        assertTrue(Iterables.elementsEqual(nonExisting, groupAction.failedIds));
+    }
+
+    @Test
+    public void testMembersRemoved() throws Exception {
+        testUser01 = getUserManager(root).createUser(TEST_USER_PREFIX + "01", "");
+        testUser02 = getUserManager(root).createUser(TEST_USER_PREFIX + "02", "");
+        testGroup.addMember(testUser01);
+
+        List<String> memberIds = ImmutableList.of(testUser01.getID());
+        List<String> failedIds = ImmutableList.of(testUser02.getID(), testGroup.getID());
+        Iterable<String> ids = Iterables.concat(memberIds, failedIds);
+
+        testGroup.removeMembers(Iterables.toArray(ids, String.class));
+        assertTrue(groupAction.onMembersRemovedCalled);
+        assertEquals(testGroup, groupAction.group);
+        assertTrue(Iterables.elementsEqual(memberIds, groupAction.memberIds));
+        assertTrue(Iterables.elementsEqual(failedIds, groupAction.failedIds));
+    }
+
+    @Test
+    public void testMembersRemovedNonExisting() throws Exception {
+        List<String> nonExisting = ImmutableList.of("blinder", "passagier");
+
+        testGroup.removeMembers(nonExisting.toArray(new String[nonExisting.size()]));
+        assertFalse(groupAction.memberIds.iterator().hasNext());
+        assertTrue(Iterables.elementsEqual(nonExisting, groupAction.failedIds));
+    }
+
+    @Override
+    protected SecurityProvider getSecurityProvider() {
+        if (securityProvider == null) {
+            securityProvider = new TestSecurityProvider();
+        }
+        return securityProvider;
+    }
+
+    class TestGroupAction extends AbstractGroupAction {
+
+        boolean onMemberAddedCalled = false;
+        boolean onMembersAddedCalled = false;
+        boolean onMemberRemovedCalled = false;
+        boolean onMembersRemovedCalled = false;
+
+        Group group;
+        Iterable<String> memberIds;
+        Iterable<String> failedIds;
+        Authorizable member;
+
+        @Override
+        public void onMemberAdded(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            this.group = group;
+            this.member = member;
+            onMemberAddedCalled = true;
+        }
+
+        @Override
+        public void onMembersAdded(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            this.group = group;
+            this.memberIds = memberIds;
+            this.failedIds = failedIds;
+            onMembersAddedCalled = true;
+        }
+
+        @Override
+        public void onMemberRemoved(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            this.group = group;
+            this.member = member;
+            onMemberRemovedCalled = true;
+        }
+
+        @Override
+        public void onMembersRemoved(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            this.group = group;
+            this.memberIds = memberIds;
+            this.failedIds = failedIds;
+            onMembersRemovedCalled = true;
+        }
+    }
+
+    private class TestSecurityProvider extends SecurityProviderImpl {
+
+        private final AuthorizableActionProvider actionProvider;
+
+        private TestSecurityProvider() {
+            actionProvider = new AuthorizableActionProvider() {
+                @Nonnull
+                @Override
+                public List<? extends AuthorizableAction> getAuthorizableActions(@Nonnull SecurityProvider securityProvider) {
+                    return ImmutableList.of(groupAction);
+                }
+            };
+        }
+
+        @Nonnull
+        public <T> T getConfiguration(@Nonnull Class<T> configClass) {
+            if (UserConfiguration.class == configClass) {
+                return (T) new UserConfigurationImpl(this) {
+                    @Nonnull
+                    @Override
+                    public ConfigurationParameters getParameters() {
+                        return ConfigurationParameters.of(super.getParameters(),
+                                ConfigurationParameters.of(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, actionProvider),
+                                ConfigurationParameters.of(ProtectedItemImporter.PARAM_IMPORT_BEHAVIOR, getImportBehavior())
+                        );
+                    }
+
+                    @Nullable
+                    @Override
+                    public PrincipalProvider getUserPrincipalProvider(@Nonnull Root root, @Nonnull NamePathMapper namePathMapper) {
+                        return null;
+                    }
+                };
+            } else {
+                return super.getConfiguration(configClass);
+            }
+        }
+    }
+
+    String getImportBehavior() {
+        return ImportBehavior.NAME_IGNORE;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/GroupActionTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md?rev=1735564&r1=1735563&r2=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md Fri Mar 18 10:00:35 2016
@@ -50,6 +50,7 @@ interfaces and classes:
 - `AuthorizableType`: ease handling with the different authorizable types.
 - `AuthorizableAction` and `AuthorizableActionProvider`: see [Authorizable Actions](user/authorizableaction.html) for details.
 - `AuthorizableNodeName`: see section  [Authorizable Node Name Generation](user/authorizablenodename.html).
+- `GroupAction` (via `AuthorizableActionProvider`): see [Group Actions](user/groupaction.html) for details.
 - `UserAuthenticationFactory`: see sections [pluggability](user/default.html#pluggability) 
 and [user authentication](authentication/default.html#user_authentication) for additional details.
 

Copied: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/groupaction.md (from r1735040, jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/authorizableaction.md)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/groupaction.md?p2=jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/groupaction.md&p1=jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/authorizableaction.md&r1=1735040&r2=1735564&rev=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/authorizableaction.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/groupaction.md Fri Mar 18 10:00:35 2016
@@ -15,175 +15,123 @@
    limitations under the License.
   -->
 
-Authorizable Actions
---------------------------------------------------------------------------------
+Group Actions
+-------------
 
 ### Overview
 
-Oak 1.0 comes with a extension to the Jackrabbit user management API that allows
-to perform additional actions or validations upon common user management tasks
+Oak 1.6 comes with an extension to the Jackrabbit user management API that allows
+to perform additional actions or validations upon group member management tasks
 such as
 
-- create authorizables
-- remove authorizables
-- change a user's password
+- add an authorizable to a group
+- remove an authorizable from a group
+- add a set of member ids as members of a group
+- remove a set of member ids from a group
 
-Similar functionality has been present in Jackrabbit 2.x as internal interface.
-Compared to the Jackrabbit interface the new `AuthorizableAction` has been slightly
-adjusted to match Oak requirements operate directly on the Oak API, which eases
-the handling of implementation specific tasks such as writing protected items.
+### GroupAction API
 
-### AuthorizableAction API
+The following public interface is provided by Oak in the package `org.apache.jackrabbit.oak.spi.security.user.action`:
 
-The following public interfaces are provided by Oak in the package `org.apache.jackrabbit.oak.spi.security.user.action`:
+- [GroupAction]
 
-- [AuthorizableAction]
-- [AuthorizableActionProvider]
-
-The `AuthorizableAction` interface itself allows to perform validations or write
-additional application specific content while executing user management related
+The `GroupAction` interface extends from `AuthorizableAction` and itself allows to perform validations or write
+additional application specific content while executing group member management related
 write operations. Therefore these actions are executed as part of the transient 
 user management modifications. This contrasts to `org.apache.jackrabbit.oak.spi.commit.CommitHook`s
 which in turn are only triggered once modifications are persisted.
 
-Consequently, implementations of the `AuthorizableAction` interface are expected 
-to adhere to this rule and perform transient repository operation or validation.
+Consequently, implementations of the `GroupAction` interface are expected 
+to adhere to this rule and perform transient repository operations or validation.
 They must not force changes to be persisted by calling `org.apache.jackrabbit.oak.api.Root.commit()`.
 
-### Default Implementations
-
-Oak 1.0 provides the following base implementations:
-
-- `AbstractAuthorizableAction`: abstract base implementation that doesn't perform any action.
-- `DefaultAuthorizableActionProvider`: default action provider service that allows to enable the built-in actions provided with oak.
-- `CompositeActionProvider`: Allows to aggregate multiple provider implementations.
-
-#### Changes wrt Jackrabbit 2.x
-
-- actions no longer operate on JCR API but rather on the Oak API direct.
-- provider interface simplifies pluggability
-
-#### Built-in AuthorizableAction Implementations
-
-The following implementations of the `AuthorizableAction` interface are provided:
-
-* `AccessControlAction`: set up permission for new authorizables
-* `PasswordValidationAction`: simplistic password verification upon user creation and password modification
-* `PasswordChangeAction`: verifies that the new password is different from the old one
-* `ClearMembershipAction`: clear group membership upon removal of an authorizable.
-
-As in Jackrabbit 2.x the actions are executed with the editing session and the
+Any group actions are executed with the editing session and the
 target operation will fail if any of the configured actions fails (e.g. due to
 insufficient permissions by the editing Oak ContentSession).
 
-### Pluggability
-
-The default security setup as present with Oak 1.0 is able to provide custom
-`AuthorizableActionProvider` implementations and will automatically combine the
-different implementations using the `CompositeActionProvider`.
-
-In an OSGi setup the following steps are required in order to add an action provider
-implementation:
-
-- implement `AuthorizableActionProvider` interface exposing your custom action(s).
-- make the provider implementation an OSGi service and make it available to the Oak repository.
-
-##### Examples
+### Default Implementations
 
-###### Example Action Provider
+Oak 1.5 provides the following base implementation for `GroupAction` implementations to build upon:
 
-    @Component()
-    @Service(AuthorizableActionProvider.class)
-    public class MyAuthorizableActionProvider implements AuthorizableActionProvider {
+- `AbstractGroupAction`: abstract base implementation that doesn't perform any action.
 
-        private static final String PUBLIC_PROFILE_NAME = "publicProfileName";
-        private static final String PRIVATE_PROFILE_NAME = "privateProfileName";
-        private static final String FRIENDS_PROFILE_NAME = "friendsProfileName";
+### Pluggability
 
-        @Property(name = PUBLIC_PROFILE_NAME, value = "publicProfile")
-        private String publicName;
+Refer to [Authorizable Actions | Pluggability ](user/authorizableaction.html#Pluggability) for details on how to plug
+a new group action into the system.
 
-        @Property(name = PRIVATE_PROFILE_NAME, value = "privateProfile")
-        private String privateName;
+### XML Import
 
-        @Property(name = FRIENDS_PROFILE_NAME, value = "friendsProfile")
-        private String friendsName;
+During import the group actions are called in the same fashion as for regular groups as long as the member reference
+can be resolved to an existing authorizable. Member IDs of authorizables that do not exist at group import time  or
+failed member IDs are passed to the group actions if `ImportBehavior.BESTEFFORT` is set for the import.
 
-        private ConfigurationParameters config = ConfigurationParameters.EMPTY;
+##### Examples
 
-        public MyAuthorizableActionProvider() {}
+###### Example Action
 
-        public MyAuthorizableActionProvider(ConfigurationParameters config) {
-            this.config = config;
-        }
+This example action creates or removes asset home directories for members
+added to or removed from a specific group:
 
-        //-----------------------------------------< AuthorizableActionProvider >---
+    public class CreateHomeForMemberGroupAction extends AbstractGroupAction {
+    
+        private static final String GROUP_ID = "asset-editors";
+        private static final String ASSET_ROOT = "/content/assets";
+        private SecurityProvider securityProvider;
+    
         @Override
-        public List<? extends AuthorizableAction> getAuthorizableActions(SecurityProvider securityProvider) {
-            AuthorizableAction action = new ProfileAction(publicName, privateName, friendsName);
-            action.init(securityProvider, config);
-            return Collections.singletonList(action);
+        public void init(SecurityProvider securityProvider, ConfigurationParameters config) {
+            this.securityProvider = securityProvider;
         }
-
-        //----------------------------------------------------< SCR Integration >---
-        @Activate
-        private void activate(Map<String, Object> properties) {
-            config = ConfigurationParameters.of(properties);
+    
+        @Override
+        public void onMemberAdded(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            createHome(group, root, member.getID(), namePathMapper);
         }
-    }
-
-###### Example Action
-
-This example action generates additional child nodes upon user/group creation
-that will later be used to store various target-specific profile information:
-
-    class ProfileAction extends AbstractAuthorizableAction {
-
-        private final String publicName;
-        private final String privateName;
-        private final String friendsName;
-
-        ProfileAction(@Nullable String publicName, @Nullable String privateName, @Nullable String friendsName) {
-            this.publicName = publicName;
-            this.privateName = privateName;
-            this.friendsName = friendsName;
+    
+        @Override
+        public void onMembersAdded(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            createHome(group, root, memberIds, failedIds, namePathMapper);
         }
-
+    
         @Override
-        public void onCreate(Group group, Root root, NamePathMapper namePathMapper) throws RepositoryException {
-            createProfileNodes(group.getPath(), root);
+        public void onMemberRemoved(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            removeHome(group, root, member.getID(), namePathMapper);
         }
-
+    
         @Override
-        public void onCreate(User user, String password, Root root, NamePathMapper namePathMapper) throws RepositoryException {
-            createProfileNodes(user.getPath(), root);
+        public void onMembersRemoved(@Nonnull Group group, @Nonnull Iterable<String> memberIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            removeHome(group, root, memberIds, failedIds, namePathMapper);
         }
-
-        private void createProfileNodes(@Nonnull String authorizablePath, @Nonnull Root root) throws AccessDeniedException {
-            Tree tree = root.getTree(authorizablePath);
-            if (tree.exists()) {
-                NodeUtil authorizableNode = new NodeUtil(tree);
-                if (publicName != null) {
-                    authorizableNode.addChild(publicName, NodeTypeConstants.NT_OAK_UNSTRUCTURED);
-                }
-                if (privateName != null) {
-                    authorizableNode.addChild(privateName, NodeTypeConstants.NT_OAK_UNSTRUCTURED);
-                }
-                if (friendsName != null) {
-                    authorizableNode.addChild(friendsName, NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+    
+        private void createHome(Group group, Root root, String memberId, NamePathMapper namePathMapper) throws RepositoryException {
+            createHome(group, root, Lists.newArrayList(memberId), Lists.<String>newArrayList(), namePathMapper);
+        }
+    
+        private void createHome(Group group, Root root, Iterable<String> memberIds, Iterable<String> failedIds, NamePathMapper namePathMapper) throws RepositoryException {
+            if (GROUP_ID.equals(group.getID())) {
+                UserManager userManager = securityProvider.getConfiguration(UserConfiguration.class).getUserManager(root, namePathMapper);
+                for (String memberId : memberIds) {
+                    Authorizable authorizable = userManager.getAuthorizable(memberId);
+                    if (authorizable != null && !authorizable.isGroup()) {
+                        // Note: this is done with the editing session of the group modification and may not
+                        // be the desired session / privilege level with which to perform these actions.
+                        NodeUtil assetRoot = new NodeUtil(root.getTree(ASSET_ROOT));
+                        NodeUtil home = assetRoot.addChild(memberId, NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+                        // ...
+                    }
                 }
             }
         }
-
-###### Example Non-OSGI Setup
-
-    Map<String, Object> userParams = new HashMap<String, Object>();
-    userParams.put(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, new MyAuthorizableActionProvider());
-    ConfigurationParameters config =  ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, ConfigurationParameters.of(userParams)));
-    SecurityProvider securityProvider = new SecurityProviderImpl(config));
-    Repository repo = new Jcr(new Oak()).with(securityProvider).createRepository();
-
+    
+        private void removeHome(Group group, Root root, String memberId, NamePathMapper namePathMapper) {
+            removeHome(group, root, Lists.newArrayList(memberId), Lists.<String>newArrayList(), namePathMapper);
+        }
+    
+        private void removeHome(Group group, Root root, Iterable<String> memberIds, Iterable<String> failedIds, NamePathMapper namePathMapper) {
+    
+        }
+    }
 
 <!-- hidden references -->
-[AuthorizableAction]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/user/action/AuthorizableAction.html
-[AuthorizableActionProvider]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/user/action/AuthorizableActionProvider.html
+[GroupAction]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/user/action/GroupAction.html

Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsBestEffortTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsBestEffortTest.java?rev=1735564&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsBestEffortTest.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsBestEffortTest.java Fri Mar 18 10:00:35 2016
@@ -0,0 +1,161 @@
+/*
+ * 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.jcr.security.user;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.security.user.action.AbstractGroupAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider;
+import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Testing {@link ImportBehavior#IGNORE} for group import
+ */
+public class GroupImportWithActionsBestEffortTest extends AbstractImportTest {
+
+    private final TestGroupAction groupAction = new TestGroupAction();
+    private final TestActionProvider actionProvider = new TestActionProvider();
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+        actionProvider.addAction(groupAction);
+    }
+
+    @Test
+    public void testImportMembersBestEffort() throws Exception {
+
+
+        User user1 = getUserManager().createUser("user1", "");
+        String uuid1 = getImportSession().getNode(user1.getPath()).getUUID();
+        User user2 = getUserManager().createUser("user2", "");
+        String uuid2 = getImportSession().getNode(user2.getPath()).getUUID();
+        String nonExistingUUID = UUID.randomUUID().toString();
+        String failedUUID = uuid1;
+
+        String xml =
+                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                        "<sv:node sv:name=\"gFolder\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                        "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
+                        "      <sv:value>rep:AuthorizableFolder</sv:value>" +
+                        "   </sv:property>" +
+                        "   <sv:node sv:name=\"g1\">" +
+                        "      <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
+                        "      <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>0120a4f9-196a-3f9e-b9f5-23f31f914da7</sv:value></sv:property>" +
+                        "      <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g1</sv:value></sv:property>" + "   <sv:property sv:name=\"rep:members\" sv:multiple=\"true\" sv:type=\"WeakReference\">" +
+                        "         <sv:value>" + uuid1 + "</sv:value>" +
+                        "         <sv:value>" + uuid2 + "</sv:value>" +
+                        "         <sv:value>" + nonExistingUUID + "</sv:value>" +
+                        "         <sv:value>" + failedUUID + "</sv:value>" +
+                        "      </sv:property>" +
+                        "   </sv:node>" +
+                        "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        Group g1 = (Group) getUserManager().getAuthorizable("g1");
+        assertTrue(groupAction.onMemberAddedCalled);
+        assertTrue(groupAction.onMembersAddedContentIdCalled);
+        assertEquals(g1.getID(), groupAction.group.getID());
+        assertTrue(Iterables.elementsEqual(ImmutableList.of(user1.getID(), user2.getID()), groupAction.memberIds));
+        assertTrue(Iterables.elementsEqual(ImmutableList.of(nonExistingUUID), groupAction.memberContentIds));
+        assertFalse(groupAction.failedIds.iterator().hasNext()); // duplicate uuids are swallowed by the set in userImporter: nonExisting#add
+    }
+
+    @Override
+    protected String getImportBehavior() {
+        return ImportBehavior.NAME_BESTEFFORT;
+    }
+
+    @Override
+    protected String getTargetPath() {
+        return GROUPPATH;
+    }
+
+    @Override
+    protected ConfigurationParameters getConfigurationParameters() {
+        Map<String, Object> userParams = new HashMap<String, Object>();
+        userParams.put(ProtectedItemImporter.PARAM_IMPORT_BEHAVIOR, getImportBehavior());
+        userParams.put(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, actionProvider);
+        return ConfigurationParameters.of(UserConfiguration.NAME, ConfigurationParameters.of(userParams));
+    }
+
+    private class TestGroupAction extends AbstractGroupAction {
+
+        boolean onMemberAddedCalled = false;
+        boolean onMembersAddedContentIdCalled = false;
+
+        Group group;
+        List<String> memberIds = Lists.newArrayList();
+        Iterable<String> memberContentIds = Lists.newArrayList();
+        Iterable<String> failedIds;
+
+        @Override
+        public void onMemberAdded(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            this.group = group;
+            memberIds.add(member.getID());
+            onMemberAddedCalled = true;
+        }
+
+        @Override
+        public void onMembersAddedContentId(@Nonnull Group group, @Nonnull Iterable<String> memberContentIds, @Nonnull Iterable<String> failedIds, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            this.group = group;
+            this.memberContentIds = memberContentIds;
+            this.failedIds = failedIds;
+            onMembersAddedContentIdCalled = true;
+        }
+    }
+
+    private final class TestActionProvider implements AuthorizableActionProvider {
+
+        private final List<AuthorizableAction> actions = Lists.newArrayList();
+
+        private void addAction(AuthorizableAction action) {
+            actions.add(action);
+        }
+
+        @Nonnull
+        @Override
+        public List<? extends AuthorizableAction> getAuthorizableActions(@Nonnull SecurityProvider securityProvider) {
+            return actions;
+        }
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsBestEffortTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsBestEffortTest.java
------------------------------------------------------------------------------
    svn:executable = *

Copied: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsTest.java (from r1735040, jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsTest.java?p2=jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsTest.java&p1=jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java&r1=1735040&r2=1735564&rev=1735564&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportIgnoreTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImportWithActionsTest.java Fri Mar 18 10:00:35 2016
@@ -16,21 +16,86 @@
  */
 package org.apache.jackrabbit.oak.jcr.security.user;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.security.user.action.AbstractGroupAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider;
 import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
 import org.junit.Test;
 
-import static org.junit.Assert.fail;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
- * Testing {@link org.apache.jackrabbit.oak.spi.xml.ImportBehavior#IGNORE} for group import
+ * Testing {@link ImportBehavior#IGNORE} for group import
  */
-public class GroupImportIgnoreTest extends AbstractImportTest {
+public class GroupImportWithActionsTest extends AbstractImportTest {
+
+    private final TestGroupAction groupAction = new TestGroupAction();
+    private final TestActionProvider actionProvider = new TestActionProvider();
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+        actionProvider.addAction(groupAction);
+    }
+
+    @Test
+    public void testImportMembersIgnore() throws Exception {
+
+        User user1 = getUserManager().createUser("user1", "");
+        String uuid1 = getImportSession().getNode(user1.getPath()).getUUID();
+        User user2 = getUserManager().createUser("user2", "");
+        String uuid2 = getImportSession().getNode(user2.getPath()).getUUID();
+        String nonExistingUUID = UUID.randomUUID().toString();
+        String failedUUID = uuid1;
+
+        String xml =
+                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                        "<sv:node sv:name=\"gFolder\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                        "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
+                        "      <sv:value>rep:AuthorizableFolder</sv:value>" +
+                        "   </sv:property>" +
+                        "   <sv:node sv:name=\"g1\">" +
+                        "      <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
+                        "      <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>0120a4f9-196a-3f9e-b9f5-23f31f914da7</sv:value></sv:property>" +
+                        "      <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g1</sv:value></sv:property>" + "   <sv:property sv:name=\"rep:members\" sv:multiple=\"true\" sv:type=\"WeakReference\">" +
+                        "         <sv:value>" + uuid1 + "</sv:value>" +
+                        "         <sv:value>" + uuid2 + "</sv:value>" +
+                        "         <sv:value>" + nonExistingUUID + "</sv:value>" +
+                        "         <sv:value>" + failedUUID + "</sv:value>" +
+                        "      </sv:property>" +
+                        "   </sv:node>" +
+                        "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        Group g1 = (Group) getUserManager().getAuthorizable("g1");
+        assertTrue(groupAction.onMemberAddedCalled);
+        assertEquals(g1.getID(), groupAction.group.getID());
+        assertTrue(Iterables.elementsEqual(ImmutableList.of(user1.getID(), user2.getID()), groupAction.memberIds));
+        assertFalse(groupAction.onMembersAddedContentIdCalled);
+    }
 
     @Override
     protected String getImportBehavior() {
@@ -42,57 +107,47 @@ public class GroupImportIgnoreTest exten
         return GROUPPATH;
     }
 
-    @Test
-    public void testImportSelfAsGroupIgnore() throws Exception {
-        String invalidId = "0120a4f9-196a-3f9e-b9f5-23f31f914da7"; // uuid of the group itself
-        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
-                "<sv:node sv:name=\"gFolder\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
-                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:AuthorizableFolder</sv:value></sv:property>" +
-                "<sv:node sv:name=\"g1\"><sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
-                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>"+invalidId+"</sv:value></sv:property>" +
-                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g1</sv:value></sv:property>" +
-                "   <sv:property sv:name=\"rep:members\" sv:type=\"WeakReference\"><sv:value>" +invalidId+ "</sv:value></sv:property>" +
-                "</sv:node>" +
-                "</sv:node>";
-        doImport(getTargetPath(), xml);
-        // no exception during import -> member must have been ignored though.
-        Authorizable a = getUserManager().getAuthorizable("g1");
-        if (a.isGroup()) {
-            assertNotDeclaredMember((Group) a, invalidId, getImportSession());
-        } else {
-            fail("'g1' was not imported as Group.");
+    @Override
+    protected ConfigurationParameters getConfigurationParameters() {
+        Map<String, Object> userParams = new HashMap<String, Object>();
+        userParams.put(ProtectedItemImporter.PARAM_IMPORT_BEHAVIOR, getImportBehavior());
+        userParams.put(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, actionProvider);
+        return ConfigurationParameters.of(UserConfiguration.NAME, ConfigurationParameters.of(userParams));
+    }
+
+    private class TestGroupAction extends AbstractGroupAction {
+
+        private boolean onMemberAddedCalled = false;
+        private boolean onMembersAddedContentIdCalled = false;
+
+        Group group;
+        List<String> memberIds = Lists.newArrayList();
+
+        @Override
+        public void onMemberAdded(@Nonnull Group group, @Nonnull Authorizable member, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException {
+            this.group = group;
+            memberIds.add(member.getID());
+            onMemberAddedCalled = true;
+        }
+
+        @Override
+        public void onMembersAddedContentId(Group group, Iterable<String> memberContentIds, Iterable<String> failedIds, Root root, NamePathMapper namePathMapper) throws RepositoryException {
+            onMembersAddedContentIdCalled = true;
         }
     }
 
-    @Test
-    public void testImportNonExistingMemberIgnore() throws Exception {
-        List<String> invalid = new ArrayList<String>();
-        invalid.add(UUID.randomUUID().toString()); // random uuid
-        invalid.add(getExistingUUID()); // uuid of non-authorizable node
-
-        for (String id : invalid) {
-            String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
-                    "<sv:node sv:name=\"gFolder\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
-                    "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:AuthorizableFolder</sv:value></sv:property>" +
-                        "<sv:node sv:name=\"g1\"><sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
-                        "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>0120a4f9-196a-3f9e-b9f5-23f31f914da7</sv:value></sv:property>" +
-                        "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g1</sv:value></sv:property>" +
-                        "   <sv:property sv:name=\"rep:members\" sv:type=\"WeakReference\"><sv:value>" +id+ "</sv:value></sv:property>" +
-                        "</sv:node>" +
-                    "</sv:node>";
-            try {
-                // there should be no exception during import,
-                // but invalid members must be ignored.
-                doImport(getTargetPath(), xml);
-                Authorizable a = getUserManager().getAuthorizable("g1");
-                if (a.isGroup()) {
-                    assertNotDeclaredMember((Group) a, id, getImportSession());
-                } else {
-                    fail("'g1' was not imported as Group.");
-                }
-            } finally {
-                getImportSession().refresh(false);
-            }
+    private final class TestActionProvider implements AuthorizableActionProvider {
+
+        private final List<AuthorizableAction> actions = Lists.newArrayList();
+
+        private void addAction(AuthorizableAction action) {
+            actions.add(action);
+        }
+
+        @Nonnull
+        @Override
+        public List<? extends AuthorizableAction> getAuthorizableActions(@Nonnull SecurityProvider securityProvider) {
+            return actions;
         }
     }
 }