SLING-9814 Add canDisable and canChangePassword methods to
AuthorizablePrivilegesInfo
diff --git a/pom.xml b/pom.xml
index 828c719..d12d347 100644
--- a/pom.xml
+++ b/pom.xml
@@ -128,8 +128,8 @@
         </dependency>
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
-            <artifactId>jackrabbit-api</artifactId>
-            <version>2.3.0</version>
+            <artifactId>oak-jackrabbit-api</artifactId>
+            <version>1.18.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/AuthorizablePrivilegesInfo.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/AuthorizablePrivilegesInfo.java
index af86136..b3db3f9 100644
--- a/src/main/java/org/apache/sling/jackrabbit/usermanager/AuthorizablePrivilegesInfo.java
+++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/AuthorizablePrivilegesInfo.java
@@ -96,4 +96,30 @@
     boolean canUpdateGroupMembers(Session jcrSession,
             String groupId);
 
+    /**
+     * Checks whether the current user has been granted privileges
+     * to disable the specified user.
+     *
+     * @param jcrSession the JCR session of the current user
+     * @param userId the user id to check
+     * @return true if the current user has the privileges, false otherwise
+     */
+    default boolean canDisable(Session jcrSession,
+            String userId) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Checks whether the current user has been granted privileges
+     * to change the password of the specified user.
+     *
+     * @param jcrSession the JCR session of the current user
+     * @param userId the user id to check
+     * @return true if the current user has the privileges, false otherwise
+     */
+    default boolean canChangePassword(Session jcrSession,
+            String userId) {
+        throw new UnsupportedOperationException();
+    }
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/AuthorizablePrivilegesInfoImpl.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/AuthorizablePrivilegesInfoImpl.java
index e7da94c..e91c31e 100644
--- a/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/AuthorizablePrivilegesInfoImpl.java
+++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/AuthorizablePrivilegesInfoImpl.java
@@ -36,6 +36,7 @@
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.sling.commons.osgi.OsgiUtil;
 import org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo;
+import org.apache.sling.jackrabbit.usermanager.ChangeUserPassword;
 import org.apache.sling.jackrabbit.usermanager.CreateUser;
 import org.apache.sling.jcr.base.util.AccessControlUtil;
 import org.osgi.framework.BundleContext;
@@ -98,6 +99,16 @@
     private String usersPath;
     private String groupsPath;
     private boolean selfRegistrationEnabled;
