You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2009/10/22 19:26:39 UTC

svn commit: r828791 [3/8] - in /jackrabbit/trunk: jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ jackrabbi...

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java Thu Oct 22 17:26:37 2009
@@ -33,6 +33,7 @@
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
@@ -88,11 +89,15 @@
 
     private final AccessControlPolicy policy;
 
-    private Path groupsPath;
-    private Path usersPath;
+    private String groupsPath;
+    private String usersPath;
 
-    private String userAdminGroup;
-    private String groupAdminGroup;
+    private Principal userAdminGroup;
+    private Principal groupAdminGroup;
+
+    private String userAdminGroupPath;
+    private String groupAdminGroupPath;
+    private String administratorsGroupPath;
 
     /**
      *
@@ -130,22 +135,26 @@
         super.init(systemSession, configuration);
         if (systemSession instanceof SessionImpl) {
             SessionImpl sImpl = (SessionImpl) systemSession;
-            userAdminGroup = (configuration.containsKey(USER_ADMIN_GROUP_NAME)) ? configuration.get(USER_ADMIN_GROUP_NAME).toString() : USER_ADMIN_GROUP_NAME;
-            groupAdminGroup = (configuration.containsKey(GROUP_ADMIN_GROUP_NAME)) ? configuration.get(GROUP_ADMIN_GROUP_NAME).toString() : GROUP_ADMIN_GROUP_NAME;
+            String userAdminName = (configuration.containsKey(USER_ADMIN_GROUP_NAME)) ? configuration.get(USER_ADMIN_GROUP_NAME).toString() : USER_ADMIN_GROUP_NAME;
+            String groupAdminName = (configuration.containsKey(GROUP_ADMIN_GROUP_NAME)) ? configuration.get(GROUP_ADMIN_GROUP_NAME).toString() : GROUP_ADMIN_GROUP_NAME;
 
             // make sure the groups exist (and possibly create them).
             UserManager uMgr = sImpl.getUserManager();
-            if (!initGroup(uMgr, userAdminGroup)) {
-                log.warn("Unable to initialize User admininistrator group -> no user admins.");
-                userAdminGroup = null;
+            userAdminGroup = initGroup(uMgr, userAdminName);
+            if (userAdminGroup != null && userAdminGroup instanceof ItemBasedPrincipal) {
+                userAdminGroupPath = ((ItemBasedPrincipal) userAdminGroup).getPath();
             }
-            if (!initGroup(uMgr, groupAdminGroup)) {
-                log.warn("Unable to initialize Group admininistrator group -> no group admins.");
-                groupAdminGroup = null;
+            groupAdminGroup = initGroup(uMgr, groupAdminName);
+            if (groupAdminGroup != null && groupAdminGroup instanceof ItemBasedPrincipal) {
+                groupAdminGroupPath = ((ItemBasedPrincipal) groupAdminGroup).getPath();
             }
 
-            usersPath = sImpl.getQPath(USERS_PATH);
-            groupsPath = sImpl.getQPath(GROUPS_PATH);
+            Principal administrators = initGroup(uMgr, SecurityConstants.ADMINISTRATORS_NAME);
+            if (administrators != null && administrators instanceof ItemBasedPrincipal) {
+                administratorsGroupPath = ((ItemBasedPrincipal) administrators).getPath();
+            }
+            usersPath = (uMgr instanceof UserManagerImpl) ? ((UserManagerImpl) uMgr).getUsersPath() : UserConstants.USERS_PATH;
+            groupsPath = (uMgr instanceof UserManagerImpl) ? ((UserManagerImpl) uMgr).getGroupsPath() : UserConstants.GROUPS_PATH;
         } else {
             throw new RepositoryException("SessionImpl (system session) expected.");
         }
@@ -194,7 +203,7 @@
     /**
      * @see org.apache.jackrabbit.core.security.authorization.AccessControlProvider#canAccessRoot(Set)
      */
-    public boolean canAccessRoot(Set principals) throws RepositoryException {
+    public boolean canAccessRoot(Set<Principal> principals) throws RepositoryException {
         checkInitialized();
         return true;
     }
@@ -255,35 +264,36 @@
         return PrivilegeRegistry.getBits(privs);
     }
 
