Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/autosave/AutoSaveEnabledManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/autosave/AutoSaveEnabledManager.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/autosave/AutoSaveEnabledManager.java	(revision )
@@ -24,6 +24,7 @@
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.Query;
 import org.apache.jackrabbit.api.security.user.User;
@@ -107,6 +108,15 @@
     public User createUser(String userID, String password, Principal principal, @Nullable String intermediatePath) throws RepositoryException {
         try {
             return wrap(dlg.createUser(userID, password, principal, intermediatePath));
+        } finally {
+            autosave();
+        }
+    }
+
+    @Override
+    public User createSystemUser(String userID, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        try {
+            return wrap(dlg.createUser(userID, intermediatePath));
         } finally {
             autosave();
         }
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/SystemUserPrincipal.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/SystemUserPrincipal.java	(revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/SystemUserPrincipal.java	(revision )
@@ -0,0 +1,25 @@
+/*
+ * 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.principal;
+
+import java.security.Principal;
+
+/**
+ * Principal used to mark a system user.
+ */
+public interface SystemUserPrincipal extends Principal {
+}
\ No newline at end of file
Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/UserManagerDelegator.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/UserManagerDelegator.java	(revision 1617701)
+++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/UserManagerDelegator.java	(revision )
@@ -170,6 +170,18 @@
     }
 
     @Override
+    public User createSystemUser(final String userID, final String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        return sessionDelegate.perform(
+                new UserManagerOperation<User>(sessionDelegate, "createUser") {
+                    @Override
+                    public User perform() throws RepositoryException {
+                        User user = userManagerDelegate.createSystemUser(userID, intermediatePath);
+                        return UserDelegator.wrap(sessionDelegate, user);
+                    }
+                });
+    }
+
+    @Override
     public Group createGroup(final String groupID) throws AuthorizableExistsException, RepositoryException {
         return sessionDelegate.perform(
                 new UserManagerOperation<Group>(sessionDelegate, "createGroup") {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java	(revision )
@@ -285,6 +285,10 @@
                 log.warn("Unexpected authorizable or definition for property rep:password");
                 return false;
             }
+            if (((User) a).isSystemUser()) {
+                log.warn("System users may not have a password set.");
+                return false;
+            }
 
             String pw = propInfo.getTextValue().getString();
             userManager.setPassword(parent, a.getID(), pw, false);
\ No newline at end of file
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/SystemUserImplTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/SystemUserImplTest.java	(revision )
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/SystemUserImplTest.java	(revision )
@@ -0,0 +1,259 @@
+/*
+ * 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.security.user;
+
+import java.util.Iterator;
+import java.util.UUID;
+import javax.annotation.Nullable;
+import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.apache.jackrabbit.oak.util.TreeUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Implementation specific test wrt system users.
+ */
+public class SystemUserImplTest extends AbstractSecurityTest {
+
+    private UserManager userMgr;
+    private String uid;
+    private User user;
+
+    @Override
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        userMgr = getUserManager(root);
+        uid = "testUser" + UUID.randomUUID();
+    }
+
+    @Override
+    public void after() throws Exception {
+        try {
+            if (user != null) {
+                user.remove();
+                root.commit();
+            }
+        } finally {
+            super.after();
+        }
+    }
+
+    private User createUser(@Nullable String intermediatePath) throws Exception {
+        User user = userMgr.createSystemUser(uid, intermediatePath);
+        root.commit();
+        return user;
+    }
+
+    @Test
+    public void testCreateSystemUser() throws Exception {
+        user = createUser(null);
+
+        assertTrue(user instanceof SystemUserImpl);
+    }
+
+    @Test
+    public void testSystemUserTree() throws Exception {
+        user = createUser(null);
+        Tree t = root.getTree(user.getPath());
+        assertFalse(t.hasProperty(UserConstants.REP_PASSWORD));
+        assertEquals(UserConstants.NT_REP_SYSTEM_USER, TreeUtil.getPrimaryTypeName(t));
+    }
+
+    @Test
+    public void testGetCredentials() throws Exception {
+        user = createUser(null);
+
+        Credentials creds = user.getCredentials();
+        assertTrue(creds instanceof CredentialsImpl);
+
+        CredentialsImpl impl = (CredentialsImpl) creds;
+        assertEquals(uid, impl.getUserId());
+        assertNull(impl.getPasswordHash());
+    }
+
+
+    /**
+     * @since OAK 1.0 In contrast to Jackrabbit core the intermediate path may
+     * not be an absolute path in OAK.
+     */
+    @Test
+    public void testCreateUserWithAbsolutePath() throws Exception {
+        try {
+            user = createUser("/any/path/to/the/new/user");
+            fail("ConstraintViolationException expected");
+        } catch (ConstraintViolationException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testCreateUserWithAbsolutePath2() throws Exception {
+        try {
+            user = createUser(UserConstants.DEFAULT_USER_PATH + "/any/path/to/the/new/user");
+            fail("ConstraintViolationException expected");
+        } catch (ConstraintViolationException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testCreateUserWithAbsolutePath3() throws Exception {
+        String userRoot = UserConstants.DEFAULT_USER_PATH + '/' + UserConstants.DEFAULT_SYSTEM_PATH;
+        String path = userRoot + "/any/path/to/the/new/user";
+
+        user = createUser(path);
+        assertTrue(user.getPath().startsWith(path));
+    }
+
+    @Test
+    public void testCreateUserWithRelativePath() throws Exception {
+        try {
+            user = createUser("any/path");
+            fail("ConstraintViolationException expected");
+        }  catch (ConstraintViolationException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testCreateUserWithRelativePath2() throws Exception {
+        user = createUser(UserConstants.DEFAULT_SYSTEM_PATH + "/any/path");
+
+        assertNotNull(user.getID());
+        assertTrue(user.getPath().contains("any/path"));
+    }
+
+    @Test
+    public void testCreateSystemUserWithOtherPath() throws Exception {
+        String path = null;
+        try {
+            Tree t = root.getTree(UserConstants.DEFAULT_USER_PATH);
+            NodeUtil systemUserTree = new NodeUtil(t).addChild("systemUser", UserConstants.NT_REP_SYSTEM_USER);
+            systemUserTree.setString(UserConstants.REP_PRINCIPAL_NAME, "systemUser");
+            systemUserTree.setString(UserConstants.REP_AUTHORIZABLE_ID, "systemUser");
+            path = systemUserTree.getTree().getPath();
+
+            root.commit();
+            fail();
+        } catch (CommitFailedException e) {
+            // success
+            assertTrue(e.isConstraintViolation());
+        } finally {
+            root.refresh();
+            if (path != null) {
+                Tree t = root.getTree(path);
+                if (t.exists()) {
+                    t.remove();
+                    root.commit();
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testLoginAsSystemUser() throws Exception {
+        user = createUser(null);
+        try {
+            login(new SimpleCredentials(uid, new char[0])).close();
+            fail();
+        } catch (LoginException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testImpersonateSystemUser() throws Exception {
+        user = createUser(null);
+        ContentSession cs = login(new ImpersonationCredentials(new SimpleCredentials(uid, new char[0]), adminSession.getAuthInfo()));
+        cs.close();
+    }
+
+
+    @Test
+    public void testImpersonateDisabledSystemUser() throws Exception {
+        user = createUser(null);
+        user.disable("disabled");
+        root.commit();
+        try {
+            ContentSession cs = login(new ImpersonationCredentials(new SimpleCredentials(uid, new char[0]), adminSession.getAuthInfo()));
+            cs.close();
+            fail();
+        } catch (LoginException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testGetPrincipal() throws Exception {
+        user = createUser(null);
+        assertTrue(user.getPrincipal() instanceof SystemUserPrincipal);
+    }
+
+    @Test
+    public void testAddToGroup() throws Exception {
+        user = createUser(null);
+
+
+        Group g = null;
+        try {
+            g = userMgr.createGroup("testGroup");
+            g.addMember(user);
+            root.commit();
+
+            assertTrue(g.isMember(user));
+            assertTrue(g.isDeclaredMember(user));
+
+            boolean isMemberOfG = false;
+            Iterator<Group> groups = user.declaredMemberOf();
+            while (groups.hasNext() && !isMemberOfG) {
+                if (g.getID().equals(groups.next().getID())) {
+                    isMemberOfG = true;
+                }
+            }
+            assertTrue(isMemberOfG);
+        } finally {
+            if (g != null) {
+                g.remove();
+                root.commit();
+            }
+        }
+    }
+}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/SystemUserImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/SystemUserImpl.java	(revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/SystemUserImpl.java	(revision )
@@ -0,0 +1,77 @@
+/*
+ * 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.security.user;
+
+import java.security.Principal;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal;
+import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
+
+/**
+ * Default implementation for a system user.
+ */
+public class SystemUserImpl extends UserImpl {
+
+    SystemUserImpl(@Nonnull String id, @Nonnull Tree tree, @Nonnull UserManagerImpl userManager) throws RepositoryException {
+        super(id, tree, userManager);
+    }
+
+    @Override
+    void checkValidTree(Tree tree) throws RepositoryException {
+        super.checkValidTree(tree);
+        if (!UserUtil.isSystemUser(tree)) {
+            throw new IllegalArgumentException("Invalid user node: node type rep:SystemUser expected.");
+        }
+    }
+
+    @Override
+    public Principal getPrincipal() throws RepositoryException {
+        if (isAdmin()) {
+            return new AdminPrincipalImpl(getPrincipalName(), getTree(), getUserManager().getNamePathMapper());
+        } else {
+            return new SystemUserPrincipalImpl(getTree());
+        }
+    }
+
+    //---------------------------------------------------------------< User >---
+    @Override
+    public boolean isSystemUser() {
+        return true;
+    }
+
+    @Override
+    public void changePassword(String password) throws RepositoryException {
+        throw new UnsupportedRepositoryOperationException("system user");
+    }
+
+    @Override
+    public void changePassword(String password, String oldPassword) throws RepositoryException {
+        throw new UnsupportedRepositoryOperationException("system user");
+    }
+
+    //--------------------------------------------------------------------------
+    private final class SystemUserPrincipalImpl extends TreeBasedPrincipal implements SystemUserPrincipal {
+
+        private SystemUserPrincipalImpl(Tree tree) throws RepositoryException {
+            super(getPrincipalName(), tree, getUserManager().getNamePathMapper());
+        }
+    }
+}
\ No newline at end of file
Index: oak-parent/pom.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-parent/pom.xml	(revision 1617701)
+++ oak-parent/pom.xml	(revision )
@@ -42,7 +42,7 @@
     <project.reporting.outputEncoding>
       ${project.build.sourceEncoding}
     </project.reporting.outputEncoding>
-    <jackrabbit.version>2.8.0</jackrabbit.version>
+    <jackrabbit.version>2.9-SNAPSHOT</jackrabbit.version>
     <mongo.host>127.0.0.1</mongo.host>
     <mongo.port>27017</mongo.port>
     <mongo.db>MongoMKDB</mongo.db>
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/autosave/UserImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/autosave/UserImpl.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/autosave/UserImpl.java	(revision )
@@ -41,6 +41,11 @@
     }
 
     @Override
+    public boolean isSystemUser() {
+        return getDelegate().isSystemUser();
+    }
+
+    @Override
     public Credentials getCredentials() throws RepositoryException {
         return getDelegate().getCredentials();
     }
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/CredentialsImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/CredentialsImpl.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/CredentialsImpl.java	(revision )
@@ -16,6 +16,9 @@
  */
 package org.apache.jackrabbit.oak.security.user;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.jcr.Credentials;
 
 /**
@@ -27,15 +30,17 @@
     private final String userId;
     private final String pwHash;
 
-    CredentialsImpl(String userId, String pwHash) {
+    CredentialsImpl(@Nonnull String userId, @Nullable String pwHash) {
         this.userId = userId;
         this.pwHash = pwHash;
     }
 
+    @Nonnull
     public String getUserId() {
         return userId;
     }
 
+    @CheckForNull
     public String getPasswordHash() {
         return pwHash;
     }
\ No newline at end of file
Index: oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd	(revision 1617701)
+++ oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd	(revision )
@@ -716,6 +716,8 @@
   - rep:password (STRING) protected
   - rep:disabled (STRING) protected
 
+[rep:SystemUser] > rep:User
+
 [rep:Group] > rep:Authorizable, rep:MemberReferences
   + rep:members (rep:Members) = rep:Members multiple protected VERSION /* @deprecated since oak 1.0 (remove?) */
   + rep:membersList (rep:MemberReferencesList) = rep:MemberReferencesList protected COPY /* @since oak 1.0 */
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java	(revision )
@@ -26,6 +26,7 @@
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 
+import com.google.common.base.Strings;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
@@ -139,9 +140,9 @@
     }
 
     @Override
-    public User createUser(final String userId, String password) throws RepositoryException {
-        Principal principal = new PrincipalImpl(userId);
-        return createUser(userId, password, principal, null);
+    public User createUser(final String userID, String password) throws RepositoryException {
+        Principal principal = new PrincipalImpl(userID);
+        return createUser(userID, password, principal, null);
     }
 
     @Override
@@ -167,6 +168,21 @@
     }
 
     @Override
+    public User createSystemUser(String userID, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        checkValidID(userID);
+        Principal principal = new PrincipalImpl(userID);
+        checkValidPrincipal(principal, false);
+
+        Tree userTree = userProvider.createSystemUser(userID, intermediatePath);
+        setPrincipal(userTree, principal);
+
+        User user = new SystemUserImpl(userID, userTree, this);
+
+        log.debug("System user created: " + userID);
+        return user;
+    }
+
+    @Override
     public Group createGroup(String groupId) throws RepositoryException {
         Principal principal = new PrincipalImpl(groupId);
         return createGroup(groupId, principal, null);
@@ -327,7 +343,11 @@
             return null;
         }
         if (UserUtil.isType(tree, AuthorizableType.USER)) {
+            if (UserUtil.isSystemUser(tree)) {
+                return new SystemUserImpl(UserUtil.getAuthorizableId(tree), tree, this);
+            } else {
-            return new UserImpl(UserUtil.getAuthorizableId(tree), tree, this);
+                return new UserImpl(UserUtil.getAuthorizableId(tree), tree, this);
+            }
         } else if (UserUtil.isType(tree, AuthorizableType.GROUP)) {
             return new GroupImpl(UserUtil.getAuthorizableId(tree), tree, this);
         } else {
@@ -344,7 +364,7 @@
     }
 
     void checkValidPrincipal(Principal principal, boolean isGroup) throws RepositoryException {
-        if (principal == null || principal.getName() == null || "".equals(principal.getName())) {
+        if (principal == null || Strings.isNullOrEmpty(principal.getName())) {
             throw new IllegalArgumentException("Principal may not be null and must have a valid name.");
         }
         if (!isGroup && EveryonePrincipal.NAME.equals(principal.getName())) {
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java	(revision )
@@ -31,6 +31,7 @@
     String NT_REP_AUTHORIZABLE_FOLDER = "rep:AuthorizableFolder";
     String NT_REP_USER = "rep:User";
     String NT_REP_GROUP = "rep:Group";
+    String NT_REP_SYSTEM_USER = "rep:SystemUser";
     String NT_REP_PASSWORD = "rep:Password";
     @Deprecated
     String NT_REP_MEMBERS = "rep:Members";
@@ -126,6 +127,11 @@
      * Default value for {@link #PARAM_GROUP_PATH}
      */
     String DEFAULT_GROUP_PATH = "/rep:security/rep:authorizables/rep:groups";
+
+    /**
+     * Default intermediate path for system users.
+     */
+    String DEFAULT_SYSTEM_PATH = "system";
 
     /**
      * Parameter used to change the number of levels that are used by default to
Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/AbstractImportTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/AbstractImportTest.java	(revision 1617701)
+++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/AbstractImportTest.java	(revision )
@@ -62,8 +62,8 @@
 public abstract class AbstractImportTest {
 
     private static final String ADMINISTRATORS = "administrators";
-    protected static final String USERPATH = "/rep:security/rep:authorizables/rep:users";
-    protected static final String GROUPPATH = "/rep:security/rep:authorizables/rep:groups";
+    protected static final String USERPATH = UserConstants.DEFAULT_USER_PATH;
+    protected static final String GROUPPATH = UserConstants.DEFAULT_GROUP_PATH;
 
     private Repository repo;
 
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java	(revision )
@@ -39,7 +39,6 @@
     private UserUtil() {
     }
 
-    @Nonnull
     public static boolean isAdmin(@Nonnull ConfigurationParameters parameters, @Nonnull String userId) {
         return getAdminId(parameters).equals(userId);
     }
@@ -61,9 +60,9 @@
                 case GROUP:
                     return NT_REP_GROUP.equals(ntName);
                 case USER:
-                    return NT_REP_USER.equals(ntName);
+                    return NT_REP_USER.equals(ntName) || NT_REP_SYSTEM_USER.equals(ntName);
                 default:
-                    return NT_REP_USER.equals(ntName) || NT_REP_GROUP.equals(ntName);
+                    return NT_REP_USER.equals(ntName) || NT_REP_GROUP.equals(ntName) || NT_REP_SYSTEM_USER.equals(ntName);
             }
         }
         return false;
@@ -73,13 +72,19 @@
     public static AuthorizableType getType(@Nonnull Tree authorizableNode) {
         String ntName = TreeUtil.getPrimaryTypeName(authorizableNode);
         if (ntName != null) {
-            if (UserConstants.NT_REP_GROUP.equals(ntName)) {
+            if (NT_REP_GROUP.equals(ntName)) {
                 return AuthorizableType.GROUP;
-            } else if (UserConstants.NT_REP_USER.equals(ntName)) {
+            } else if (NT_REP_USER.equals(ntName)) {
                 return AuthorizableType.USER;
+            } else if (NT_REP_SYSTEM_USER.equals(ntName)) {
+                return AuthorizableType.USER;
             }
         }
         return null;
+    }
+
+    public static boolean isSystemUser(@Nullable Tree authorizableTree) {
+        return authorizableTree != null && NT_REP_SYSTEM_USER.equals(TreeUtil.getPrimaryTypeName(authorizableTree));
     }
 
     @CheckForNull
\ No newline at end of file
Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/UserDelegator.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/UserDelegator.java	(revision 1617701)
+++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/UserDelegator.java	(revision )
@@ -72,6 +72,16 @@
     }
 
     @Override
+    public boolean isSystemUser() {
+        return sessionDelegate.safePerform(new SessionOperation<Boolean>("isSystemUser") {
+            @Override
+            public Boolean perform() {
+                return getDelegate().isSystemUser();
+            }
+        });
+    }
+
+    @Override
     public Credentials getCredentials() {
         return sessionDelegate.safePerform(new SessionOperation<Credentials>("getCredentials") {
             @Override
Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/SystemUserTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/SystemUserTest.java	(revision )
+++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/SystemUserTest.java	(revision )
@@ -0,0 +1,208 @@
+/*
+ * 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 java.util.Iterator;
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests for system {@code User} creation.
+ *
+ * @since Oak 1.1
+ */
+public class SystemUserTest extends AbstractUserTest {
+
+    private static Logger log = LoggerFactory.getLogger(SystemUserTest.class);
+
+    private String uid;
+    private User user;
+
+    @Before
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        uid = getTestPrincipal().getName();
+    }
+
+    @After
+    @Override
+    protected void tearDown() throws Exception {
+        superuser.refresh(false);
+        // remove all created groups again
+        if (user != null) {
+            try {
+                user.remove();
+                superuser.save();
+            } catch (RepositoryException e) {
+                log.warn("Failed to remove User " + user.getID() + " during tearDown.");
+            }
+        }
+        super.tearDown();
+    }
+
+    private User createUser(String uid) throws RepositoryException {
+        return createUser(uid, null);
+    }
+
+    private User createUser(String uid, String intermediatePath) throws RepositoryException {
+        User u = userMgr.createSystemUser(uid, intermediatePath);
+        superuser.save();
+        return u;
+    }
+
+    @Test
+    public void testCreateUser() throws RepositoryException, NotExecutableException {
+        user = createUser(uid);
+        assertNotNull(user.getID());
+
+        assertTrue(user.isSystemUser());
+        assertFalse(user.isAdmin());
+        assertFalse(user.isGroup());
+    }
+
+    @Test
+    public void testCreateUserWithNullUserID() throws RepositoryException {
+        try {
+            user = createUser(null);
+            fail("A User cannot be built with 'null' userID");
+        } catch (Exception e) {
+            // ok
+        }
+    }
+
+    @Test
+    public void testCreateUserWithEmptyUserID() throws RepositoryException {
+        try {
+            user = createUser("");
+            fail("A User cannot be built with \"\" userID");
+        } catch (Exception e) {
+            // ok
+        }
+    }
+
+    @Test
+    public void testCreateTwiceWithSameUserID() throws RepositoryException, NotExecutableException {
+        user = createUser(uid);
+        try {
+            User user2 = createUser(uid);
+            fail("Creating 2 users with the same UserID should throw AuthorizableExistsException.");
+        } catch (AuthorizableExistsException e) {
+            // success.
+        }
+    }
+
+    @Test
+    public void testGetUserByID() throws RepositoryException, NotExecutableException {
+        user = createUser(uid);
+
+        Authorizable authorizable = userMgr.getAuthorizable(user.getID());
+        assertNotNull(authorizable);
+        assertFalse(authorizable.isGroup());
+        assertFalse(((User) authorizable).isAdmin());
+        assertTrue(((User) authorizable).isSystemUser());
+    }
+
+    @Test
+    public void testGetUserByPrincipal() throws Exception {
+        user = createUser(uid);
+
+        Authorizable authorizable = userMgr.getAuthorizable(user.getPrincipal());
+        assertNotNull(authorizable);
+        assertFalse(authorizable.isGroup());
+        assertFalse(((User) authorizable).isAdmin());
+        assertTrue(((User) authorizable).isSystemUser());
+    }
+
+    public void testGetUserByPath() throws Exception {
+        user = createUser(uid);
+
+        Authorizable authorizable = userMgr.getAuthorizableByPath(user.getPath());
+        assertNotNull(authorizable);
+        assertFalse(authorizable.isGroup());
+        assertFalse(((User) authorizable).isAdmin());
+        assertTrue(((User) authorizable).isSystemUser());
+        assertEquals(user.getPath(), authorizable.getPath());
+    }
+
+    @Test
+    public void testFindAuthorizable() throws Exception {
+        user = createUser(uid);
+
+        Iterator<Authorizable> iterator = userMgr.findAuthorizables(UserConstants.REP_PRINCIPAL_NAME, user.getPrincipal().getName());
+        assertTrue(iterator.hasNext());
+
+        Authorizable authorizable = iterator.next();
+        assertNotNull(authorizable);
+        assertFalse(authorizable.isGroup());
+        assertTrue(((User) authorizable).isSystemUser());
+
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void testChangePassword() throws Exception {
+        user = createUser(uid);
+        try {
+            user.changePassword("pw");
+            superuser.save();
+            fail();
+        } catch (UnsupportedRepositoryOperationException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testChangePassword2() throws Exception {
+        user = createUser(uid);
+        try {
+            user.changePassword("old", "pw");
+            superuser.save();
+            fail();
+        } catch (UnsupportedRepositoryOperationException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testDisable() throws Exception {
+        user = createUser(uid);
+        user.disable("gone");
+        superuser.save();
+
+        assertTrue(user.isDisabled());
+        assertEquals("gone", user.getDisabledReason());
+
+        user.disable(null);
+        assertFalse(user.isDisabled());
+    }
+}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java	(revision )
@@ -23,6 +23,7 @@
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.jcr.AccessDeniedException;
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.ConstraintViolationException;
@@ -168,7 +169,7 @@
     private final String groupPath;
     private final String userPath;
 
-    UserProvider(Root root, ConfigurationParameters config) {
+    UserProvider(@Nonnull Root root, @Nonnull ConfigurationParameters config) {
         super(root, config);
 
         defaultDepth = config.getConfigValue(PARAM_DEFAULT_DEPTH, DEFAULT_DEPTH);
@@ -177,27 +178,42 @@
     }
 
     @Nonnull
-    Tree createUser(String userID, String intermediateJcrPath) throws RepositoryException {
-        return createAuthorizableNode(userID, false, intermediateJcrPath);
+    Tree createUser(@Nonnull String userID, @Nullable String intermediateJcrPath) throws RepositoryException {
+        return createAuthorizableNode(userID, NT_REP_USER, intermediateJcrPath);
     }
 
     @Nonnull
-    Tree createGroup(String groupID, String intermediateJcrPath) throws RepositoryException {
-        return createAuthorizableNode(groupID, true, intermediateJcrPath);
+    Tree createGroup(@Nonnull String groupID, @Nullable String intermediateJcrPath) throws RepositoryException {
+        return createAuthorizableNode(groupID, NT_REP_GROUP, intermediateJcrPath);
     }
 
+    @Nonnull
+    Tree createSystemUser(@Nonnull String userID, @Nullable String intermediateJcrPath) throws RepositoryException {
+        String relPath;
+        if (intermediateJcrPath == null) {
+            relPath = DEFAULT_SYSTEM_PATH;
+        } else {
+            if (!(intermediateJcrPath.startsWith(DEFAULT_SYSTEM_PATH) ||
+                intermediateJcrPath.startsWith(userPath + '/' + DEFAULT_SYSTEM_PATH))) {
+                throw new ConstraintViolationException("System users must be located in the 'system' subtree of the user root.");
+            }
+            relPath = intermediateJcrPath;
+        }
+        return createAuthorizableNode(userID, NT_REP_SYSTEM_USER, relPath);
+    }
+
     @CheckForNull
-    Tree getAuthorizable(String authorizableId) {
+    Tree getAuthorizable(@Nonnull String authorizableId) {
         return getByID(authorizableId, AuthorizableType.AUTHORIZABLE);
     }
 
     @CheckForNull
-    Tree getAuthorizableByPath(String authorizableOakPath) {
+    Tree getAuthorizableByPath(@Nonnull String authorizableOakPath) {
         return getByPath(authorizableOakPath);
     }
 
     @CheckForNull
-    Tree getAuthorizableByPrincipal(Principal principal) {
+    Tree getAuthorizableByPrincipal(@Nonnull Principal principal) {
         if (principal instanceof TreeBasedPrincipal) {
             return root.getTree(((TreeBasedPrincipal) principal).getOakPath());
         }
@@ -229,11 +245,12 @@
 
     //------------------------------------------------------------< private >---
 
-    private Tree createAuthorizableNode(String authorizableId, boolean isGroup, String intermediatePath) throws RepositoryException {
+    private Tree createAuthorizableNode(@Nonnull String authorizableId,
+                                        @Nonnull String ntName,
+                                        @Nullable String intermediatePath) throws RepositoryException {
         String nodeName = getNodeName(authorizableId);
-        NodeUtil folder = createFolderNodes(authorizableId, nodeName, isGroup, intermediatePath);
+        NodeUtil folder = createFolderNodes(authorizableId, nodeName, NT_REP_GROUP.equals(ntName), intermediatePath);
 
-        String ntName = (isGroup) ? NT_REP_GROUP : NT_REP_USER;
         NodeUtil authorizableNode = folder.addChild(nodeName, ntName);
 
         String nodeID = getContentID(authorizableId);
@@ -256,8 +273,10 @@
      * @return The folder node.
      * @throws RepositoryException If an error occurs
      */
-    private NodeUtil createFolderNodes(String authorizableId, String nodeName,
-                                       boolean isGroup, String intermediatePath) throws RepositoryException {
+    private NodeUtil createFolderNodes(@Nonnull String authorizableId,
+                                       @Nonnull String nodeName,
+                                       boolean isGroup,
+                                       @Nullable String intermediatePath) throws RepositoryException {
         String authRoot = (isGroup) ? groupPath : userPath;
         String folderPath = new StringBuilder()
                 .append(authRoot)
@@ -295,7 +314,9 @@
         return folder;
     }
 
-    private String getFolderPath(String authorizableId, String intermediatePath, String authRoot) throws ConstraintViolationException {
+    private String getFolderPath(@Nonnull String authorizableId,
+                                 @Nullable String intermediatePath,
+                                 @Nonnull String authRoot) throws ConstraintViolationException {
         if (intermediatePath != null && intermediatePath.charAt(0) == '/') {
             if (!intermediatePath.startsWith(authRoot)) {
                 throw new ConstraintViolationException("Attempt to create authorizable outside of configured tree");
@@ -323,7 +344,7 @@
         return sb.toString();
     }
 
-    private String getNodeName(String authorizableId) {
+    private String getNodeName(@Nonnull String authorizableId) {
         AuthorizableNodeName generator = checkNotNull(config.getConfigValue(PARAM_AUTHORIZABLE_NODE_NAME, AuthorizableNodeName.DEFAULT, AuthorizableNodeName.class));
         return generator.generateNodeName(authorizableId);
     }
\ No newline at end of file
Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/CreateUserTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/CreateUserTest.java	(revision 1617701)
+++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/CreateUserTest.java	(revision )
@@ -45,7 +45,7 @@
     @Override
     protected void tearDown() throws Exception {
         superuser.refresh(false);
-        // remove all created groups again
+        // remove all created users again
         for (Object createdUser : createdUsers) {
             Authorizable auth = (Authorizable) createdUser;
             try {
@@ -100,7 +100,7 @@
     }
 
     @Test
-    public void testCreateGroupWithAbsolutePath2() throws RepositoryException, NotExecutableException {
+    public void testCreateUserWithAbsolutePath2() throws RepositoryException, NotExecutableException {
         Principal p = getTestPrincipal();
         String uid = p.getName();
 
@@ -272,8 +272,20 @@
 
         User user = createUser(uid, "pw");
         createdUsers.add(user);
+        assertFalse(user.isSystemUser());
+        assertFalse(user.isGroup());
+        assertFalse(user.isAdmin());
 
-        assertNotNull(userMgr.getAuthorizable(user.getID()));
-        assertNotNull(userMgr.getAuthorizable(p));
+        Authorizable authorizable = userMgr.getAuthorizable(user.getID());
+        assertNotNull(authorizable);
+        assertFalse(authorizable.isGroup());
+        assertFalse(((User) authorizable).isAdmin());
+        assertFalse(((User) authorizable).isSystemUser());
+
+        authorizable = userMgr.getAuthorizable(p);
+        assertNotNull(authorizable);
+        assertFalse(authorizable.isGroup());
+        assertFalse(((User) authorizable).isAdmin());
+        assertFalse(((User) authorizable).isSystemUser());
     }
 }
\ No newline at end of file
Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/SystemUserImportTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/SystemUserImportTest.java	(revision )
+++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/SystemUserImportTest.java	(revision )
@@ -0,0 +1,606 @@
+/*
+ * 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 java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.security.auth.Subject;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
+import org.apache.jackrabbit.api.security.user.Impersonation;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Testing system user import with default {@link org.apache.jackrabbit.oak.spi.xml.ImportBehavior}
+ */
+public class SystemUserImportTest extends AbstractImportTest {
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        adminSession.getNode(USERPATH).addNode(UserConstants.DEFAULT_SYSTEM_PATH, UserConstants.NT_REP_AUTHORIZABLE_FOLDER);
+        adminSession.save();
+    }
+
+    @Override
+    public void after() throws Exception {
+        try {
+            getTargetNode().remove();
+            adminSession.save();
+        } finally {
+            super.after();
+        }
+    }
+
+    @Override
+    protected String getTargetPath() {
+        return USERPATH + '/' + UserConstants.DEFAULT_SYSTEM_PATH;
+    }
+
+    @Override
+    protected String getImportBehavior() {
+        return null;
+    }
+
+    @Test
+    public void testImportUser() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:disabled\" sv:type=\"String\"><sv:value>disabledUser</sv:value></sv:property>" +
+                "</sv:node>";
+
+        Node target = getTargetNode();
+        doImport(getTargetPath(), xml);
+
+        assertTrue(target.isModified());
+        assertTrue(adminSession.hasPendingChanges());
+
+        Authorizable newUser = userMgr.getAuthorizable("t");
+        assertNotNull(newUser);
+        assertFalse(newUser.isGroup());
+        assertTrue(((User) newUser).isSystemUser());
+
+        assertEquals("t", newUser.getPrincipal().getName());
+        assertEquals("t", newUser.getID());
+        assertTrue(((User) newUser).isDisabled());
+        assertEquals("disabledUser", ((User) newUser).getDisabledReason());
+
+        Node n = adminSession.getNode(newUser.getPath());
+        assertTrue(n.isNew());
+        assertTrue(n.getParent().isSame(target));
+
+        assertEquals("t", n.getName());
+        assertEquals("t", n.getProperty(UserConstants.REP_PRINCIPAL_NAME).getString());
+        assertEquals("disabledUser", n.getProperty(UserConstants.REP_DISABLED).getString());
+
+        assertFalse(n.hasProperty(UserConstants.REP_PASSWORD));
+
+        // saving changes of the import -> must succeed. all mandatory props should have been created.
+        adminSession.save();
+    }
+
+    /**
+     * @since OAK 1.0 : constraintviolation is no longer detected during import
+     *        but only upon save.
+     */
+    @Test
+    public void testImportIntoNonSystemPath() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+
+        /*
+         importing a system user below the regular users-path:
+         - saving changes must fail with ConstraintViolationEx.
+         */
+        Node target = adminSession.getNode(USERPATH);
+        doImport(USERPATH, xml);
+
+        assertTrue(target.isModified());
+        assertTrue(adminSession.hasPendingChanges());
+
+        Authorizable user = userMgr.getAuthorizable("t");
+        assertNotNull(user);
+        assertTrue(target.hasNode("t"));
+        assertTrue(target.hasProperty("t/rep:principalName"));
+
+        // saving changes of the import -> must fail
+        try {
+            adminSession.save();
+            fail("Import must be incomplete. Saving changes must fail.");
+        } catch (ConstraintViolationException e) {
+            // success
+        } finally {
+            adminSession.refresh(false);
+            if (target.hasNode("t")) {
+                target.getNode("t").remove();
+                target.save();
+            }
+        }
+    }
+
+    @Test
+    public void testImportUuidMismatch() throws Exception {
+        // importing an authorizable with an jcr:uuid that doesn't match the
+        // hash of the given ID -> getAuthorizable(String id) will not find the
+        // authorizable.
+        //String calculatedUUID = "e358efa4-89f5-3062-b10d-d7316b65649e";
+        String mismatchUUID = "a358efa4-89f5-3062-b10d-d7316b65649e";
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>" + mismatchUUID + "</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property></sv:node>";
+
+        Node target = getTargetNode();
+        doImport(getTargetPath(), xml);
+
+        assertTrue(target.isModified());
+        assertTrue(adminSession.hasPendingChanges());
+
+        // node must be present:
+        assertTrue(target.hasNode("t"));
+        Node n = target.getNode("t");
+        assertEquals(mismatchUUID, n.getUUID());
+
+        // but UserManager.getAuthorizable(String) will not find the
+        // authorizable
+        Authorizable newUser = userMgr.getAuthorizable("t");
+        assertNull(newUser);
+    }
+
+    @Test
+    public void testExistingPrincipal() throws Exception {
+        Principal existing = null;
+        PrincipalIterator principalIterator = ((JackrabbitSession) adminSession).getPrincipalManager().getPrincipals(PrincipalManager.SEARCH_TYPE_ALL);
+        while (principalIterator.hasNext()) {
+            Principal p = principalIterator.nextPrincipal();
+            if (userMgr.getAuthorizable(p) != null) {
+                existing = p;
+                break;
+            }
+        }
+        if (existing == null) {
+            throw new NotExecutableException();
+        }
+
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>" + existing.getName() + "</sv:value></sv:property>" +
+                "</sv:node>";
+
+        try {
+            doImport(getTargetPath(), xml);
+            adminSession.save();
+
+            fail("Import must detect conflicting principals.");
+        } catch (RepositoryException e) {
+            // success
+        }
+    }
+
+    @Test
+    public void testPassword() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        Node target = getTargetNode();
+        assertFalse(target.hasProperty(UserConstants.REP_PASSWORD));
+
+        Authorizable user = userMgr.getAuthorizable("t");
+        assertNotNull(user);
+        assertFalse(user.isGroup());
+        assertTrue(((User)user).isSystemUser());
+
+        adminSession.save();
+    }
+
+    /**
+     * @since OAK 1.0 : password property is not longer mandatory -> multivalued
+     *        property will just be ignored (instead of throwing ConstraintViolationException
+     *        upon save).
+     */
+    @Test
+    public void testMultiValuedPassword() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+        /*
+         importing a user with a multi-valued rep:password property
+         - nonProtected node rep:SystemUser must be created.
+         - property rep:password must be created regularly without being protected
+         */
+        Node target = getTargetNode();
+        doImport(getTargetPath(), xml);
+
+        assertTrue(target.isModified());
+        assertTrue(adminSession.hasPendingChanges());
+
+        Authorizable newUser = userMgr.getAuthorizable("t");
+        assertNotNull(newUser);
+
+        assertTrue(target.hasNode("t"));
+        assertTrue(target.hasProperty("t/rep:password"));
+        assertFalse(target.getProperty("t/rep:password").getDefinition().isProtected());
+    }
+
+    @Test
+    public void testIncompleteUser() throws Exception {
+        List<String> incompleteXml = new ArrayList<String>();
+        incompleteXml.add("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "</sv:node>");
+        incompleteXml.add("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>");
+
+        for (String xml : incompleteXml) {
+            Node target = adminSession.getNode(getTargetPath());
+            try {
+                doImport(getTargetPath(), xml);
+                // saving changes of the import -> must fail as mandatory prop is missing
+                try {
+                    adminSession.save();
+                    fail("Import must be incomplete. Saving changes must fail.");
+                } catch (ConstraintViolationException e) {
+                    // success
+                }
+            } finally {
+                adminSession.refresh(false);
+                if (target.hasNode("t")) {
+                    target.getNode("t").remove();
+                    adminSession.save();
+                }
+            }
+        }
+    }
+
+    /**
+     * @since OAK 1.0 : importing User without password must succeed.
+     */
+    @Test
+    public void testUserWithoutPassword() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        Authorizable user = userMgr.getAuthorizable("t");
+        assertNotNull(user);
+        assertFalse(user.isGroup());
+        assertFalse(adminSession.propertyExists(user.getPath() + "/rep:password"));
+    }
+
+    @Test
+    public void testImportWithIntermediatePath() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"some\" 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:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>d5433be9-68d0-4fba-bf96-efc29f461993</sv:value></sv:property>" +
+                "<sv:node sv:name=\"intermediate\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:AuthorizableFolder</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>d87354a4-037e-4756-a8fb-deb2eb7c5149</sv:value></sv:property>" +
+                "<sv:node sv:name=\"path\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:AuthorizableFolder</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>24263272-b789-4568-957a-3bcaf99dbab3</sv:value></sv:property>" +
+                "<sv:node sv:name=\"t3\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>0b8854ad-38f0-36c6-9807-928d28195609</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}4358694eeb098c6708ae914a10562ce722bbbc34</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t3</sv:value></sv:property>" +
+                "</sv:node>" +
+                "</sv:node>" +
+                "</sv:node>" +
+                "</sv:node>";
+
+        Node target = getTargetNode();
+        doImport(getTargetPath(), xml);
+
+        assertTrue(target.isModified());
+        assertTrue(adminSession.hasPendingChanges());
+
+        Authorizable newUser = userMgr.getAuthorizable("t3");
+        assertNotNull(newUser);
+        assertFalse(newUser.isGroup());
+        assertEquals("t3", newUser.getPrincipal().getName());
+        assertEquals("t3", newUser.getID());
+
+        Node n = adminSession.getNode(newUser.getPath());
+        assertTrue(n.isNew());
+
+        Node parent = n.getParent();
+        assertFalse(n.isSame(target));
+        assertTrue(parent.isNodeType(UserConstants.NT_REP_AUTHORIZABLE_FOLDER));
+        assertFalse(parent.getDefinition().isProtected());
+
+        assertTrue(target.hasNode("some"));
+        assertTrue(target.hasNode("some/intermediate/path"));
+    }
+
+    @Test
+    public void testImportImpersonation() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                "<sv:node sv:name=\"uFolder\" 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=\"t\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:impersonators\" sv:type=\"String\"><sv:value>g</sv:value></sv:property>" +
+                "</sv:node>" +
+                "<sv:node sv:name=\"g\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>b2f5ff47-4366-31b6-a533-d8dc3614845d</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g</sv:value></sv:property>" +
+                "</sv:node>" +
+                "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        Authorizable newUser = userMgr.getAuthorizable("t");
+        assertNotNull(newUser);
+
+        Authorizable u2 = userMgr.getAuthorizable("g");
+        assertNotNull(u2);
+
+        Subject subj = new Subject();
+        subj.getPrincipals().add(u2.getPrincipal());
+
+        Impersonation imp = ((User) newUser).getImpersonation();
+        assertTrue(imp.allows(subj));
+    }
+
+    @Test
+    public void testImportUuidCollisionRemoveExisting() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"r\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>4b43b0ae-e356-34cd-95b9-10189b3dc231</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        // re-import should succeed if UUID-behavior is set accordingly
+        doImport(getTargetPath(), xml, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
+
+        // saving changes of the import -> must succeed. add mandatory
+        // props should have been created.
+        adminSession.save();
+    }
+
+    /**
+     * Same as {@link #testImportUuidCollisionRemoveExisting} with the single
+     * difference that the initial import is saved before being overwritten.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testImportUuidCollisionRemoveExisting2() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"r\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>4b43b0ae-e356-34cd-95b9-10189b3dc231</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+        doImport(getTargetPath(), xml);
+        adminSession.save();
+
+        // re-import should succeed if UUID-behavior is set accordingly
+        doImport(getTargetPath(), xml, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
+
+        // saving changes of the import -> must succeed. add mandatory
+        // props should have been created.
+        adminSession.save();
+    }
+
+    @Test
+    public void testImportUuidCollisionThrow() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+
+        try {
+            doImport(getTargetPath(), xml);
+            doImport(getTargetPath(), xml, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+            fail("UUID collision must be handled according to the uuid behavior.");
+
+        } catch (ItemExistsException e) {
+            // success.
+        }
+    }
+
+    /**
+     * @since OAK 1.0 : Importing rep:authorizableId
+     */
+    @Test
+    public void testImportUserWithAuthorizableId() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        Authorizable newUser = userMgr.getAuthorizable("t");
+        assertNotNull(newUser);
+        assertFalse(newUser.isGroup());
+        assertEquals("t", newUser.getID());
+        assertTrue(adminSession.propertyExists(newUser.getPath() + "/rep:authorizableId"));
+        assertEquals("t", adminSession.getProperty(newUser.getPath() + "/rep:authorizableId").getString());
+        adminSession.save();
+    }
+
+    /**
+     * @since OAK 1.0 : Importing rep:authorizableId
+     */
+    @Test
+    public void testImportUserWithIdDifferentFromNodeName() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t_diff\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+
+        doImport(getTargetPath(), xml);
+
+        Authorizable newUser = userMgr.getAuthorizable("t");
+
+        assertNotNull(newUser);
+        assertFalse(newUser.isGroup());
+        assertEquals("t", newUser.getID());
+        assertTrue(adminSession.propertyExists(newUser.getPath() + "/rep:authorizableId"));
+        assertEquals("t", adminSession.getProperty(newUser.getPath() + "/rep:authorizableId").getString());
+        adminSession.save();
+    }
+
+    /**
+     * Same as {@link #testImportUserWithIdDifferentFromNodeName} but with
+     * different order of properties.
+     *
+     * @since OAK 1.0 : Importing rep:authorizableId
+     */
+    @Test
+    public void testImportUserWithIdDifferentFromNodeName2() throws Exception {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t_diff\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "</sv:node>";
+        doImport(getTargetPath(), xml);
+
+        Authorizable newUser = userMgr.getAuthorizable("t");
+
+        assertNotNull(newUser);
+        assertFalse(newUser.isGroup());
+        assertEquals("t", newUser.getID());
+        assertTrue(adminSession.propertyExists(newUser.getPath() + "/rep:authorizableId"));
+        assertEquals("t", adminSession.getProperty(newUser.getPath() + "/rep:authorizableId").getString());
+        adminSession.save();
+    }
+
+    /**
+     * @since OAK 1.0 : Importing rep:authorizableId
+     */
+    @Test
+    public void testImportUserWithExistingId() throws Exception {
+        String existingId = "admin";
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t_diff\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>" + existingId + "</sv:value></sv:property>" +
+                "</sv:node>";
+        try {
+            doImport(getTargetPath(), xml);
+            fail("Reuse of existing ID must be detected.");
+        } catch (AuthorizableExistsException e) {
+            // success
+        }
+    }
+
+    /**
+     * @since OAK 1.0 : Importing rep:authorizableId
+     */
+    @Test
+    public void testImportUserWithIdCollision() throws Exception {
+        String collidingId = "t";
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"uFolder\" 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=\"t1\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>" + collidingId + "</sv:value></sv:property>" +
+                "</sv:node>" +
+                "<sv:node sv:name=\"t2\" 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:SystemUser</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>0f826a89-cf68-3399-85f4-cf320c1a5842</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>" + collidingId + "</sv:value></sv:property>" +
+                "</sv:node>" +
+                "</sv:node>";
+        try {
+            doImport(getTargetPath(), xml);
+            fail("Reuse of existing ID must be detected.");
+        } catch (AuthorizableExistsException e) {
+            // success
+        }
+    }
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java	(revision )
@@ -135,13 +135,26 @@
         Tree tree = checkNotNull(parentAfter.getChild(name));
 
         AuthorizableType type = UserUtil.getType(tree);
+        boolean isSystemUser = (type == AuthorizableType.USER) && UserUtil.isSystemUser(tree);
         String authRoot = UserUtil.getAuthorizableRootPath(provider.getConfig(), type);
+        if (isSystemUser) {
+            authRoot = authRoot + '/' + DEFAULT_SYSTEM_PATH;
+        }
         if (authRoot != null) {
             assertHierarchy(tree, authRoot);
             // assert rep:principalName is present (that should actually by covered
             // by node type validator)
             if (TreeUtil.getString(tree, REP_PRINCIPAL_NAME) == null) {
                 throw constraintViolation(26, "Mandatory property rep:principalName missing.");
+            }
+
+            if (isSystemUser) {
+                if (TreeUtil.getString(tree, REP_PASSWORD) != null) {
+                    throw constraintViolation(27, "Attempt to set password with system user.");
+                }
+                if (tree.hasChild(REP_PWD)) {
+                    throw constraintViolation(28, "Attempt to add rep:pwd node to a system user.");
+                }
             }
         }
         return new VisibleValidator(new UserValidator(null, tree, provider), true, true);
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java	(revision 1617701)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java	(revision )
@@ -80,6 +80,11 @@
     }
 
     @Override
+    public boolean isSystemUser() {
+        return false;
+    }
+
+    @Override
     public Credentials getCredentials() {
         return new CredentialsImpl(getID(), getPasswordHash());
     }
\ No newline at end of file