+    private boolean alwaysAllowSelfChangePassword = false;
+
+    @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
+    private void bindChangeUserPassword(ChangeUserPassword changeUserPassword, Map<String, Object> properties) {
+        alwaysAllowSelfChangePassword = OsgiUtil.toBoolean(properties.get("alwaysAllowSelfChangePassword"), false);
+    }
+    @SuppressWarnings("unused")
+    private void unbindChangeUserPassword(ChangeUserPassword changeUserPassword, Map<String, Object> properties) {
+        alwaysAllowSelfChangePassword = false;
+    }
     
     @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
     private void bindUserConfiguration(UserConfiguration userConfig, Map<String, Object> properties) {
@@ -266,19 +277,12 @@
             PropertyUpdateTypes... propertyUpdateTypes) {
         boolean hasRights = false;
         try {
-            if (jcrSession.getUserID().equals(principalId)) {
-                //user is allowed to update it's own properties
-                hasRights = true;
+            UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
+            Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
+
+            if (currentUser instanceof User && ((User)currentUser).isAdmin()) {
+                hasRights = true; //admin user has full control
             } else {
-                UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
-                Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
-
-                if (currentUser instanceof User) {
-                    if (((User)currentUser).isAdmin()) {
-                        return true; //admin user has full control
-                    }
-                }
-
                 Authorizable authorizable = userManager.getAuthorizable(principalId);
                 if (authorizable == null) {
                     log.debug("Failed to find authorizable: {}", principalId);
@@ -322,10 +326,85 @@
         return hasRights;
     }
 
+    /* (non-Javadoc)
+     * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canDisable(javax.jcr.Session, java.lang.String)
+     */
+    @Override
+    public boolean canDisable(Session jcrSession, String userId) {
+        boolean hasRights = false;
+        try {
+            UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
+            Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
+
+            if (currentUser instanceof User && ((User)currentUser).isAdmin()) {
+                hasRights = true; //admin user has full control
+            } else {
+                Authorizable authorizable = userManager.getAuthorizable(userId);
+                if (!(authorizable instanceof User)) {
+                    log.debug("Failed to find user: {}", userId);
+                } else {
+                    String path = authorizable.getPath();
+                    if (path != null) {
+                        //check if the non-admin user has sufficient rights on the home folder
+                        AccessControlManager acm = jcrSession.getAccessControlManager();
+                        Set<Privilege> requiredPrivileges = new HashSet<>();
+                        requiredPrivileges.add(acm.privilegeFromName(Privilege.JCR_READ));
+                        requiredPrivileges.add(acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT));
+                        hasRights = acm.hasPrivileges(path, requiredPrivileges.toArray(new Privilege[requiredPrivileges.size()]));
+                    }
+                }
+            }
+        } catch (RepositoryException e) {
+            log.warn("Failed to determine if {} can disable user {}", jcrSession.getUserID(), userId);
+        }
+        return hasRights;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canChangePassword(javax.jcr.Session, java.lang.String)
+     */
+    @Override
+    public boolean canChangePassword(Session jcrSession, String userId) {
+        boolean hasRights = false;
+        try {
+            UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
+            Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
+
+            Authorizable authorizable = userManager.getAuthorizable(userId);
+            if (!(authorizable instanceof User)) {
+                log.debug("Failed to find user: {}", userId);
+            } else {
+                if (((User)authorizable).isSystemUser() || "anonymous".equals(authorizable.getID())) {
+                    hasRights = false; //system users and anonymous have no passwords
+                } else if (currentUser instanceof User && ((User)currentUser).isAdmin()) {
+                    hasRights = true; //admin user has full control
+                } else {
+                    // otherwise let's check the granted privileges
+                    String path = authorizable.getPath();
+                    if (path != null) {
+                        //check if the non-admin user has sufficient rights on the home folder
+                        AccessControlManager acm = jcrSession.getAccessControlManager();
+                        Set<Privilege> requiredPrivileges = new HashSet<>();
+                        requiredPrivileges.add(acm.privilegeFromName(Privilege.JCR_READ));
+                        requiredPrivileges.add(acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT));
+                        hasRights = acm.hasPrivileges(path, requiredPrivileges.toArray(new Privilege[requiredPrivileges.size()]));
+                    }
+
+                    if (!hasRights && jcrSession.getUserID().equals(userId)) {
+                        // check if the ChangeUserPassword service is configured to always allow
+                        // a user to change their own password.
+                        hasRights = alwaysAllowSelfChangePassword;
+                    }
+                }
+            }
+        } catch (RepositoryException e) {
+            log.warn("Failed to determine if {} can change the password of user {}", jcrSession.getUserID(), userId);
+        }
+        return hasRights;
+    }
 
     // ---------- SCR Integration ----------------------------------------------
 
-
     @Activate
     protected void activate(BundleContext bundleContext, Map<String, Object> properties)
             throws InvalidKeyException, NoSuchAlgorithmException,
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/AuthorizablePrivilegesInfoIT.java b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/AuthorizablePrivilegesInfoIT.java
index 939a4d0..fbc2347 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/AuthorizablePrivilegesInfoIT.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/AuthorizablePrivilegesInfoIT.java
@@ -45,6 +45,7 @@
 import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
 import org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo;
 import org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo.PropertyUpdateTypes;
+import org.apache.sling.jackrabbit.usermanager.ChangeUserPassword;
 import org.apache.sling.jackrabbit.usermanager.CreateGroup;
 import org.apache.sling.jackrabbit.usermanager.CreateUser;
 import org.apache.sling.jackrabbit.usermanager.DeleteGroup;
@@ -53,6 +54,7 @@
 import org.apache.sling.jackrabbit.usermanager.UpdateUser;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.base.util.AccessControlUtil;
+import org.apache.sling.jcr.jackrabbit.accessmanager.DeleteAces;
 import org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce;
 import org.apache.sling.servlets.post.Modification;
 import org.junit.After;
@@ -76,95 +78,101 @@
 @RunWith(PaxExam.class)
 @ExamReactorStrategy(PerClass.class)
 public class AuthorizablePrivilegesInfoIT extends UserManagerTestSupport {
-	private static AtomicLong counter = new AtomicLong(0);
+    private static AtomicLong counter = new AtomicLong(0);
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     @Inject
     protected BundleContext bundleContext;
-    
+
     @Inject
     protected SlingRepository repository;
 
-	@Inject
-	private AuthorizablePrivilegesInfo privilegesInfo;
-	
-	@Inject
-	private UserConfiguration userConfig;
-	
-	@Inject
-	private CreateUser createUser;
+    @Inject
+    private AuthorizablePrivilegesInfo privilegesInfo;
 
-	@Inject
-	private CreateGroup createGroup;
+    @Inject
+    private UserConfiguration userConfig;
 
-	@Inject
-	private UpdateUser updateUser;
+    @Inject
+    private CreateUser createUser;
 
-	@Inject
-	private UpdateGroup updateGroup;
+    @Inject
+    private CreateGroup createGroup;
 
-	@Inject
-	private DeleteUser deleteUser;
+    @Inject
+    private UpdateUser updateUser;
 
-	@Inject
-	private DeleteGroup deleteGroup;
+    @Inject
+    private UpdateGroup updateGroup;
 
-	@Inject
-	private ModifyAce modifyAce;
+    @Inject
+    private DeleteUser deleteUser;
 
-	@Rule
-	public TestName testName = new TestName();
+    @Inject
+    private DeleteGroup deleteGroup;
+
+    @Inject
+    private ModifyAce modifyAce;
+
+    @Inject
+    private DeleteAces deleteAces;
+
+    @Inject
+    private ChangeUserPassword changeUserPassword;
+
+    @Rule
+    public TestName testName = new TestName();
 
     protected Session adminSession;
     protected User user1;
     protected Session user1Session;
 
-	@Configuration
-	public Option[] configuration() {
-	    return options(
-	        baseConfiguration()
-	    );
-	}
+    @Configuration
+    public Option[] configuration() {
+        return options(
+            baseConfiguration()
+        );
+    }
 
     @Before
     public void setup() throws Exception {
         adminSession = repository.login(new SimpleCredentials("admin", "admin".toCharArray()));
-		assertNotNull("Expected adminSession to not be null", adminSession);
+        assertNotNull("Expected adminSession to not be null", adminSession);
 
-		user1 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd", 
-				Collections.emptyMap(), new ArrayList<Modification>());
-		assertNotNull("Expected user1 to not be null", user1);
-		
-		if (adminSession.hasPendingChanges()) {
-			adminSession.save();
-		}
-		
-		user1Session = repository.login(new SimpleCredentials(user1.getID(), "testPwd".toCharArray()));
-		assertNotNull("Expected user1Session to not be null", user1Session);
+        user1 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd", 
+                Collections.emptyMap(), new ArrayList<Modification>());
+        assertNotNull("Expected user1 to not be null", user1);
+        
+        if (adminSession.hasPendingChanges()) {
+            adminSession.save();
+        }
+        
+        user1Session = repository.login(new SimpleCredentials(user1.getID(), "testPwd".toCharArray()));
+        assertNotNull("Expected user1Session to not be null", user1Session);
     }
 
     @After
     public void teardown() {
-    	try {
-			adminSession.refresh(false);
-			if (user1 != null) {
-				deleteUser.deleteUser(adminSession, user1.getID(), new ArrayList<>());
-			}
+        try {
+            adminSession.refresh(false);
+            if (user1 != null) {
+                deleteUser.deleteUser(adminSession, user1.getID(), new ArrayList<>());
+            }
 
-			if (adminSession.hasPendingChanges()) {
-				adminSession.save();
-			}
-		} catch (RepositoryException e) {
-			logger.warn("Failed to delete user: " + e.getMessage(), e);
-		}
+            if (adminSession.hasPendingChanges()) {
+                adminSession.save();
+            }
+        } catch (RepositoryException e) {
+            logger.warn("Failed to delete user: " + e.getMessage(), e);
+        }
 
-    	user1Session.logout();
+        user1Session.logout();
         adminSession.logout();
     }
 
-	protected String createUniqueName(String prefix) {
-		return String.format("%s_%s%d", prefix, testName.getMethodName(), counter.incrementAndGet());
-	}
+    protected String createUniqueName(String prefix) {
+        return String.format("%s_%s%d", prefix, testName.getMethodName(), counter.incrementAndGet());
+    }
 
     /**
      * Checks whether the current user has been granted privileges
@@ -172,49 +180,49 @@
      */
     @Test
     public void canAddUser() throws Exception {
-		assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
-		
-		User user2 = null;
-		try {
-			// initially user can't do the operations
-			assertFalse("Should not be allowed to add user",
-					privilegesInfo.canAddUser(user1Session));
-			
-			String usersPath = userConfig.getParameters().getConfigValue("usersPath", (String)null);
-			assertNotNull("Users Path should not be null", usersPath);
-			assertTrue("Users Path should exist",
-					adminSession.itemExists(usersPath));
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
 
-			// grant user1 rights
-			Map<String, String> privileges = new HashMap<>();
-			privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_READ_ACCESS_CONTROL), "granted");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_MODIFY_ACCESS_CONTROL), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_WRITE), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
-			modifyAce.modifyAce(adminSession, usersPath, user1.getID(), 
-					privileges, 
-					"first");
-			assertTrue("Should be allowed to add user",
-					privilegesInfo.canAddUser(user1Session));
+        User user2 = null;
+        try {
+            // initially user can't do the operations
+            assertFalse("Should not be allowed to add user",
+                    privilegesInfo.canAddUser(user1Session));
 
-			// verify that the user can actually add the user
-			try {
-				Map<String, String> propMap = new HashMap<>();
-				propMap.put("prop1", "value1");
-				propMap.put("nested/prop2", "value2");
-				user2 = createUser.createUser(user1Session, createUniqueName("user"), "testPwd", "testPwd", 
-						propMap, new ArrayList<Modification>());
-				assertNotNull("Expected user2 to not be null", user2);
-			} catch (RepositoryException e) {
-				logger.error("Did not expect RepositoryException when adding user: " + e.getMessage(), e);
-				fail("Did not expect RepositoryException when adding user: " + e.getMessage());
-			}
-		} finally {
-			if (user2 != null) {
-				deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
-			}
-		}
+            String usersPath = userConfig.getParameters().getConfigValue("usersPath", (String)null);
+            assertNotNull("Users Path should not be null", usersPath);
+            assertTrue("Users Path should exist",
+                    adminSession.itemExists(usersPath));
+
+            // grant user1 rights
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ_ACCESS_CONTROL), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_MODIFY_ACCESS_CONTROL), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_WRITE), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, usersPath, user1.getID(),
+                    privileges,
+                    "first");
+            assertTrue("Should be allowed to add user",
+                    privilegesInfo.canAddUser(user1Session));
+
+            // verify that the user can actually add the user
+            try {
+                Map<String, String> propMap = new HashMap<>();
+                propMap.put("prop1", "value1");
+                propMap.put("nested/prop2", "value2");
+                user2 = createUser.createUser(user1Session, createUniqueName("user"), "testPwd", "testPwd", 
+                        propMap, new ArrayList<Modification>());
+                assertNotNull("Expected user2 to not be null", user2);
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when adding user: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when adding user: " + e.getMessage());
+            }
+        } finally {
+            if (user2 != null) {
+                deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
+            }
+        }
     }
 
     /**
@@ -223,71 +231,71 @@
      */
     @Test
     public void canAddGroup() throws Exception {
-		assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
-		
-		workaroundMissingGroupsPath();
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
 
-		Group group1 = null;
-		try {
-			// initially user can't do the operations
-			assertFalse("Should not be allowed to add group",
-					privilegesInfo.canAddGroup(user1Session));
-			
-			String groupsPath = userConfig.getParameters().getConfigValue("groupsPath", (String)null);
-			assertNotNull("Groups Path should not be null", groupsPath);
-			assertTrue("Groups Path should exist",
-					adminSession.itemExists(groupsPath));
-			
-			// grant user1 rights
-			Map<String, String> privileges = new HashMap<>();
-			privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_READ_ACCESS_CONTROL), "granted");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_MODIFY_ACCESS_CONTROL), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_WRITE), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
-			modifyAce.modifyAce(adminSession, groupsPath, user1.getID(), 
-					privileges, 
-					"first");
-			assertTrue("Should be allowed to add group",
-					privilegesInfo.canAddGroup(user1Session));
+        workaroundMissingGroupsPath();
 