-    private static boolean containsGroup(Set<Principal> principals, String groupName) {
-        for (Iterator it = principals.iterator(); it.hasNext() && groupName != null;) {
-            Principal p = (Principal) it.next();
-            if (p.getName().equals(groupName)) {
+    private static boolean containsGroup(Set<Principal> principals, Principal group) {
+        for (Iterator<Principal> it = principals.iterator(); it.hasNext() && group != null;) {
+            Principal p = it.next();
+            if (p.getName().equals(group.getName())) {
                 return true;
             }
         }
         return false;
     }
 
-    private static boolean initGroup(UserManager uMgr, String principalName) {
-        boolean success;
+    private static Principal initGroup(UserManager uMgr, String principalName) {
         Principal prnc = new PrincipalImpl(principalName);
         try {
             Authorizable auth = uMgr.getAuthorizable(prnc);
             if (auth == null) {
-                success = (uMgr.createGroup(prnc) != null);
+                auth = uMgr.createGroup(prnc);
             } else {
-                success = auth.isGroup();
-                if (!success) {
+                if (!auth.isGroup()) {
                     log.warn("Cannot create group '" + principalName + "'; User with that principal already exists.");
+                    auth = null;
                 }
             }
+            if (auth != null) {
+                return auth.getPrincipal();
+            }
         } catch (RepositoryException e) {
             // should never get here
             log.error("Error while initializing user/group administrators", e.getMessage());
-            success = false;
         }
-        return success;
+        return null;
     }
 
     //--------------------------------------------------------< inner class >---
@@ -304,7 +314,7 @@
             isGroupAdmin = containsGroup(principals, groupAdminGroup);
 
             int events = Event.PROPERTY_CHANGED | Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED;
-            observationMgr.addEventListener(this, events, USERS_PATH, true, null, null, false);
+            observationMgr.addEventListener(this, events, groupsPath, true, null, null, false);
         }
 
         //------------------------------------< AbstractCompiledPermissions >---
@@ -335,102 +345,78 @@
             int privs;
             // Determine if for path, the set of privileges must be calculated:
             // Generally, privileges can only be determined for existing nodes.
-            boolean calcPrivs = session.nodeExists(resolver.getJCRPath(path.getNormalizedPath()));
+            String jcrPath = resolver.getJCRPath(path.getNormalizedPath());
+            boolean calcPrivs = session.nodeExists(jcrPath);
             if (calcPrivs) {
                 privs = getPrivilegeBits(Privilege.JCR_READ);
             } else {
                 privs = PrivilegeRegistry.NO_PRIVILEGE;
             }
 
-            Path abs2Path = (4 > path.getLength()) ? null : path.subPath(0, 4);
-            if (usersPath.equals(abs2Path)) {
+            if (Text.isDescendant(usersPath, jcrPath)) {
                 /*
                  below the user-tree
-                 - determine position of target relative
+                 - determine position of target relative to the editing user
                  - target may not be below an existing user but only below an
                    authorizable folder.
-                 - determine if the editing user is user/group-admin
-                 - special treatment for rep:groups property
+                 - determine if the editing user is user-admin
                  */
                 NodeImpl node = (NodeImpl) getExistingNode(path);
-
-                if (node.isNodeType(NT_REP_AUTHORIZABLE) || node.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
-                    boolean editingHimSelf = node.isSame(userNode);
-                    boolean isGroupProp = P_GROUPS.equals(path.getNameElement().getName());
-                    // only user-admin is allowed to modify users.
-                    // for group membership (rep:groups) group-admin is required
-                    // in addition.
-                    boolean memberOfRequiredGroups = isUserAdmin;
-                    if (memberOfRequiredGroups && isGroupProp) {
-                        memberOfRequiredGroups = isGroupAdmin;
+                if (node.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
+                    // an authorizable folder -> must be user admin in order
+                    // to have permission to write.
+                    if (isUserAdmin) {
+                        allows |= (Permission.ADD_NODE | Permission.REMOVE_NODE | Permission.SET_PROPERTY | Permission.REMOVE_PROPERTY | Permission.NODE_TYPE_MNGMT);
+                        if (calcPrivs) {
+                            // grant WRITE privilege
+                            // note: ac-read/modification is not included
+                            privs |= getPrivilegeBits(PrivilegeRegistry.REP_WRITE);
+                        }
                     }
-                    if (editingHimSelf) {
-                        /*
-                        node to be modified is same node as userNode. 3 cases to distinguish
-                        1) user is User-Admin -> R, W
-                        2) user is NOT U-admin but nodeID is its own node.
-                        3) special treatment for rep:group property which can
-                           only be modified by group-administrators
-                        */
-                        Path aPath = session.getQPath(node.getPath());
-                        if (memberOfRequiredGroups) {
-                            // principals contain 'user-admin'
-                            // -> user can modify items below the user-node except rep:group.
-                            // principals contains 'user-admin' + 'group-admin'
-                            // -> user can modify rep:group property as well.
-                            if (path.equals(aPath)) {
-                                allows |= (Permission.ADD_NODE | Permission.REMOVE_PROPERTY | Permission.SET_PROPERTY);
-                            } else {
-                                allows |= Permission.ALL;
-                            }
+                } else {
+                    // rep:User node or some other custom node below an existing user.
+                    // as the auth-folder doesn't allow other residual child nodes.
+                    boolean editingOwnUser = node.isSame(userNode);
+                    if (editingOwnUser) {
+                        // user can only read && write his own props
+                        allows |= (Permission.SET_PROPERTY | Permission.REMOVE_PROPERTY);
+                        if (calcPrivs) {
+                            privs |= getPrivilegeBits(Privilege.JCR_MODIFY_PROPERTIES);
+                        }
+                    } else if (isUserAdmin) {
+                        allows |= (Permission.ADD_NODE | Permission.REMOVE_NODE | Permission.SET_PROPERTY | Permission.REMOVE_PROPERTY | Permission.NODE_TYPE_MNGMT);
+                        if (calcPrivs) {
+                            // grant WRITE privilege
+                            // note: ac-read/modification is not included
+                            privs |= getPrivilegeBits(PrivilegeRegistry.REP_WRITE);
+                        }
+                    } // else: normal user that isn't allowed to modify another user.
+                }
+            } else if (Text.isDescendant(groupsPath, jcrPath)) {
+                /*
+                below group-tree:
+                - test if the user is group-administrator.
+                - make sure group-admin cannot modify user-admin or administrators
+                - ... and cannot remove itself.
+                */
+                if (isGroupAdmin) {
+                    if (!jcrPath.startsWith(administratorsGroupPath) &&
+                            !jcrPath.startsWith(userAdminGroupPath)) {
+                        if (jcrPath.equals(groupAdminGroupPath)) {
+                            // no remove perm on group-admin node
+                            allows |= (Permission.ADD_NODE | Permission.SET_PROPERTY | Permission.REMOVE_PROPERTY | Permission.NODE_TYPE_MNGMT);
                             if (calcPrivs) {
-                                // grant WRITE privilege
-                                // note: ac-read/modification is not included
-                                //       remove_node is not included
                                 privs |= getPrivilegeBits(PrivilegeRegistry.REP_WRITE);
-                                if (!path.equals(aPath)) {
-                                    privs |= getPrivilegeBits(Privilege.JCR_REMOVE_NODE);
-                                }
+                                privs ^= getPrivilegeBits(Privilege.JCR_REMOVE_NODE);
                             }
-                        } else if (userNode.isSame(node) && (!isGroupProp || isGroupAdmin)) {
-                            // user can only read && write his own props
-                            // except for the rep:group property.
-                            allows |= (Permission.SET_PROPERTY | Permission.REMOVE_PROPERTY);
+                        } else {
+                            // complete write
+                            allows |= (Permission.ADD_NODE | Permission.REMOVE_NODE | Permission.SET_PROPERTY | Permission.REMOVE_PROPERTY | Permission.NODE_TYPE_MNGMT);
                             if (calcPrivs) {
-                                privs |= getPrivilegeBits(Privilege.JCR_MODIFY_PROPERTIES);
-                            }
-                        } // else some other node below but not U-admin -> read-only.
-                    } else {
-                        /*
-                        authN points to some other user-node, i.e.
-                        1) nodeId points to an authorizable that isn't the editing user
-                        2) nodeId points to an auth-folder within the user-tree
-
-                        In either case user-admin group-membership is
-                        required in order to get write permission.
-                        group-admin group-membership is required in addition
-                        if rep:groups is the target item.
-                        */
-                        if (memberOfRequiredGroups) {
-                            allows = Permission.ALL;
-                            if (calcPrivs) {
-                                // grant WRITE privilege
-                                // note: ac-read/modification is not included
                                 privs |= getPrivilegeBits(PrivilegeRegistry.REP_WRITE);
                             }
                         }
                     }
-                } // outside of the user tree
-            } else if (groupsPath.equals(abs2Path)) {
-                /*
-                below group-tree:
-                - test if the user is group-administrator.
-                */
-                if (isGroupAdmin) {
-                    allows = Permission.ALL;
-                    if (calcPrivs) {
-                        privs |= getPrivilegeBits(PrivilegeRegistry.REP_WRITE);
-                    }
                 }
             } // else outside of user/group tree -> read only.
             return new Result(allows, denies, privs, PrivilegeRegistry.NO_PRIVILEGE);
@@ -480,33 +466,27 @@
                 Event ev = events.nextEvent();
                 try {
                     String evPath = ev.getPath();
-                    String repGroups = session.getJCRName(UserConstants.P_GROUPS);
-                    // TODO: add better evaluation.
-                    if (repGroups.equals(Text.getName(evPath)) &&
-                            userNodePath.equals(Text.getRelativeParent(evPath, 1))) {
-                        // recalculate the is...Admin flags
-                        switch (ev.getType()) {
-                            case Event.PROPERTY_REMOVED:
-                                isUserAdmin = false;
-                                isGroupAdmin = false;
-                                break;
-                            case Event.PROPERTY_ADDED:
-                            case Event.PROPERTY_CHANGED:
-                                if (session.propertyExists(evPath)) {
-                                    Value[] vs = session.getProperty(evPath).getValues();
-                                    String princName = session.getJCRName(P_PRINCIPAL_NAME);
-                                    for (Value v : vs) {
-                                        Node groupNode = session.getNodeByUUID(v.getString());
-                                        String pName = groupNode.getProperty(princName).getString();
-                                        if (userAdminGroup.equals(pName)) {
-                                            isUserAdmin = true;
-                                        } else if (groupAdminGroup.equals(pName)) {
-                                            isGroupAdmin = true;
-                                        }
-                                    }
+                    String repMembers = session.getJCRName(UserConstants.P_MEMBERS);
+                    if (repMembers.equals(Text.getName(evPath))) {
+                        // recalculate the is...Admin flages
+                        Node userNode = session.getNode(userNodePath);
+                        String nodePath = Text.getRelativeParent(evPath, 1);
+                        if (userAdminGroupPath.equals(nodePath)) {
+                            isUserAdmin = false;
+                            if (ev.getType() != Event.PROPERTY_REMOVED) {
+                                Value[] vs = session.getProperty(evPath).getValues();
+                                for (int i = 0; i < vs.length && !isUserAdmin; i++) {
+                                    isUserAdmin = userNode.getIdentifier().equals(vs[i].getString());
                                 }
-                                break;
-                                // default: other events are not relevant.
+                            }
+                        } else if (groupAdminGroupPath.equals(nodePath)) {
+                            isGroupAdmin = false;
+                            if (ev.getType() != Event.PROPERTY_REMOVED) {
+                                Value[] vs = session.getProperty(evPath).getValues();
+                                for (int i = 0; i < vs.length && !isGroupAdmin; i++) {
+                                    isGroupAdmin = userNode.getIdentifier().equals(vs[i].getString());
+                                }
+                            }
                         }
                         // invalidate the cached results
                         clearCache();

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserConstants.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserConstants.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserConstants.java Thu Oct 22 17:26:37 2009
@@ -55,7 +55,12 @@
     Name P_USERID = NF.create(Name.NS_REP_URI, "userId");
     Name P_PASSWORD = NF.create(Name.NS_REP_URI, "password");
 
+    /**
+     * @deprecated As of 2.0 group membership is stored with the group node.
+     * @see #P_MEMBERS
+     */
     Name P_GROUPS = NF.create(Name.NS_REP_URI, "groups");
+    Name P_MEMBERS = NF.create(Name.NS_REP_URI, "members");
 
     /**
      * Name of the user property containing the principal names of those allowed
@@ -67,5 +72,6 @@
     Name NT_REP_AUTHORIZABLE_FOLDER = NF.create(Name.NS_REP_URI, "AuthorizableFolder");
     Name NT_REP_USER = NF.create(Name.NS_REP_URI, "User");
     Name NT_REP_GROUP = NF.create(Name.NS_REP_URI, "Group");
+    Name MIX_REP_IMPERSONATABLE = NF.create(Name.NS_REP_URI, "Impersonatable");
 
 }
\ No newline at end of file

Copied: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java (from r818472, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java)
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java?p2=jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java&p1=jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java&r1=818472&r2=828791&rev=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java Thu Oct 22 17:26:37 2009
@@ -14,375 +14,557 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.core.xml;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-
-import javax.jcr.AccessDeniedException;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.Value;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.Privilege;
+package org.apache.jackrabbit.core.security.user;
 
 import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.Impersonation;
+import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
-import org.apache.jackrabbit.core.security.principal.UnknownPrincipal;
-import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.xml.DefaultProtectedPropertyImporter;
+import org.apache.jackrabbit.core.xml.PropInfo;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
+import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.jcr.RepositoryException;
+import javax.jcr.PropertyType;
+import javax.jcr.Value;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.nodetype.ConstraintViolationException;
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Arrays;
+import java.security.Principal;
+
 /**
- * <code>AccessControlImporter</code> implements a
- * <code>ProtectedNodeImporter</code> that is able to deal with access control
- * content as defined by the default ac related node types present with
- * jackrabbit-core.
+ * <code>UserImporter</code> implements a
+ * <code>DefaultProtectedPropertyImporter</code> that is able to deal with
+ * user/group content as defined by the default user related node types present
+ * with jackrabbit-core.<p/>
+ *
+ * The importer is intended to be used by applications that import user content
+ * extracted from another repository instance and immediately persist the
+ * imported content using {@link javax.jcr.Session#save()}. Omitting the
+ * save call will lead to transient, semi-validated user content and eventually
+ * to inconsistencies.
+ * <p/>
+ * Note the following restrictions:
+ * <ul>
+ * <li>The importer will only be initialized if the user manager is an instance
+ * of <code>TransientChangeUserManager</code>.
+ * </li>
+ * <li>The importer will only be initialized if the editing session starting
+ * this import is the same as the UserManager's Session instance.
+ * </li>
+ * <li>The jcr:uuid property of user and groups is defined to represent the
+ * hashed authorizable id as calculated by the UserManager. This importer
+ * is therefore not able to handle imports with
+ * {@link ImportUUIDBehavior#IMPORT_UUID_CREATE_NEW}.</li>
+ * <li>The rep:password property is expected to contain the crypted password
+ * value as stored in the content upon calling {@link UserManager#createUser}
+ * and exposed upon {@link javax.jcr.Property#getString()}
+ * or {@link javax.jcr.Session#exportSystemView}</li>
+ * <li>Importing user/group nodes outside of the hierarchy defined by
+ * {@link org.apache.jackrabbit.core.security.user.UserManagerImpl#getUsersPath()}
+ * and {@link org.apache.jackrabbit.core.security.user.UserManagerImpl#getGroupsPath()}
+ * will fail upon save as the mandatory properties will not be imported. The same may
+ * be true in case of {@link ImportUUIDBehavior#IMPORT_UUID_COLLISION_REPLACE_EXISTING}
+ * inserting the user/group node at some other place in the node hierarchy.</li>
+ * <li>While creating user/groups through the API the <code>UserManagerImpl</code> makes
+ * sure that authorizables are never nested and are created below a hierarchy
+ * of nt:AuthorizableFolder nodes. This isn't efforced by means of node type
+ * constraints but only by the API. This importer currently doesn't perform such
+ * a validation check.</li>
+ * <li>Any attempt to import conflicting data will cause the import to fail
+ * either immediately or upon calling {@link javax.jcr.Session#save()} with the
+ * following exceptions:
+ * <ul>
+ * <li><code>rep:members</code> : Group membership</li>
+ * <li><code>rep:impersonators</code> : Impersonators of a User.</li>
+ * </ul>
+ * The import behavior of these two properties is defined by the {@link #PARAM_IMPORT_BEHAVIOR}
+ * configuration parameter, which can be set to
+ * <ul>
+ * <li>{@link ImportBehavior#NAME_IGNORE ignore}: A warning is logged.</li>
+ * <li>{@link ImportBehavior#NAME_BESTEFFORT besteffort}: A warning is logged
+ * and the importer tries to fix the problem.</li>
+ * <li>{@link ImportBehavior#NAME_ABORT abort}: The import is immediately
+ * aborted with a ConstraintViolationException. (<strong>default</strong>)</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * Known Issue:<br>
+ * Importing <code>rep:impersonators</code> property refering to principals
+ * that are created during this import AND have principalName different from the
+ * ID will no succeed, as the validation in <code>ImpersonationImpl</code> isn't able
+ * to find the authorizable with the given principal (reason: query will only
+ * find persisted content).
  */
-public class AccessControlImporter extends DefaultProtectedNodeImporter {
+public class UserImporter extends DefaultProtectedPropertyImporter {
 
     /**
      * logger instance
      */
-    private static final Logger log = LoggerFactory.getLogger(AccessControlImporter.class);
+    private static final Logger log = LoggerFactory.getLogger(UserImporter.class);
 
-    private static final int STATUS_UNDEFINED = 0;
-    private static final int STATUS_AC_FOLDER = 1;
-    private static final int STATUS_PRINCIPAL_AC = 2;
-    private static final int STATUS_ACL = 3;
-    private static final int STATUS_ACE = 4;
-
-    private static final Set<Name> ACE_NODETYPES = new HashSet<Name>(2);
-    static {
-        ACE_NODETYPES.add(AccessControlConstants.NT_REP_DENY_ACE);
-        ACE_NODETYPES.add(AccessControlConstants.NT_REP_GRANT_ACE);
-    }
+    public static final String PARAM_IMPORT_BEHAVIOR = "importBehavior";
 
-    private final AccessControlManager acMgr;
-    private final Stack<Integer> prevStatus = new Stack<Integer>();
+    private UserPerWorkspaceUserManager userManager;
 
-    private int status = STATUS_UNDEFINED;
-    private NodeImpl parent = null;
+    private boolean initialized = false;
 
-    private boolean principalbased = false;
+    private boolean resetAutoSave = false;
 
-    /**
-     * the ACL for non-principal based
-     */
-    private JackrabbitAccessControlList acl = null;
+    private int importBehavior = ImportBehavior.IGNORE;
 
-    public AccessControlImporter(JackrabbitSession session, NamePathResolver resolver,
-                                 boolean isWorkspaceImport, int uuidBehavior) throws RepositoryException {
-        super(session, resolver, isWorkspaceImport, uuidBehavior);
-
-        acMgr = session.getAccessControlManager();
-    }
-
-    public boolean start(NodeImpl protectedParent) throws RepositoryException {
-        if (isStarted()) {
-            // only ok if same parent
-            if (!protectedParent.isSame(parent)) {
-                throw new IllegalStateException();
+    @Override
+    public boolean init(JackrabbitSession session, NamePathResolver resolver,
+                        boolean isWorkspaceImport,
+                        int uuidBehavior, ReferenceChangeTracker referenceTracker) {
+        if (super.init(session, resolver, isWorkspaceImport, uuidBehavior, referenceTracker)) {
+            if (initialized) {
+                throw new IllegalStateException("Already initialized");
             }
-            return true;
-        }
-        if (isWorkspaceImport) {
-            log.debug("AccessControlImporter may not be used with the WorkspaceImporter");
-            return false;
-        }
-        if (!protectedParent.getDefinition().isProtected()) {
-            log.debug("AccessControlImporter may not be started with a non-protected parent.");
-            return false;
-        }
-
-        if (AccessControlConstants.N_POLICY.equals(protectedParent.getQName())
-                && protectedParent.isNodeType(AccessControlConstants.NT_REP_ACL)) {
-            acl = getACL(protectedParent.getParent().getPath());
-            if (acl == null) {
-                log.warn("AccessControlImporter cannot be started: no ACL for {}.", parent.getParent().getPath());
+            if (uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW) {
+                log.debug("ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW isn't supported when importing users or groups.");
+                return false;
+            }
+            if (isWorkspaceImport) {
+                log.debug("Only Session-Import is supported when importing users or groups.");
                 return false;
             }
-            status = STATUS_ACL;
-        } else if (protectedParent.isNodeType(AccessControlConstants.NT_REP_ACCESS_CONTROL)) {
-            status = STATUS_AC_FOLDER;
-            principalbased = true;
-            acl = null;
-        } // else: nothing this importer can deal with.
-
-        if (isStarted()) {
-            parent = protectedParent;
-            return true;
-        } else {
+            try {
+                UserManager uMgr = session.getUserManager();
+                if (uMgr instanceof UserPerWorkspaceUserManager) {
+                    // make sure the user managers autosave flag can be changed to false.
+                    if (uMgr.isAutoSave()) {
+                        uMgr.autoSave(false);
+                        resetAutoSave = true;
+                        log.debug("Changed autosave behavior of UserManager to 'false'.");
+                    }
+                    userManager = (UserPerWorkspaceUserManager) uMgr;
+                    initialized = true;
+                } else {
+                    // either wrong implementation or one that implicitly calls save.
+                    log.debug("Failed to initialize UserImporter: UserManager isn't instance of UserPerWorkspaceUserManager or does implicit save call.");
+                }
+            } catch (RepositoryException e) {
+                // failed to access user manager or to set the autosave behavior
+                // -> return false (not initialized) as importer can't operate.
+                log.error("Failed to initialize UserImporter: ", e);
+            }
+        }
+        return initialized;
+    }
+
+    @Override
+    public boolean handlePropInfo(NodeImpl parent, PropInfo protectedPropInfo, QPropertyDefinition def) throws RepositoryException {
+        if (!initialized) {
+            throw new IllegalStateException("Not initialized");
+        }
+
+        /* importer can only handle protected properties below user/group
+           nodes that are properly stored underneith the configured users/groups
+           hierarchies (see {@link UserManagerImpl#getAuthorizable(NodeImpl)}.
+           this prevents from importing user/group nodes somewhere in the
+           content hierarchy which isn't possible when creating user/groups
+           using the corresponding API calls  {@link UserManager#createUser} or
+           {@link UserManager#createGroup} respectively. */
+        Authorizable a = userManager.getAuthorizable(parent);
+        if (a == null) {
+            log.warn("Cannot handle protected PropInfo " + protectedPropInfo + ". Node " + parent + " doesn't represent a valid Authorizable.");
             return false;
         }
-    }
 
-    private JackrabbitAccessControlList getACL(String path) throws RepositoryException, AccessDeniedException {
-        JackrabbitAccessControlList acl = null;
-        for (AccessControlPolicy p: acMgr.getPolicies(path)) {
-            if (p instanceof JackrabbitAccessControlList) {
-                acl = (JackrabbitAccessControlList) p;
-                // don't know if this check is needed
-                if (path.equals(acl.getPath())) {
-                    break;
+        // TODO: check if import should be aborted in case of nested authorizable.
+
+        // assert that user manager is isn't in auto-save mode
+        if (userManager.isAutoSave()) {
+            userManager.autoSave(false);
+        }
+        try {
+            Name propName = protectedPropInfo.getName();
+            if (UserConstants.P_PRINCIPAL_NAME.equals(propName)) {
+                // minimal validation that passed definition really matches the
+                // protected rep:principalName property defined by rep:Authorizable.
+                if (def.isMultiple() || !UserConstants.NT_REP_AUTHORIZABLE.equals(def.getDeclaringNodeType())) {
+                    // some other unexpected property definition -> cannot handle
+                    log.warn("Unexpected definition for property rep:principalName");
+                    return false;
                 }
-                acl = null;
-            }
-        }
-        if (acl != null) {
-            // clear all existing entries
-            for (AccessControlEntry ace: acl.getAccessControlEntries()) {
-                acl.removeAccessControlEntry(ace);
-            }
-        }
-        return acl;
-    }
 
-    public boolean start(NodeState protectedParent) throws IllegalStateException, RepositoryException {
-        if (isStarted()) {
-            throw new IllegalStateException();
-        }
-        if (isWorkspaceImport) {
-            log.debug("AccessControlImporter may not be used with the WorkspaceImporter");
+                Value v = protectedPropInfo.getValues(PropertyType.STRING, resolver)[0];
+                String princName = v.getString();
+                userManager.setPrincipal(parent, new PrincipalImpl(princName));
+                return true;
+            } else if (UserConstants.P_PASSWORD.equals(propName)) {
+                if (a.isGroup()) {
+                    log.warn("Expected parent node of type rep:User.");
+                    return false;
+                }
+                // minimal validation of the passed definition
+                if (def.isMultiple() || !UserConstants.NT_REP_USER.equals(def.getDeclaringNodeType())) {
+                    // some other unexpected property definition -> cannot handle
+                    log.warn("Unexpected definition for property rep:password");
+                    return false;
+                }
+
+                // expectation: pw must already be crypted.
+                Value v = protectedPropInfo.getValues(PropertyType.STRING, resolver)[0];
+                userManager.setProtectedProperty(parent, UserConstants.P_PASSWORD, v);
+
+                return true;
+
+            } else if (UserConstants.P_IMPERSONATORS.equals(propName)) {
+                if (a.isGroup()) {
+                    // unexpected parent type -> cannot handle
+                    log.warn("Expected parent node of type rep:User.");
+                    return false;
+                }
+
+                // minimal validation of the passed definition
+                if (!def.isMultiple() || !UserConstants.MIX_REP_IMPERSONATABLE.equals(def.getDeclaringNodeType())) {
+                    // some other unexpected property definition -> cannot handle
+                    log.warn("Unexpected definition for property rep:impersonators");
+                    return false;
+                }
+
+                // since impersonators may be imported later on, postpone processing
+                // to the end.
+                // see -> processRefeferences
+                Value[] vs = protectedPropInfo.getValues(PropertyType.STRING, resolver);
+                referenceTracker.processedReference(new Impersonators(a.getID(), vs));
+                return true;
+
+            } else if (UserConstants.P_MEMBERS.equals(propName)) {
+                if (!a.isGroup()) {
+                    // unexpected parent type -> cannot handle
+                    log.warn("Expected parent node of type rep:Group.");
+                    return false;
+                }
+
+                // minimal validation of the passed definition
+                if (!def.isMultiple() || !UserConstants.NT_REP_GROUP.equals(def.getDeclaringNodeType())) {
+                    // some other unexpected property definition -> cannot handle
+                    log.warn("Unexpected definition for property rep:members");
+                    return false;
+                }
+
+                // since group-members are references to user/groups that potentially
+                // are to be imported later on -> postpone processing to the end.
+                // see -> processRefeferences
+                Value[] vs = protectedPropInfo.getValues(PropertyType.WEAKREFERENCE, resolver);
+                NodeId[] ids = new NodeId[vs.length];
+                for (int i = 0; i < vs.length; i++) {
+                    ids[i] = new NodeId(vs[i].getString());
+                }
+                referenceTracker.processedReference(new Membership(a.getID(), ids));
+                return true;
+
+            } // else: cannot handle -> return false
+
             return false;
+        } finally {
+            // reset the autosave mode of the user manager in order to restore
+            // the original state.
+            if (resetAutoSave) {
+                userManager.autoSave(true);
+            }
         }
-        return false;
     }
 
-    public void end(NodeImpl protectedParent) throws RepositoryException {
-        if (!isStarted()) {
-            return;
+    @Override
+    public void processReferences() throws RepositoryException {
+        if (!initialized) {
+            throw new IllegalStateException("Not initialized");
         }
 
-        if (!principalbased) {
-            checkStatus(STATUS_ACL, "");
-            acMgr.setPolicy(acl.getPath(), acl);
-        } else {
-            checkStatus(STATUS_AC_FOLDER, "");
-            if (!prevStatus.isEmpty()) {
-                throw new ConstraintViolationException("Incomplete protected item tree: "+ prevStatus.size()+ " calls to 'endChildInfo' missing.");
-            }
+        // assert that user manager is isn't in auto-save mode
+        if (userManager.isAutoSave()) {
+            userManager.autoSave(false);
         }
-        reset();
-    }
+        try {
+            List<Object> processed = new ArrayList();
+            for (Iterator<Object> it = referenceTracker.getProcessedReferences(); it.hasNext();) {
+                Object reference = it.next();
+                if (reference instanceof Membership) {
+                    Authorizable a = userManager.getAuthorizable(((Membership) reference).groupId);
+                    if (a == null || !a.isGroup()) {
+                        throw new RepositoryException(((Membership) reference).groupId + " does not represent a valid group.");
+                    }
 
-    public void end(NodeState protectedParent) throws IllegalStateException, ConstraintViolationException, RepositoryException {
-        // nothing to do. will never get here.
-    }
+                    Group gr = (Group) a;
+                    // 1. collect members to add and to remove.
+                    Map<String, Authorizable> toRemove = new HashMap();
+                    for (Iterator<Authorizable> aIt = gr.getDeclaredMembers(); it.hasNext();) {
+                        Authorizable dm = aIt.next();
+                        toRemove.put(dm.getID(), dm);
+                    }
 
-    public void startChildInfo(NodeInfo childInfo, List<PropInfo> propInfos) throws RepositoryException {
-        if (!isStarted()) {
-            return;
-        }
-        
-        Name ntName = childInfo.getNodeTypeName();
-        int previousStatus = status;
-
-        if (!principalbased) {
-            checkStatus(STATUS_ACL, "Cannot handle childInfo " + childInfo + "; rep:ACL may only contain a single level of child nodes representing the ACEs");
-            addACE(childInfo, propInfos);
-            status = STATUS_ACE;
-        } else {
-            switch (status) {
-                case STATUS_AC_FOLDER:
-                    if (AccessControlConstants.NT_REP_ACCESS_CONTROL.equals(ntName)) {
-                        // yet another intermediate node -> keep status
-                    } else if (AccessControlConstants.NT_REP_PRINCIPAL_ACCESS_CONTROL.equals(ntName)) {
-                        // the start of a principal-based acl
-                        status = STATUS_PRINCIPAL_AC;
-                    } else {
-                        // illegal node type.
-                        throw new ConstraintViolationException("Unexpected node type " + ntName + ". Should be rep:AccessControl or rep:PrincipalAccessControl.");
-                    }
-                    checkIdMixins(childInfo);
-                    break;
-                case STATUS_PRINCIPAL_AC:
-                    if (AccessControlConstants.NT_REP_ACCESS_CONTROL.equals(ntName)) {
-                        // some intermediate path between principal paths.
-                        status = STATUS_AC_FOLDER;
-                    } else if (AccessControlConstants.NT_REP_PRINCIPAL_ACCESS_CONTROL.equals(ntName)) {
-                        // principal-based ac node underneath another one
-                        // keep status
-                    } else {
-                        // the start the acl definition itself
-                        checkDefinition(childInfo, AccessControlConstants.N_POLICY, AccessControlConstants.NT_REP_ACL);
-                        status = STATUS_ACL;
-                    }
-                    checkIdMixins(childInfo);
-                    break;
-                case STATUS_ACL:
-                    // nodeinfo must define an ACE
-                    addACE(childInfo, propInfos);
-                    status = STATUS_ACE;
-                    break;
-                default:
-                    throw new ConstraintViolationException("Cannot handle childInfo " + childInfo + "; inexpected status " + status + " .");
+                    List<Authorizable> toAdd = new ArrayList();
+                    List<Value> nonExisting = new ArrayList();
+
+                    for (NodeId originalId : ((Membership) reference).ids) {
+
+                        NodeId remapped = referenceTracker.getMappedId(originalId);
+                        NodeId id = (remapped == null) ? originalId : remapped;
 
+                        Authorizable authorz = null;
+                        try {
+                            NodeImpl n = ((SessionImpl) session).getNodeById(id);
+                            authorz = userManager.getAuthorizable(n);
+                        } catch (RepositoryException e) {
+                            // no such node or failed to retrieve authorizable
+                            // warning is logged below.
+                        }
+                        if (authorz != null) {
+                            if (toRemove.remove(authorz.getID()) == null) {
+                                toAdd.add(authorz);
+                            } // else: no need to remove from rep:members
+                        } else {
+                            handleFailure("Ignoring new member of " + gr + ". No such authorizable (NodeID = " + id + ")");
+                            if (importBehavior == ImportBehavior.BESTEFFORT) {
+                                nonExisting.add(session.getValueFactory().createValue(id.toString(), PropertyType.WEAKREFERENCE));
+                            }
+                        }
+                    }
+
+                    // 2. adjust members of the group
+                    for (Authorizable m : toRemove.values()) {
+                        if (!gr.removeMember(m)) {
+                            handleFailure("Failed remove existing member (" + m + ") from " + gr);
+                        }
+                    }
+                    for (Authorizable m : toAdd) {
+                        if (!gr.addMember(m)) {
+                            handleFailure("Failed add member (" + m + ") to " + gr);
+                        }
+                    }
+
+                    // handling non-existing members in case of best-effort
+                    if (!nonExisting.isEmpty()) {
+                        log.warn("Found " + nonExisting.size() + " entries of rep:members pointing to non-existing authorizables. Best-effort approach configured -> add to rep:members.");
+
+                        NodeImpl groupNode = ((AuthorizableImpl) gr).getNode();
+                        // build list of valid members set before ....
+                        List<Value> memberValues = new ArrayList();
+                        if (groupNode.hasProperty(UserConstants.P_MEMBERS)) {
+                            Value[] vls = groupNode.getProperty(UserConstants.P_MEMBERS).getValues();
+                            memberValues.addAll(Arrays.asList(vls));
+                        }
+                        // ... and the non-Existing onces.
+                        memberValues.addAll(nonExisting);
+                        // and use implementation specific method to set the
+                        // value of rep:members properties which was not possible
+                        // through the API
+                        userManager.setProtectedProperty(groupNode, UserConstants.P_MEMBERS, memberValues.toArray(new Value[memberValues.size()]));
+                    }
+
+                    processed.add(reference);
+
+                } else if (reference instanceof Impersonators) {
+                    Authorizable a = userManager.getAuthorizable(((Impersonators) reference).userId);
+                    if (a == null || a.isGroup()) {
+                        throw new RepositoryException(((Impersonators) reference).userId + " does not represent a valid user.");
+                    }
+
+                    Impersonation imp = ((User) a).getImpersonation();
+
+                    // 1. collect principals to add and to remove.
+                    Map<String, Principal> toRemove = new HashMap();
+                    for (PrincipalIterator pit = imp.getImpersonators(); pit.hasNext();) {
+                        Principal princ = pit.nextPrincipal();
+                        toRemove.put(princ.getName(), princ);
+                    }
+
+                    List<Principal> toAdd = new ArrayList();
+                    Value[] vs = ((Impersonators) reference).values;
+                    for (Value v : vs) {
+                        String princName = v.getString();
+                        if (toRemove.remove(princName) == null) {
+                            // add it to the list of new impersonators to be added.
+                            toAdd.add(new PrincipalImpl(princName));
+                        } // else: no need to revoke impersonation for the given principal.
+                    }
+
+                    // 2. adjust set of impersonators
+                    for (Principal princ : toRemove.values()) {
+                        if (!imp.revokeImpersonation(princ)) {
+                            handleFailure("Failed to revoke impersonation for " + princ.getName() + " on " + a);
+                        }
+                    }
+                    for (Principal princ : toAdd) {
+                        if (!imp.grantImpersonation(princ)) {
+                            handleFailure("Failed to grant impersonation for " + princ.getName() + " on " + a);
+                        }
+                    }
+                    // NOTE: no best effort handling so far. (TODO)
+
+                    processed.add(reference);
+                }
+            }
+            // successfully processed this entry of the reference tracker
+            // -> remove from the reference tracker.
+            referenceTracker.removeReferences(processed);
+        } finally {
+            // reset the autosave mode of the user manager in order to restore
+            // the original state.
+            if (resetAutoSave) {
+                userManager.autoSave(true);
             }
         }
-        prevStatus.push(previousStatus);        
     }
 
-    /**
-     * @throws javax.jcr.RepositoryException
-     */
-    public void endChildInfo() throws RepositoryException {
-        if (!isStarted()) {
-            return;
-        }
-
-        // if the protected imported is started at an existing protected node
-        // SessionImporter does not remember it on the stack of parents node.
-        if (!principalbased) {
-            // childInfo + props have already been handled
-            // -> assert valid status
-            // -> no further actions required.
-            checkStatus(STATUS_ACE, "Upon completion of a NodeInfo the status must be STATUS_ACE.");
-        }
+    //------------------------------------------------------------< private >---
+    private void handleFailure(String msg) throws RepositoryException {
+        switch (importBehavior) {
+            case ImportBehavior.IGNORE:
+            case ImportBehavior.BESTEFFORT:
+                log.warn(msg);
+                break;
+            case ImportBehavior.ABORT:
+                throw new ConstraintViolationException(msg);
+            default:
+                // no other behavior. nothing to do.
 
-        // reset the status
-        status = prevStatus.pop();
+        }
     }
 
 
-    private boolean isStarted() {
-        return status > STATUS_UNDEFINED;
+    //---------------------------------------------------------< BeanConfig >---
+    /**
+     * @return human readable representation of the <code>importBehavior</code> value.
+     */
+    public String getImportBehavior() {
+        return ImportBehavior.nameFromValue(importBehavior);
     }
-    
-    private void reset() {
-        status = STATUS_UNDEFINED;
-        parent = null;
-        acl = null;
+
+    /**
+     *
+     * @param importBehaviorStr
+     */
+    public void setImportBehavior(String importBehaviorStr) {
+        this.importBehavior = ImportBehavior.valueFromName(importBehaviorStr);
     }
 
-    private void checkStatus(int expectedStatus, String message) throws ConstraintViolationException {
-        if (status != expectedStatus) {
-            throw new ConstraintViolationException(message);
+    /**
+     *
+     * @param importBehavior
+     */
+    public void setImportBehavior(int importBehavior) {
+        switch (importBehavior) {
+            case ImportBehavior.IGNORE:
+            case ImportBehavior.ABORT:
+            case ImportBehavior.BESTEFFORT:
+                this.importBehavior = importBehavior;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid import behavior: " + importBehavior);
         }
     }
 
-    private static void checkDefinition(NodeInfo nInfo, Name expName, Name expNodeTypeName) throws ConstraintViolationException {
-        if (expName != null && !expName.equals(nInfo.getName())) {
-            // illegal name
-            throw new ConstraintViolationException("Unexpected Node name "+ nInfo.getName() +". Node name should be " + expName + ".");
-        }
-        if (expNodeTypeName != null && !expNodeTypeName.equals(nInfo.getNodeTypeName())) {
-            // illegal name
-            throw new ConstraintViolationException("Unexpected node type " + nInfo.getNodeTypeName() + ". Node type should be " + expNodeTypeName + ".");
+    //--------------------------------------------------------------------------
+    /**
+     * Inner class used to postpone import of group membership to the very end
+     * of the import. This allows to import membership of user/groups that
+     * are only being created during this import.
+     *
+     * @see ImportBehavior For additional configuration options.
+     */
+    private final class Membership {
+
+        private final String groupId;
+        private final NodeId[] ids;
+
+        private Membership(String groupId, NodeId[] ids) {
+            this.groupId = groupId;
+            this.ids = ids;
         }
     }
 
-    private static void checkIdMixins(NodeInfo nInfo) throws ConstraintViolationException {
-        // neither explicit id NOR mixin types may be present.
-        Name[] mixins = nInfo.getMixinNames();
-        NodeId id = nInfo.getId();
-        if (id != null || mixins != null) {
-            throw new ConstraintViolationException("The node represented by NodeInfo " + nInfo + " may neither be referenceable nor have mixin types.");
+    /**
+     * Inner class used to postpone import of impersonators to the very end
+     * of the import. This allows to import impersonation values pointing
+     * to user that are only being created during this import.
+     *
+     * @see ImportBehavior For additional configuration options.
+     */
+    private final class Impersonators {
+
+        private final String userId;
+        private final Value[] values;
+
+        private Impersonators(String userId, Value[] values) {
+            this.userId = userId;
+            this.values = values;
         }
     }
 
-    private void addACE(NodeInfo childInfo, List<PropInfo> propInfos) throws RepositoryException, UnsupportedRepositoryOperationException {
+    /**
+     * Inner class defining the treatment of membership or impersonator
+     * values pointing to non-existing authorizables.
+     */
+    public static final class ImportBehavior {
 
-        // node type may only be rep:GrantACE or rep:DenyACE
-        Name ntName = childInfo.getNodeTypeName();
-        if (!ACE_NODETYPES.contains(ntName)) {
-            throw new ConstraintViolationException("Cannot handle childInfo " + childInfo + "; expected a valid, applicable rep:ACE node definition.");
-        }
-
-        checkIdMixins(childInfo);
-
-        boolean isAllow = AccessControlConstants.NT_REP_GRANT_ACE.equals(ntName);
-        Principal principal = null;
-        Privilege[] privileges = null;
-        Map<String, TextValue> restrictions = new HashMap<String, TextValue>();
-
-        for (PropInfo pInfo : propInfos) {
-            Name name = pInfo.getName();
-            if (AccessControlConstants.P_PRINCIPAL_NAME.equals(name)) {
-                Value[] values = pInfo.getValues(PropertyType.STRING, resolver);
-                if (values == null || values.length != 1) {
-                    throw new ConstraintViolationException("");
-                }
-                String pName = values[0].getString();
-                principal = session.getPrincipalManager().getPrincipal(pName);
-                if (principal == null) {
-                    // create "fake" principal
-                    principal = new UnknownPrincipal(pName);
-                }
-            } else if (AccessControlConstants.P_PRIVILEGES.equals(name)) {
-                Value[] values = pInfo.getValues(PropertyType.NAME, resolver);
-                privileges = new Privilege[values.length];
-                for (int i = 0; i < values.length; i++) {
-                    privileges[i] = acMgr.privilegeFromName(values[i].getString());
-                }
+        /**
+         * If a member or impersonator value cannot be set due to constraints
+         * enforced by the API implementation, the failure is logged as
+         * warning but otherwise ignored.
+         */
+        public static final int IGNORE = 1;
+        /**
+         * Same as {@link #IGNORE} but in addition tries to circumvent the
+         * problem. This option should only be used with validated and trusted
+         * XML passed to the SessionImporter.
+         */
+        public static final int BESTEFFORT = 2;
+        /**
+         * Aborts the import as soon as invalid values are detected throwing
+         * a <code>ConstraintViolationException</code>.
+         */
+        public static final int ABORT = 3;
+
+        public static final String NAME_IGNORE = "ignore";
+        public static final String NAME_BESTEFFORT = "besteffort";
+        public static final String NAME_ABORT = "abort";
+
+        public static int valueFromName(String behaviorString) {
+            if (NAME_IGNORE.equalsIgnoreCase(behaviorString)) {
+                return IGNORE;
+            } else if (NAME_BESTEFFORT.equalsIgnoreCase(behaviorString)) {
+                return BESTEFFORT;
+            } else if (NAME_ABORT.equalsIgnoreCase(behaviorString)) {
+                return ABORT;
             } else {
-                TextValue[] txtVls = pInfo.getTextValues();
-                for (TextValue txtV : txtVls) {
-                    restrictions.put(resolver.getJCRName(name), txtV);
-                }
+                log.error("Invalid behavior " + behaviorString + " -> Using default: ABORT.");
+                return ABORT;
             }
         }
 
-        if (principalbased) {
-            // try to access policies
-            List<AccessControlPolicy> policies = new ArrayList<AccessControlPolicy>();
-            if (acMgr instanceof JackrabbitAccessControlManager) {
-                JackrabbitAccessControlManager jacMgr = (JackrabbitAccessControlManager) acMgr;
-                policies.addAll(Arrays.asList(jacMgr.getPolicies(principal)));
-                policies.addAll(Arrays.asList(jacMgr.getApplicablePolicies(principal)));
-            }
-            for (AccessControlPolicy policy : policies) {
-                if (policy instanceof JackrabbitAccessControlList) {
-                    JackrabbitAccessControlList acl = (JackrabbitAccessControlList) policy;
-                    Map<String, Value> restr = new HashMap<String, Value>();
-                    for (String restName : acl.getRestrictionNames()) {
-                        TextValue txtVal = restrictions.remove(restName);
-                        if (txtVal != null) {
-                            restr.put(restName, txtVal.getValue(acl.getRestrictionType(restName), resolver));
-                        }
-                    }
-                    if (!restrictions.isEmpty()) {
-                        throw new ConstraintViolationException("ACE childInfo contained restrictions that could not be applied.");
-                    }
-                    acl.addEntry(principal, privileges, isAllow, restr);
-                    acMgr.setPolicy(acl.getPath(), acl);
-                    return;
-                }
-            }
-        } else {
-            Map<String, Value> restr = new HashMap<String, Value>();
-            for (String restName : acl.getRestrictionNames()) {
-                TextValue txtVal = restrictions.remove(restName);
-                if (txtVal != null) {
-                    restr.put(restName, txtVal.getValue(acl.getRestrictionType(restName), resolver));
-                }
-            }
-            if (!restrictions.isEmpty()) {
-                throw new ConstraintViolationException("ACE childInfo contained restrictions that could not be applied.");
+        public static String nameFromValue(int importBehavior) {
+            switch (importBehavior) {
+                case ImportBehavior.IGNORE:
+                    return NAME_IGNORE;
+                case ImportBehavior.ABORT:
+                    return NAME_ABORT;
+                case ImportBehavior.BESTEFFORT:
+                    return NAME_BESTEFFORT;
+                default:
+                    throw new IllegalArgumentException("Invalid import behavior: " + importBehavior);
             }
-            acl.addEntry(principal, privileges, isAllow, restr);
-            return;
         }
-
-
-        // could not apply the ACE. No suitable ACL found.
-        throw new ConstraintViolationException("Cannot handle childInfo " + childInfo + "; No policy found to apply the ACE.");        
     }
-}
+}
\ No newline at end of file

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url