-			// verify that the user can actually add the user
-			try {
-				Map<String, String> propMap = new HashMap<>();
-				propMap.put("prop1", "value1");
-				propMap.put("nested/prop2", "value2");
-				group1 = createGroup.createGroup(user1Session, createUniqueName("group"), 
-						propMap, new ArrayList<Modification>());
-				assertNotNull("Expected group1 to not be null", group1);
-			} catch (RepositoryException e) {
-				logger.error("Did not expect RepositoryException when adding group: " + e.getMessage(), e);
-				fail("Did not expect RepositoryException when adding group: " + e.getMessage());
-			}
-		} finally {
-			if (group1 != null) {
-				deleteGroup.deleteGroup(user1Session, group1.getID(), new ArrayList<>());
-			}
-		}
+        Group group1 = null;
+        try {
+            // initially user can't do the operations
+            assertFalse("Should not be allowed to add group",
+                    privilegesInfo.canAddGroup(user1Session));
+
+            String groupsPath = userConfig.getParameters().getConfigValue("groupsPath", (String)null);
+            assertNotNull("Groups Path should not be null", groupsPath);
+            assertTrue("Groups Path should exist",
+                    adminSession.itemExists(groupsPath));
+
+            // grant user1 rights
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ_ACCESS_CONTROL), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_MODIFY_ACCESS_CONTROL), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_WRITE), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, groupsPath, user1.getID(),
+                    privileges,
+                    "first");
+            assertTrue("Should be allowed to add group",
+                    privilegesInfo.canAddGroup(user1Session));
+
+            // verify that the user can actually add the user
+            try {
+                Map<String, String> propMap = new HashMap<>();
+                propMap.put("prop1", "value1");
+                propMap.put("nested/prop2", "value2");
+                group1 = createGroup.createGroup(user1Session, createUniqueName("group"),
+                        propMap, new ArrayList<Modification>());
+                assertNotNull("Expected group1 to not be null", group1);
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when adding group: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when adding group: " + e.getMessage());
+            }
+        } finally {
+            if (group1 != null) {
+                deleteGroup.deleteGroup(user1Session, group1.getID(), new ArrayList<>());
+            }
+        }
     }
 
-	/**
-	 * The oak groupsPath appears to be missing until the first group is created.
-	 * So create a group as the admin user to get it bootstrapped. This makes it
-	 * possible for non-admin users to create groups without requiring extra access 
-	 * rights to the intermediate parents of the groupsPath folder.
-	 */
-	protected void workaroundMissingGroupsPath() throws RepositoryException {
-		String groupsPath = userConfig.getParameters().getConfigValue("groupsPath", (String)null);
-		assertNotNull("Groups Path should not be null", groupsPath);
-		if (!adminSession.itemExists(groupsPath)) {
-			// create a group and the remove it
-			Group tempGroup = createGroup.createGroup(adminSession, createUniqueName("group"), 
-					Collections.emptyMap(), new ArrayList<Modification>());
-			deleteGroup.deleteGroup(adminSession, tempGroup.getID(), new ArrayList<>());
-		}
-		assertTrue("Groups Path should exist",
-				adminSession.itemExists(groupsPath));
-	}
+    /**
+     * The oak groupsPath appears to be missing until the first group is created.
+     * So create a group as the admin user to get it bootstrapped. This makes it
+     * possible for non-admin users to create groups without requiring extra access
+     * rights to the intermediate parents of the groupsPath folder.
+     */
+    protected void workaroundMissingGroupsPath() throws RepositoryException {
+        String groupsPath = userConfig.getParameters().getConfigValue("groupsPath", (String)null);
+        assertNotNull("Groups Path should not be null", groupsPath);
+        if (!adminSession.itemExists(groupsPath)) {
+            // create a group and the remove it
+            Group tempGroup = createGroup.createGroup(adminSession, createUniqueName("group"),
+                    Collections.emptyMap(), new ArrayList<Modification>());
+            deleteGroup.deleteGroup(adminSession, tempGroup.getID(), new ArrayList<>());
+        }
+        assertTrue("Groups Path should exist",
+                adminSession.itemExists(groupsPath));
+    }
 
     /**
      * Checks whether the current user has been granted privileges
@@ -295,297 +303,297 @@
      */
     @Test
     public void canUpdateProperties() throws Exception {
-		assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
 
-		workaroundMissingGroupsPath();
+        workaroundMissingGroupsPath();
 
-		User user2 = null;
-		Group group1 = null;
+        User user2 = null;
+        Group group1 = null;
 
-		try {
-			// create a couple of test users
-			user2 = createUser.createUser(adminSession, createUniqueName("group"), "testPwd", "testPwd", 
-					Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
-			assertNotNull("Expected user2 to not be null", user2);
+        try {
+            // create a couple of test users
+            user2 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd",
+                    Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+            assertNotNull("Expected user2 to not be null", user2);
 
-			group1 = createGroup.createGroup(adminSession, createUniqueName("group"), 
-					Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
-			assertNotNull("Expected group1 to not be null", group1);
+            group1 = createGroup.createGroup(adminSession, createUniqueName("group"),
+                    Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+            assertNotNull("Expected group1 to not be null", group1);
 
-			String [] principalIds = new String[] { user2.getID(), group1.getID() };
+            String [] principalIds = new String[] { user2.getID(), group1.getID() };
 
-			// initially user can't do the operation
-			for (String pid : principalIds) {
-				assertFalse("Should not be allowed to update properties for: " + pid, 
-						privilegesInfo.canUpdateProperties(user1Session, pid));
-			}
-			
-			// start with only read rights
-			Map<String, String> privileges = new HashMap<>();
-			privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			for (String pid : principalIds) {
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
-			}
+            // initially user can't do the operation
+            for (String pid : principalIds) {
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid));
+            }
 
-			// + grant user management rights
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			for (String pid : principalIds) {
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
-			}
+            // start with only read rights
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            for (String pid : principalIds) {
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
+            }
 
-			
-			// grant rights to only remove properties
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "none");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "none");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "none");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "granted");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			for (String pid : principalIds) {
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
-			}
-			
-			// verify that the user can actually delete property
-			Map<String, Object> propsMap = new HashMap<>();
-			propsMap.put("prop1@Delete", "value1");
-			try {
-				updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
-				updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
-				assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
-				user1Session.save();
-			} catch (RepositoryException e) {
-				logger.error("Did not expect RepositoryException when deleting property: " + e.getMessage(), e);
-				fail("Did not expect RepositoryException when deleting property: " + e.getMessage());
-			}
-			// verify that the user can not add nested property
-			propsMap = new HashMap<>();
-			propsMap.put("nested/prop2", "value2");
-			updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
-			updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
-			assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
-			try {
-				user1Session.save();
-				fail("Expected AccessDeniedException when adding nested property");
-			} catch (AccessDeniedException e) {
-				// expected
-				user1Session.refresh(false);
-			}
-			
-			
-			
-			// grant rights to only alter (non-nested) properties
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "granted");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "none");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "none");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			for (String pid : principalIds) {
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
-			}
-			
-			// verify that the user can actually add property
-			propsMap = new HashMap<>();
-			propsMap.put("prop1", "value1");
-			try {
-				updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
-				updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
-				assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
-				user1Session.save();
-			} catch (RepositoryException e) {
-				logger.error("Did not expect RepositoryException when adding property: " + e.getMessage(), e);
-				fail("Did not expect RepositoryException when adding property: " + e.getMessage());
-			}
-			// verify that the user can not add nested property
-			propsMap.put("nested/prop2", "value2");
-			updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
-			updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
-			assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
-			try {
-				user1Session.save();
-				fail("Expected AccessDeniedException when adding nested property");
-			} catch (AccessDeniedException e) {
-				// expected
-				user1Session.refresh(false);
-			}
-			
-			
-			
-			// grant rights to alter (non-nested or nested) properties
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "granted");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "none");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			for (String pid : principalIds) {
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
-				assertFalse("Should not be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
-			}
-			
-			// verify that the user can actually add property and nested property
-			propsMap = new HashMap<>();
-			propsMap.put("prop1", "value1");
-			propsMap.put("nested/prop2", "value2");
-			try {
-				updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
-				updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
-				assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
-				user1Session.save();
-			} catch (RepositoryException e) {
-				logger.error("Did not expect RepositoryException when adding properties: " + e.getMessage(), e);
-				fail("Did not expect RepositoryException when adding properties: " + e.getMessage());
-			}
-			
-			
-			
-			// grant rights to alter (non-nested or nested) properties and remove properties
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "granted");
-			privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "granted");
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "granted");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			for (String pid : principalIds) {
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
-				assertTrue("Should be allowed to update properties for: " + pid,
-						privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
-			}			
-			
-			// verify that the user can actually add property and nested property
-			propsMap = new HashMap<>();
-			propsMap.put("prop3", "value3");
-			propsMap.put("nested/prop4", "value4");
-			propsMap.put("prop1@Delete", "value1");
-			propsMap.put("nested/prop2@Delete", "value1");
-			try {
-				updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
-				updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
-				assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
-				user1Session.save();
-			} catch (RepositoryException e) {
-				logger.error("Did not expect RepositoryException when adding or deleting properties: " + e.getMessage(), e);
-				fail("Did not expect RepositoryException when adding or deleting properties: " + e.getMessage());
-			}
-		} finally {
-			if (user2 != null) {
-				deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
-			}
-		}
+            // + grant user management rights
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            for (String pid : principalIds) {
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
+            }
+
+
+            // grant rights to only remove properties
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "none");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "none");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "none");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            for (String pid : principalIds) {
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
+            }
+
+            // verify that the user can actually delete property
+            Map<String, Object> propsMap = new HashMap<>();
+            propsMap.put("prop1@Delete", "value1");
+            try {
+                updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
+                updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
+                assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+                user1Session.save();
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when deleting property: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when deleting property: " + e.getMessage());
+            }
+            // verify that the user can not add nested property
+            propsMap = new HashMap<>();
+            propsMap.put("nested/prop2", "value2");
+            updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
+            updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
+            assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+            try {
+                user1Session.save();
+                fail("Expected AccessDeniedException when adding nested property");
+            } catch (AccessDeniedException e) {
+                // expected
+                user1Session.refresh(false);
+            }
+
+
+
+            // grant rights to only alter (non-nested) properties
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "none");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "none");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            for (String pid : principalIds) {
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
+            }
+
+            // verify that the user can actually add property
+            propsMap = new HashMap<>();
+            propsMap.put("prop1", "value1");
+            try {
+                updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
+                updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
+                assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+                user1Session.save();
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when adding property: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when adding property: " + e.getMessage());
+            }
+            // verify that the user can not add nested property
+            propsMap.put("nested/prop2", "value2");
+            updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
+            updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
+            assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+            try {
+                user1Session.save();
+                fail("Expected AccessDeniedException when adding nested property");
+            } catch (AccessDeniedException e) {
+                // expected
+                user1Session.refresh(false);
+            }
+
+
+
+            // grant rights to alter (non-nested or nested) properties
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "none");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            for (String pid : principalIds) {
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
+                assertFalse("Should not be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
+            }
+
+            // verify that the user can actually add property and nested property
+            propsMap = new HashMap<>();
+            propsMap.put("prop1", "value1");
+            propsMap.put("nested/prop2", "value2");
+            try {
+                updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
+                updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
+                assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+                user1Session.save();
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when adding properties: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when adding properties: " + e.getMessage());
+            }
+
+
+
+            // grant rights to alter (non-nested or nested) properties and remove properties
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ADD_PROPERTIES), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_ADD_CHILD_NODES), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_REMOVE_PROPERTIES), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            for (String pid : principalIds) {
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.removeProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.removeProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.addNestedProperty));
+                assertTrue("Should be allowed to update properties for: " + pid,
+                        privilegesInfo.canUpdateProperties(user1Session, pid, PropertyUpdateTypes.removeProperty));
+            }
+
+            // verify that the user can actually add property and nested property
+            propsMap = new HashMap<>();
+            propsMap.put("prop3", "value3");
+            propsMap.put("nested/prop4", "value4");
+            propsMap.put("prop1@Delete", "value1");
+            propsMap.put("nested/prop2@Delete", "value1");
+            try {
+                updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
+                updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
+                assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+                user1Session.save();
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when adding or deleting properties: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when adding or deleting properties: " + e.getMessage());
+            }
+        } finally {
+            if (user2 != null) {
+                deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
+            }
+        }
     }
 
     /**
@@ -594,74 +602,74 @@
      */
     @Test
     public void canRemove() throws Exception {
-		assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
-		
-		workaroundMissingGroupsPath();
-		
-		User user2 = null;
-		Group group1 = null;
-		try {
-			user2 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd", 
-					Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
-			assertNotNull("Expected user2 to not be null", user2);
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
 
-			group1 = createGroup.createGroup(adminSession, createUniqueName("group"), 
-					Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
-			assertNotNull("Expected group1 to not be null", group1);
+        workaroundMissingGroupsPath();
 
-			// initially user can't do the operations
-			assertFalse("Should not be allowed to remove user", 
-					privilegesInfo.canRemove(user1Session, user2.getID()));
-			assertFalse("Should not be allowed to remove group", 
-					privilegesInfo.canRemove(user1Session, group1.getID()));
-			
-			// grant user1 rights to user2 profile
-			Map<String, String> privileges = new HashMap<>();
-			privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			assertFalse("Should not be allowed to remove user", 
-					privilegesInfo.canRemove(user1Session, user2.getID()));
-			assertFalse("Should not be allowed to remove group", 
-					privilegesInfo.canRemove(user1Session, group1.getID()));
+        User user2 = null;
+        Group group1 = null;
+        try {
+            user2 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd",
+                    Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+            assertNotNull("Expected user2 to not be null", user2);
 
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
-			modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			assertTrue("Should be allowed to remove user", 
-					privilegesInfo.canRemove(user1Session, user2.getID()));
-			assertTrue("Should be allowed to remove group", 
-					privilegesInfo.canRemove(user1Session, group1.getID()));
+            group1 = createGroup.createGroup(adminSession, createUniqueName("group"),
+                    Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+            assertNotNull("Expected group1 to not be null", group1);
 
-			// verify that the user can actually delete the user
-			String user2Id = user2.getID();
-			deleteUser.deleteUser(user1Session, user2Id, new ArrayList<>());
-			user2 = null;
-			// verify the user is no longer there
-			UserManager um = AccessControlUtil.getUserManager(user1Session);
-			assertNull("Expected user to be gone: " + user2Id, um.getAuthorizable(user2Id));
-			
-			// verify that the user can actually delete the group
-			String group1Id = group1.getID();
-			deleteGroup.deleteGroup(user1Session, group1Id, new ArrayList<>());
-			group1 = null;
-			assertNull("Expected group to be gone: " + group1Id, um.getAuthorizable(group1Id));
-		} finally {
-			if (user2 != null) {
-				deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
-			}
-			if (group1 != null) {
-				deleteGroup.deleteGroup(adminSession, group1.getID(), new ArrayList<>());
-			}
-		}
+            // initially user can't do the operations
+            assertFalse("Should not be allowed to remove user",
+                    privilegesInfo.canRemove(user1Session, user2.getID()));
+            assertFalse("Should not be allowed to remove group",
+                    privilegesInfo.canRemove(user1Session, group1.getID()));
+
+            // grant user1 rights to user2 profile
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            assertFalse("Should not be allowed to remove user",
+                    privilegesInfo.canRemove(user1Session, user2.getID()));
+            assertFalse("Should not be allowed to remove group",
+                    privilegesInfo.canRemove(user1Session, group1.getID()));
+
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            assertTrue("Should be allowed to remove user",
+                    privilegesInfo.canRemove(user1Session, user2.getID()));
+            assertTrue("Should be allowed to remove group",
+                    privilegesInfo.canRemove(user1Session, group1.getID()));
+
+            // verify that the user can actually delete the user
+            String user2Id = user2.getID();
+            deleteUser.deleteUser(user1Session, user2Id, new ArrayList<>());
+            user2 = null;
+            // verify the user is no longer there
+            UserManager um = AccessControlUtil.getUserManager(user1Session);
+            assertNull("Expected user to be gone: " + user2Id, um.getAuthorizable(user2Id));
+
+            // verify that the user can actually delete the group
+            String group1Id = group1.getID();
+            deleteGroup.deleteGroup(user1Session, group1Id, new ArrayList<>());
+            group1 = null;
+            assertNull("Expected group to be gone: " + group1Id, um.getAuthorizable(group1Id));
+        } finally {
+            if (user2 != null) {
+                deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
+            }
+            if (group1 != null) {
+                deleteGroup.deleteGroup(adminSession, group1.getID(), new ArrayList<>());
+            }
+        }
     }
 
     /**
@@ -670,52 +678,197 @@
      */
     @Test
     public void canUpdateGroupMembers() throws Exception {
-		assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
-		
-		workaroundMissingGroupsPath();
-		
-		Group group1 = null;
-		try {
-			group1 = createGroup.createGroup(adminSession, createUniqueName("group"), 
-					Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
-			assertNotNull("Expected group1 to not be null", group1);
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
 
-			// initially user can't do the operations
-			assertFalse("Should not be allowed to update group members", 
-					privilegesInfo.canUpdateGroupMembers(user1Session, group1.getID()));
-			
-			// grant user1 rights to group1 profile
-			Map<String, String> privileges = new HashMap<>();
-			privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			assertFalse("Should not be allowed to update group members", 
-					privilegesInfo.canUpdateGroupMembers(user1Session, group1.getID()));
+        workaroundMissingGroupsPath();
 
-			privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
-			modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(), 
-					privileges, 
-					"first");
-			assertTrue("Should be allowed to update group members", 
-					privilegesInfo.canUpdateGroupMembers(user1Session, group1.getID()));
+        Group group1 = null;
+        try {
+            group1 = createGroup.createGroup(adminSession, createUniqueName("group"),
+                    Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+            assertNotNull("Expected group1 to not be null", group1);
 
-			// verify that the user can actually change the group members
-			try {
-				Map<String, Object> propsMap = new HashMap<>();
-				propsMap.put(":member", user1.getID());
-				updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
-				assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
-				user1Session.save();
-			} catch (RepositoryException e) {
-				logger.error("Did not expect RepositoryException when adding member to group: " + e.getMessage(), e);
-				fail("Did not expect RepositoryException when adding member to group: " + e.getMessage());
-			}
-		} finally {
-			if (group1 != null) {
-				deleteGroup.deleteGroup(adminSession, group1.getID(), new ArrayList<>());
-			}
-		}
+            // initially user can't do the operations
+            assertFalse("Should not be allowed to update group members",
+                    privilegesInfo.canUpdateGroupMembers(user1Session, group1.getID()));
+
+            // grant user1 rights to group1 profile
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            assertFalse("Should not be allowed to update group members",
+                    privilegesInfo.canUpdateGroupMembers(user1Session, group1.getID()));
+
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, group1.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            assertTrue("Should be allowed to update group members",
+                    privilegesInfo.canUpdateGroupMembers(user1Session, group1.getID()));
+
+            // verify that the user can actually change the group members
+            try {
+                Map<String, Object> propsMap = new HashMap<>();
+                propsMap.put(":member", user1.getID());
+                updateGroup.updateGroup(user1Session, group1.getID(), propsMap, new ArrayList<>());
+                assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+                user1Session.save();
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when adding member to group: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when adding member to group: " + e.getMessage());
+            }
+        } finally {
+            if (group1 != null) {
+                deleteGroup.deleteGroup(adminSession, group1.getID(), new ArrayList<>());
+            }
+        }
     }
-    
+
+    protected void configMinimumUserPrivileges(User user) throws RepositoryException {
+        //change the ACE for the user home folder to the minimum privileges
+        // and without rep:userManagement
+        deleteAces.deleteAces(adminSession, user.getPath(), new String[] {user.getID()});
+        Map<String, String> privileges = new HashMap<>();
+        privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+        privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "granted");
+        modifyAce.modifyAce(adminSession, user.getPath(), user.getID(),
+                privileges,
+                "first");
+        if (adminSession.hasPendingChanges()) {
+            adminSession.save();
+        }
+    }
+
+    /**
+     * SLING-9814 Checks whether the current user has been granted privileges
+     * to disable a user.
+     */
+    @Test
+    public void canDisableUser() throws Exception {
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
+
+        User user2 = null;
+        try {
+            // setup the user privileges
+            configMinimumUserPrivileges(user1);
+
+            // create another test user
+            user2 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd",
+                    Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+            assertNotNull("Expected user2 to not be null", user2);
+            // setup the user privileges
+            configMinimumUserPrivileges(user2);
+
+            // initially user can't do the operations
+            assertFalse("Should not be allowed to disable yourself",
+                    privilegesInfo.canDisable(user1Session, user1.getID()));
+            assertFalse("Should not be allowed to disable user",
+                    privilegesInfo.canDisable(user1Session, user2.getID()));
+
+            // grant user1 rights to user2 profile
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            assertTrue("Should be allowed to disable user",
+                    privilegesInfo.canDisable(user1Session, user2.getID()));
+
+            // verify that the user can actually disable the other user
+            Map<String, Object> propsMap = new HashMap<>();
+            propsMap.put(":disabled", "true");
+            propsMap.put(":disabledReason", "Just a test");
+            try {
+                updateUser.updateUser(user1Session, user2.getID(), propsMap, new ArrayList<>());
+                assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+                user1Session.save();
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when disabling the user: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when disabling the user: " + e.getMessage());
+            }
+        } finally {
+            if (user2 != null) {
+                deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
+            }
+        }
+    }
+
+    /**
+     * SLING-9814 Checks whether the current user has been granted privileges
+     * to change a user's password.
+     */
+    @Test
+    public void canChangePassword() throws Exception {
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
+
+        User user2 = null;
+        try {
+            // create another test user
+            user2 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd",
+                    Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+            assertNotNull("Expected user2 to not be null", user2);
+
+            // initially user can't do the operations
+            assertFalse("Should not be allowed to change the user password",
+                    privilegesInfo.canChangePassword(user1Session, user2.getID()));
+
+            // grant user1 rights to user2 profile
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), user1.getID(),
+                    privileges,
+                    "first");
+            assertTrue("Should be allowed to change the user password user",
+                    privilegesInfo.canChangePassword(user1Session, user2.getID()));
+
+            // verify that the user can actually change the password of the other user
+            try {
+                changeUserPassword.changePassword(user1Session, user2.getID(), "testPwd", "newPassword", "newPassword", new ArrayList<>());
+                assertTrue("Expected pending changes in the jcr session", user1Session.hasPendingChanges());
+                user1Session.save();
+            } catch (RepositoryException e) {
+                logger.error("Did not expect RepositoryException when changing the user password: " + e.getMessage(), e);
+                fail("Did not expect RepositoryException when changing the user password: " + e.getMessage());
+            }
+        } finally {
+            if (user2 != null) {
+                deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
+            }
+        }
+    }
+
+    /**
+     * SLING-9814 Checks whether the current user has been granted privileges
+     * to change the anonymous user's password.
+     */
+    @Test
+    public void cannotChangePassword_for_anonymous() throws Exception {
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
+
+        // anonymous user has no password to change
+        assertFalse("Should not be allowed to change the user password",
+                privilegesInfo.canChangePassword(user1Session, "anonymous"));
+        assertFalse("Should not be allowed to change the user password",
+                privilegesInfo.canChangePassword(adminSession, "anonymous"));
+    }
+
+    /**
+     * SLING-9814 Checks whether the current user has been granted privileges
+     * to change the anonymous user's password.
+     */
+    @Test
+    public void cannotChangePassword_for_service_user() throws Exception {
+        assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
+
+        // service user has no password to change
+        assertFalse("Should not be allowed to change the user password",
+                privilegesInfo.canChangePassword(user1Session, "sling-jcr-usermanager"));
+        assertFalse("Should not be allowed to change the user password",
+                privilegesInfo.canChangePassword(adminSession, "sling-jcr-usermanager"));
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/post/ChangeUserPasswordIT.java b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/post/ChangeUserPasswordIT.java
index 007575f..782d683 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/post/ChangeUserPasswordIT.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/post/ChangeUserPasswordIT.java
@@ -19,6 +19,7 @@
 package org.apache.sling.jcr.jackrabbit.usermanager.it.post;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -41,7 +42,9 @@
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+import org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo;
 import org.apache.sling.jackrabbit.usermanager.ChangeUserPassword;
+import org.apache.sling.jackrabbit.usermanager.CreateGroup;
 import org.apache.sling.jackrabbit.usermanager.CreateUser;
 import org.apache.sling.jackrabbit.usermanager.DeleteUser;
 import org.apache.sling.jcr.api.SlingRepository;
@@ -79,17 +82,20 @@
 
     @Inject
     protected BundleContext bundleContext;
-    
+
     @Inject
     protected SlingRepository repository;
 
     @Inject
     protected ConfigurationAdmin configAdmin;
-    
+
     @Inject
     private CreateUser createUser;
 
     @Inject
+    private CreateGroup createGroup;
+
+    @Inject
     private ModifyAce modifyAce;
 
     @Inject
@@ -98,6 +104,9 @@
     @Inject
     private DeleteUser deleteUser;
 
+    @Inject
+    private AuthorizablePrivilegesInfo privilegesInfo;
+
     @Rule
     public TestName testName = new TestName();
 
@@ -117,40 +126,40 @@
         adminSession = repository.login(new SimpleCredentials("admin", "admin".toCharArray()));
         assertNotNull("Expected adminSession to not be null", adminSession);
 
-        user1 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd", 
+        user1 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd",
                 Collections.emptyMap(), new ArrayList<Modification>());
         assertNotNull("Expected user1 to not be null", user1);
-        
+
         user1Session = repository.login(new SimpleCredentials(user1.getID(), "testPwd".toCharArray()));
         assertNotNull("Expected user1Session to not be null", user1Session);
-        
+
         //change the ACE for the user home folder to the minimum privileges
         // and without rep:userManagement
         deleteAces.deleteAces(adminSession, user1.getPath(), new String[] {user1.getID()});
         Map<String, String> privileges = new HashMap<>();
         privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
         privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_ALTER_PROPERTIES), "granted");
-        modifyAce.modifyAce(adminSession, user1.getPath(), user1.getID(), 
-                privileges, 
+        modifyAce.modifyAce(adminSession, user1.getPath(), user1.getID(),
+                privileges,
                 "first");
         if (adminSession.hasPendingChanges()) {
-        	adminSession.save();
+            adminSession.save();
         }
     }
 
     @After
     public void teardown() {
-    	try {
-			adminSession.refresh(false);
-			if (user1 != null) {
-				deleteUser.deleteUser(adminSession, user1.getID(), new ArrayList<>());
-			}
-			if (adminSession.hasPendingChanges()) {
-				adminSession.save();
-			}
-		} catch (RepositoryException e) {
-			logger.warn("Failed to delete user: " + e.getMessage(), e);
-		}
+        try {
+            adminSession.refresh(false);
+            if (user1 != null) {
+                deleteUser.deleteUser(adminSession, user1.getID(), new ArrayList<>());
+            }
+            if (adminSession.hasPendingChanges()) {
+                adminSession.save();
+            }
+        } catch (RepositoryException e) {
+            logger.warn("Failed to delete user: " + e.getMessage(), e);
+        }
 
         user1Session.logout();
         adminSession.logout();
@@ -172,18 +181,23 @@
             // update the service configuration to ensure the option is enabled
             Dictionary<String, Object> newServiceProps = replaceConfigProp(originalServiceProps, "alwaysAllowSelfChangePassword", Boolean.TRUE);
             configuration.update(newServiceProps);
-            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class, 
+            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class,
                     "alwaysAllowSelfChangePassword", Boolean.TRUE);
-            
+
             serviceReference = bundleContext.getServiceReference(ChangeUserPassword.class);
             assertEquals(Boolean.TRUE, serviceReference.getProperty("alwaysAllowSelfChangePassword"));
             ChangeUserPassword changeUserPassword = bundleContext.getService(serviceReference);
             assertNotNull(changeUserPassword);
-            changeUserPassword.changePassword(user1Session, 
-                    user1.getID(), 
-                    "testPwd", 
-                    "testPwdChanged", 
-                    "testPwdChanged", 
+
+            // user can do the operation
+            assertTrue("Should be allowed to change the user password",
+                    privilegesInfo.canChangePassword(user1Session, user1.getID()));
+
+            changeUserPassword.changePassword(user1Session,
+                    user1.getID(),
+                    "testPwd",
+                    "testPwdChanged",
+                    "testPwdChanged",
                     new ArrayList<>());
             try {
                 user1Session.save();
@@ -199,7 +213,7 @@
             
             //put the original config back
             configuration.update(originalServiceProps);
-            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class, "alwaysAllowSelfChangePassword", 
+            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class, "alwaysAllowSelfChangePassword",
                     originalServiceProps == null ? null :originalServiceProps.get("alwaysAllowSelfChangePassword"));
         }
     }
@@ -216,18 +230,23 @@
             // update the service configuration to ensure the option is disabled
             Dictionary<String, Object> newServiceProps = replaceConfigProp(originalServiceProps, "alwaysAllowSelfChangePassword", Boolean.FALSE);
             configuration.update(newServiceProps);
-            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class, 
+            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class,
                     "alwaysAllowSelfChangePassword", Boolean.FALSE);
-            
+
             serviceReference = bundleContext.getServiceReference(ChangeUserPassword.class);
             assertEquals(Boolean.FALSE, serviceReference.getProperty("alwaysAllowSelfChangePassword"));
             ChangeUserPassword changeUserPassword = bundleContext.getService(serviceReference);
             assertNotNull(changeUserPassword);
-            changeUserPassword.changePassword(user1Session, 
-                    user1.getID(), 
-                    "testPwd", 
-                    "testPwdChanged", 
-                    "testPwdChanged", 
+
+            // user can't do the operation
+            assertFalse("Should not be allowed to change the user password",
+                    privilegesInfo.canChangePassword(user1Session, user1.getID()));
+
+            changeUserPassword.changePassword(user1Session,
+                    user1.getID(),
+                    "testPwd",
+                    "testPwdChanged",
+                    "testPwdChanged",
                     new ArrayList<>());
             assertTrue(user1Session.hasPendingChanges());
             try {
@@ -244,12 +263,179 @@
                 // done with this.
                 bundleContext.ungetService(serviceReference);
             }
-            
+
             //put the original config back
             configuration.update(originalServiceProps);
-            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class, "alwaysAllowSelfChangePassword", 
+            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class, "alwaysAllowSelfChangePassword",
                     originalServiceProps == null ? null :originalServiceProps.get("alwaysAllowSelfChangePassword"));
         }
     }
 
+    /**
+     * test changing your own password without sending the old password is not allowed
+     */
+    @Test
+    public void changePasswordAsSelfWithoutOldPasswordFails() throws Exception {
+        org.osgi.service.cm.Configuration configuration = configAdmin.getConfiguration("org.apache.sling.jackrabbit.usermanager.impl.post.ChangeUserPasswordServlet", null);
+        Dictionary<String, Object> originalServiceProps = configuration.getProperties();
+        ServiceReference<ChangeUserPassword> serviceReference = null;
+        try {
+            // update the service configuration to ensure the option is enabled
+            Dictionary<String, Object> newServiceProps = replaceConfigProp(originalServiceProps, "alwaysAllowSelfChangePassword", Boolean.TRUE);
+            configuration.update(newServiceProps);
+            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class,
+                    "alwaysAllowSelfChangePassword", Boolean.TRUE);
+
+            serviceReference = bundleContext.getServiceReference(ChangeUserPassword.class);
+            assertEquals(Boolean.TRUE, serviceReference.getProperty("alwaysAllowSelfChangePassword"));
+            ChangeUserPassword changeUserPassword = bundleContext.getService(serviceReference);
+            assertNotNull(changeUserPassword);
+
+            // user can do the operation
+            assertTrue("Should be allowed to change the user password",
+                    privilegesInfo.canChangePassword(user1Session, user1.getID()));
+
+            // no oldPassword submitted
+            try {
+                changeUserPassword.changePassword(user1Session,
+                        user1.getID(),
+                        null,
+                        "testPwdChanged",
+                        "testPwdChanged",
+                        new ArrayList<>());
+                fail("Expected a RepositoryException when changing user passsword.");
+            } catch (RepositoryException e) {
+                assertEquals("Old Password was not submitted", e.getMessage());
+                user1Session.refresh(false);
+            }
+
+            // empty oldPassword submitted
+            try {
+                changeUserPassword.changePassword(user1Session,
+                        user1.getID(),
+                        "",
+                        "testPwdChanged2",
+                        "testPwdChanged2",
+                        new ArrayList<>());
+                fail("Expected a RepositoryException when changing user passsword.");
+            } catch (RepositoryException e) {
+                assertEquals("Old Password was not submitted", e.getMessage());
+                user1Session.refresh(false);
+            }
+
+        } finally {
+            if (serviceReference != null) {
+                // done with this.
+                bundleContext.ungetService(serviceReference);
+            }
+
+            //put the original config back
+            configuration.update(originalServiceProps);
+            new WaitForServiceUpdated(5000, 100, bundleContext, ChangeUserPassword.class, "alwaysAllowSelfChangePassword",
+                    originalServiceProps == null ? null :originalServiceProps.get("alwaysAllowSelfChangePassword"));
+        }
+    }
+
+    /**
+     * test changing a user's password without sending the old password is allowed if the current
+     * user is a member of the UserAdmin group.
+     */
+    @Test
+    public void changePasswordAsUserAdminMemberWithoutOldPassword() throws Exception {
+        User user2 = null;
+        ServiceReference<ChangeUserPassword> serviceReference = null;
+        try {
+            // create a second user to attempt the change password on
+            user2 = createUser.createUser(adminSession, createUniqueName("user"), "testPwd", "testPwd",
+                    Collections.emptyMap(), new ArrayList<Modification>());
+            if (adminSession.hasPendingChanges()) {
+                adminSession.save();
+            }
+
+            // figure out what the user admin group name has been configured as
+            serviceReference = bundleContext.getServiceReference(ChangeUserPassword.class);
+            String userAdminGroup = (String)serviceReference.getProperty("user.admin.group.name");
+            if (userAdminGroup == null || userAdminGroup.isEmpty()) {
+                userAdminGroup = "UserAdmin"; // fallback to the default
+            }
+
+            // add user1 to the UserAdmin group
+            createGroup.createGroup(adminSession, userAdminGroup,
+                    Collections.singletonMap(":member", user1.getID()), new ArrayList<>());
+            if (adminSession.hasPendingChanges()) {
+                adminSession.save();
+            }
+
+            //make sure the UserAdmin group has the expected privileges granted
+            Map<String, String> privileges = new HashMap<>();
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_READ_ACCESS_CONTROL), "granted");
+            privileges.put(String.format("privilege@%s", Privilege.JCR_MODIFY_ACCESS_CONTROL), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_WRITE), "granted");
+            privileges.put(String.format("privilege@%s", PrivilegeConstants.REP_USER_MANAGEMENT), "granted");
+            modifyAce.modifyAce(adminSession, user2.getPath(), userAdminGroup,
+                    privileges,
+                    "first");
+
+
+            // create a fresh session so changes from the adminSession are picked up
+            user1Session.logout();
+            user1Session = repository.login(new SimpleCredentials(user1.getID(), "testPwd".toCharArray()));
+            assertNotNull("Expected user1Session to not be null", user1Session);
+
+            ChangeUserPassword changeUserPassword = bundleContext.getService(serviceReference);
+            assertNotNull(changeUserPassword);
+
+            // user can do the operation
+            assertTrue("Should be allowed to change the user password",
+                    privilegesInfo.canChangePassword(user1Session, user2.getID()));
+
+            // no oldPassword submitted
+            try {
+                changeUserPassword.changePassword(user1Session,
+                        user2.getID(),
+                        null,
+                        "testPwdChanged",
+                        "testPwdChanged",
+                        new ArrayList<>());
+            } catch (RepositoryException e) {
+                fail("Did not expect a RepositoryException when changing user passsword.");
+            }
+            try {
+                user1Session.save();
+            } catch (AccessDeniedException e) {
+                logger.error("Did not expect AccessDeniedException when changing user passsword: " + e.getMessage(), e);
+                fail("Did not expect AccessDeniedException when changing user passsword: " + e.getMessage());
+            }
+
+            // empty oldPassword submitted
+            try {
+                changeUserPassword.changePassword(user1Session,
+                        user2.getID(),
+                        "",
+                        "testPwdChanged2",
+                        "testPwdChanged2",
+                        new ArrayList<>());
+            } catch (RepositoryException e) {
+                fail("Did not expect a RepositoryException when changing user passsword.");
+            }
+            try {
+                user1Session.save();
+            } catch (AccessDeniedException e) {
+                logger.error("Did not expect AccessDeniedException when changing user passsword: " + e.getMessage(), e);
+                fail("Did not expect AccessDeniedException when changing user passsword: " + e.getMessage());
+            }
+
+        } finally {
+            if (user2 != null) {
+                deleteUser.deleteUser(adminSession, user2.getID(), new ArrayList<>());
+            }
+
+            if (serviceReference != null) {
+                // done with this.
+                bundleContext.ungetService(serviceReference);
+            }
+        }
+    }
+
 }