You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by en...@apache.org on 2020/10/10 19:41:30 UTC

[sling-org-apache-sling-jcr-jackrabbit-usermanager] branch master updated: SLING-9807 AuthorizablePrivilegesInfo is checking for too may privileges for some of the operations

This is an automated email from the ASF dual-hosted git repository.

enorman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-jackrabbit-usermanager.git


The following commit(s) were added to refs/heads/master by this push:
     new 31a8a30  SLING-9807 AuthorizablePrivilegesInfo is checking for too may privileges for some of the operations
31a8a30 is described below

commit 31a8a306f834945d9f079b5671a01b6581b8526a
Author: Eric Norman <en...@apache.org>
AuthorDate: Sat Oct 10 12:41:18 2020 -0700

    SLING-9807 AuthorizablePrivilegesInfo is checking for too may privileges
    for some of the operations
---
 pom.xml                                            | 104 +++
 .../usermanager/AuthorizablePrivilegesInfo.java    |  25 +
 .../impl/AuthorizablePrivilegesInfoImpl.java       | 251 +++++---
 .../sling/jackrabbit/usermanager/package-info.java |   2 +-
 .../it/AuthorizablePrivilegesInfoIT.java           | 711 +++++++++++++++++++++
 .../usermanager/it/UserManagerTestSupport.java     |  76 +++
 6 files changed, 1063 insertions(+), 106 deletions(-)

diff --git a/pom.xml b/pom.xml
index 3d87292..6b2c905 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,7 @@
 
     <properties>
         <project.build.outputTimestamp>2020-07-27T15:10:15Z</project.build.outputTimestamp>
+        <org.ops4j.pax.exam.version>4.13.3</org.ops4j.pax.exam.version>
     </properties>
 
     <scm>
@@ -66,6 +67,32 @@
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-maven-plugin</artifactId>
             </plugin>
+
+            <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-failsafe-plugin</artifactId>
+              <executions>
+                <execution>
+                  <goals>
+                    <goal>integration-test</goal>
+                    <goal>verify</goal>
+                  </goals>
+                </execution>
+              </executions>
+              <configuration>
+                <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                <systemProperties>
+                  <property>
+                    <name>bundle.filename</name>
+                    <value>${basedir}/target/${project.build.finalName}.jar</value>
+                  </property>
+                </systemProperties>
+              </configuration>
+            </plugin>
+            <plugin>
+              <groupId>org.apache.servicemix.tooling</groupId>
+              <artifactId>depends-maven-plugin</artifactId>
+            </plugin>
         </plugins>
     </build>
 
@@ -147,5 +174,82 @@
             <version>1.8.8</version>
             <optional>true</optional>
         </dependency>
+
+
+        <!-- Sling Testing PaxExam -->
+        <dependency>
+          <groupId>org.apache.sling</groupId>
+          <artifactId>org.apache.sling.testing.paxexam</artifactId>
+          <version>3.1.0</version>
+          <scope>test</scope>
+        </dependency>
+
+        <!-- an OSGi framework -->
+        <dependency>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>org.apache.felix.framework</artifactId>
+          <version>6.0.3</version>
+          <scope>test</scope>
+        </dependency>
+
+        <!-- JUnit -->
+        <dependency>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+          <scope>test</scope>
+        </dependency>
+
+        <!-- Pax Exam -->
+        <dependency>
+          <groupId>org.ops4j.pax.exam</groupId>
+          <artifactId>pax-exam</artifactId>
+          <version>${org.ops4j.pax.exam.version}</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.ops4j.pax.exam</groupId>
+          <artifactId>pax-exam-cm</artifactId>
+          <version>${org.ops4j.pax.exam.version}</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.ops4j.pax.exam</groupId>
+          <artifactId>pax-exam-container-forked</artifactId>
+          <version>${org.ops4j.pax.exam.version}</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.ops4j.pax.exam</groupId>
+          <artifactId>pax-exam-junit4</artifactId>
+          <version>${org.ops4j.pax.exam.version}</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.ops4j.pax.exam</groupId>
+          <artifactId>pax-exam-link-mvn</artifactId>
+          <version>${org.ops4j.pax.exam.version}</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>javax.inject</groupId>
+          <artifactId>javax.inject</artifactId>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.osgi</groupId>
+          <artifactId>osgi.cmpn</artifactId>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-simple</artifactId>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.sling</groupId>
+          <artifactId>org.apache.sling.jcr.jackrabbit.accessmanager</artifactId>
+          <version>3.0.6</version>
+          <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
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 c500dd8..af86136 100644
--- a/src/main/java/org/apache/sling/jackrabbit/usermanager/AuthorizablePrivilegesInfo.java
+++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/AuthorizablePrivilegesInfo.java
@@ -21,6 +21,16 @@ import javax.jcr.Session;
 public interface AuthorizablePrivilegesInfo {
 
     /**
+     * An enumeration of the possible types of property changes
+     */
+    public enum PropertyUpdateTypes {
+        addProperty,
+        addNestedProperty,
+        alterProperty,
+        removeProperty
+    }
+
+    /**
      * Checks whether the current user has been granted privileges
      * to add a new user.
      *  
@@ -41,6 +51,7 @@ public interface AuthorizablePrivilegesInfo {
     /**
      * Checks whether the current user has been granted privileges
      * to update the properties of the specified user or group.
+     * Equivalent of: #canUpdateProperties(Session, String, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty, PropertyUpdateTypes.alterProperty, PropertyUpdateTypes.removeProperty)
      *  
      * @param jcrSession the JCR session of the current user
      * @param principalId the user or group id to check
@@ -51,6 +62,20 @@ public interface AuthorizablePrivilegesInfo {
 
     /**
      * Checks whether the current user has been granted privileges
+     * to update the properties of the specified user or group.
+     *
+     * @param jcrSession the JCR session of the current user
+     * @param principalId the user or group id to check
+     * @param propertyUpdateTypes specify the types of property updates that may be supplied. See: {@link PropertyUpdateTypes}
+     * @return true if the current user has the privileges, false otherwise
+     */
+    default boolean canUpdateProperties(Session jcrSession,
+            String principalId, PropertyUpdateTypes ... propertyUpdateTypes) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Checks whether the current user has been granted privileges
      * to remove the specified user or group.
      *  
      * @param jcrSession the JCR session of the current user
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 c82208a..e7da94c 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
@@ -19,7 +19,9 @@ package org.apache.sling.jackrabbit.usermanager.impl;
 import java.io.UnsupportedEncodingException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -59,8 +61,8 @@ import org.slf4j.LoggerFactory;
  */
 @Component(service=AuthorizablePrivilegesInfo.class,
     property={
-    		AuthorizablePrivilegesInfoImpl.PAR_USER_ADMIN_GROUP_NAME + "=" + AuthorizablePrivilegesInfoImpl.DEFAULT_USER_ADMIN_GROUP_NAME,
-    		AuthorizablePrivilegesInfoImpl.PAR_GROUP_ADMIN_GROUP_NAME + "=" + AuthorizablePrivilegesInfoImpl.DEFAULT_GROUP_ADMIN_GROUP_NAME		
+            AuthorizablePrivilegesInfoImpl.PAR_USER_ADMIN_GROUP_NAME + "=" + AuthorizablePrivilegesInfoImpl.DEFAULT_USER_ADMIN_GROUP_NAME,
+            AuthorizablePrivilegesInfoImpl.PAR_GROUP_ADMIN_GROUP_NAME + "=" + AuthorizablePrivilegesInfoImpl.DEFAULT_GROUP_ADMIN_GROUP_NAME
     })
 public class AuthorizablePrivilegesInfoImpl implements AuthorizablePrivilegesInfo {
 
@@ -99,186 +101,225 @@ public class AuthorizablePrivilegesInfoImpl implements AuthorizablePrivilegesInf
     
     @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
     private void bindUserConfiguration(UserConfiguration userConfig, Map<String, Object> properties) {
-    	usersPath = (String)properties.get(UserConstants.PARAM_USER_PATH);
-    	groupsPath = (String)properties.get(UserConstants.PARAM_GROUP_PATH);
+        usersPath = (String)properties.get(UserConstants.PARAM_USER_PATH);
+        groupsPath = (String)properties.get(UserConstants.PARAM_GROUP_PATH);
     }
     @SuppressWarnings("unused")
-	private void unbindUserConfiguration(UserConfiguration userConfig, Map<String, Object> properties) {
-    	usersPath = null;
-    	groupsPath = null;
+    private void unbindUserConfiguration(UserConfiguration userConfig, Map<String, Object> properties) {
+        usersPath = null;
+        groupsPath = null;
     }
 
     @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
     private void bindCreateUser(CreateUser createUser, Map<String, Object> properties) {
-    	selfRegistrationEnabled = Boolean.TRUE.equals(properties.get("self.registration.enabled"));
+        selfRegistrationEnabled = Boolean.TRUE.equals(properties.get("self.registration.enabled"));
     }
     @SuppressWarnings("unused")
-	private void unbindCreateUser(CreateUser createUser, Map<String, Object> properties) {
-    	selfRegistrationEnabled = false;
+    private void unbindCreateUser(CreateUser createUser, Map<String, Object> properties) {
+        selfRegistrationEnabled = false;
     }
 
     /* (non-Javadoc)
      * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canAddGroup(javax.jcr.Session)
      */
     public boolean canAddGroup(Session jcrSession) {
+        boolean hasRights = false;
         try {
             UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
             Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
 
-            if (currentUser != null) {
-                if (((User)currentUser).isAdmin()) {
-                    return true; //admin user has full control
+            if (currentUser instanceof User && ((User)currentUser).isAdmin()) {
+                hasRights = true; //admin user has full control
+            } else {
+                if (groupsPath != null) {
+                    //check if the non-admin user has sufficient rights on the home folder
+                    AccessControlManager acm = jcrSession.getAccessControlManager();
+                    hasRights = acm.hasPrivileges(groupsPath, new Privilege[] {
+                                            acm.privilegeFromName(Privilege.JCR_READ),
+                                            acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL),
+                                            acm.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL),
+                                            acm.privilegeFromName(PrivilegeConstants.REP_WRITE),
+                                            acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
+                                    });
                 }
             }
-            
-            if (groupsPath != null) {
-                //check if the non-admin user has sufficient rights on the home folder
-                AccessControlManager acm = jcrSession.getAccessControlManager();
-                boolean hasRights = acm.hasPrivileges(groupsPath, new Privilege[] {
-        				        		acm.privilegeFromName(Privilege.JCR_READ),
-        				        		acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_WRITE),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
-        				        });
-                return hasRights;
-            }
         } catch (RepositoryException e) {
             log.warn("Failed to determine if {} can add a new group", jcrSession.getUserID());
         }
-        return false;
+        return hasRights;
     }
 
     /* (non-Javadoc)
      * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canAddUser(javax.jcr.Session)
      */
     public boolean canAddUser(Session jcrSession) {
+        boolean hasRights = false;
         try {
             //if self-registration is enabled, then anyone can create a user
-        	if (selfRegistrationEnabled) {
-        		return true;
-        	}
-
-            UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
-            Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
-            if (currentUser != null) {
-                if (((User)currentUser).isAdmin()) {
-                    return true; //admin user has full control
+            if (selfRegistrationEnabled) {
+                hasRights = true;
+            } else {
+                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 {
+                    if (usersPath != null) {
+                        //check if the non-admin user has sufficient rights on the home folder
+                        AccessControlManager acm = jcrSession.getAccessControlManager();
+                        hasRights = acm.hasPrivileges(usersPath, new Privilege[] {
+                                                acm.privilegeFromName(Privilege.JCR_READ),
+                                                acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL),
+                                                acm.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL),
+                                                acm.privilegeFromName(PrivilegeConstants.REP_WRITE),
+                                                acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
+                                        });
+                    }
                 }
             }
-            
-            if (usersPath != null) {
-                //check if the non-admin user has sufficient rights on the home folder
-                AccessControlManager acm = jcrSession.getAccessControlManager();
-                boolean hasRights = acm.hasPrivileges(usersPath, new Privilege[] {
-        				        		acm.privilegeFromName(Privilege.JCR_READ),
-        				        		acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_WRITE),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
-        				        });
-                return hasRights;
-            }
         } catch (RepositoryException e) {
             log.warn("Failed to determine if {} can add a new user", jcrSession.getUserID());
         }
-        return false;
+        return hasRights;
     }
 
     /* (non-Javadoc)
      * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canRemove(javax.jcr.Session, java.lang.String)
      */
     public boolean canRemove(Session jcrSession, String principalId) {
+        boolean hasRights = false;
         try {
             UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
             Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
 
-            if (((User)currentUser).isAdmin()) {
-                return true; //admin user has full control
-            }
-            
-            if (usersPath != null) {
-                //check if the non-admin user has sufficient rights on the home folder
-                AccessControlManager acm = jcrSession.getAccessControlManager();
-                boolean hasRights = acm.hasPrivileges("/home/users", new Privilege[] {
-        				        		acm.privilegeFromName(Privilege.JCR_READ),
-        				        		acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_WRITE),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
-        				        });
-                return hasRights;
+            if (currentUser instanceof User && ((User)currentUser).isAdmin()) {
+                hasRights = true; //admin user has full control
+            } else {
+                Authorizable authorizable = userManager.getAuthorizable(principalId);
+                if (authorizable == null) {
+                    log.debug("Failed to find authorizable: {}", principalId);
+                } 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();
+                        hasRights = acm.hasPrivileges(path, new Privilege[] {
+                                                acm.privilegeFromName(Privilege.JCR_READ),
+                                                acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
+                                        });
+                    }
+                }
             }
         } catch (RepositoryException e) {
             log.warn("Failed to determine if {} can remove authorizable {}", jcrSession.getUserID(), principalId);
         }
-        return false;
+        return hasRights;
     }
 
     /* (non-Javadoc)
      * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canUpdateGroupMembers(javax.jcr.Session, java.lang.String)
      */
     public boolean canUpdateGroupMembers(Session jcrSession, String groupId) {
+        boolean hasRights = false;
         try {
             UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
             Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
 
-            if (((User)currentUser).isAdmin()) {
-                return true; //admin user has full control
-            }
-            
-            if (groupsPath != null) {
-                //check if the non-admin user has sufficient rights on the home folder
-                AccessControlManager acm = jcrSession.getAccessControlManager();
-                boolean hasRights = acm.hasPrivileges(groupsPath, new Privilege[] {
-        				        		acm.privilegeFromName(Privilege.JCR_READ),
-        				        		acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_WRITE),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
-        				        });
-                return hasRights;
+            if (currentUser instanceof User && ((User)currentUser).isAdmin()) {
+                hasRights = true; //admin user has full control
+            } else {
+                Authorizable authorizable = userManager.getAuthorizable(groupId);
+                if (authorizable == null) {
+                    log.debug("Failed to find group: {}", groupId);
+                } 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();
+                        hasRights = acm.hasPrivileges(path, new Privilege[] {
+                                                acm.privilegeFromName(Privilege.JCR_READ),
+                                                acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
+                                        });
+                    }
+                }
             }
         } catch (RepositoryException e) {
             log.warn("Failed to determine if {} can remove authorizable {}", jcrSession.getUserID(), groupId);
         }
-        return false;
+        return hasRights;
     }
 
     /* (non-Javadoc)
      * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canUpdateProperties(javax.jcr.Session, java.lang.String)
      */
     public boolean canUpdateProperties(Session jcrSession, String principalId) {
+        return canUpdateProperties(jcrSession, principalId,
+                PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty,
+                PropertyUpdateTypes.alterProperty, PropertyUpdateTypes.removeProperty);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo#canUpdateProperties(javax.jcr.Session, java.lang.String, org.apache.sling.jackrabbit.usermanager.AuthorizablePrivilegesInfo.PropertyUpdateTypes[])
+     */
+    @Override
+    public boolean canUpdateProperties(Session jcrSession, String principalId,
+            PropertyUpdateTypes... propertyUpdateTypes) {
+        boolean hasRights = false;
         try {
             if (jcrSession.getUserID().equals(principalId)) {
                 //user is allowed to update it's own properties
-                return true;
-            }
-            
-            UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
-            Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
+                hasRights = true;
+            } else {
+                UserManager userManager = AccessControlUtil.getUserManager(jcrSession);
+                Authorizable currentUser = userManager.getAuthorizable(jcrSession.getUserID());
 
-            if (((User)currentUser).isAdmin()) {
-                return true; //admin user has full control
-            }
-            
-            String path = currentUser.isGroup() ? groupsPath : usersPath;
-            if (path != null) {
-                //check if the non-admin user has sufficient rights on the home folder
-                AccessControlManager acm = jcrSession.getAccessControlManager();
-                boolean hasRights = acm.hasPrivileges(path, new Privilege[] {
-        				        		acm.privilegeFromName(Privilege.JCR_READ),
-        				        		acm.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_WRITE),
-        				        		acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
-        				        });
-                return hasRights;
+                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);
+                } 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));
+                        if (propertyUpdateTypes != null) {
+                            for (PropertyUpdateTypes updateType : propertyUpdateTypes) {
+                                switch (updateType) {
+                                case addNestedProperty:
+                                    requiredPrivileges.add(acm.privilegeFromName(PrivilegeConstants.REP_ADD_PROPERTIES));
+                                    requiredPrivileges.add(acm.privilegeFromName(Privilege.JCR_ADD_CHILD_NODES));
+                                    break;
+                                case addProperty:
+                                    requiredPrivileges.add(acm.privilegeFromName(PrivilegeConstants.REP_ADD_PROPERTIES));
+                                    break;
+                                case alterProperty:
+                                    requiredPrivileges.add(acm.privilegeFromName(PrivilegeConstants.REP_ALTER_PROPERTIES));
+                                    break;
+                                case removeProperty:
+                                    requiredPrivileges.add(acm.privilegeFromName(PrivilegeConstants.REP_REMOVE_PROPERTIES));
+                                    break;
+                                default:
+                                    log.warn("Unexpected property update type: {}", updateType);
+                                    break;
+                                }
+                            }
+                        }
+
+                        hasRights = acm.hasPrivileges(path, requiredPrivileges.toArray(new Privilege[requiredPrivileges.size()]));
+                    }
+                }
             }
         } catch (RepositoryException e) {
-            log.warn("Failed to determine if {} can remove authorizable {}", jcrSession.getUserID(), principalId);
+            log.warn("Failed to determine if {} can update properties of authorizable {}", jcrSession.getUserID(), principalId);
         }
-        return false;
+        return hasRights;
     }
 
 
diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/package-info.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/package-info.java
index 02521b0..c236241 100644
--- a/src/main/java/org/apache/sling/jackrabbit/usermanager/package-info.java
+++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@org.osgi.annotation.versioning.Version("2.2.1")
+@org.osgi.annotation.versioning.Version("2.3.0")
 package org.apache.sling.jackrabbit.usermanager;
 
 
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
new file mode 100644
index 0000000..7dc8196
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/AuthorizablePrivilegesInfoIT.java
@@ -0,0 +1,711 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.jcr.jackrabbit.usermanager.it;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.inject.Inject;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+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.CreateGroup;
+import org.apache.sling.jackrabbit.usermanager.CreateUser;
+import org.apache.sling.jackrabbit.usermanager.DeleteGroup;
+import org.apache.sling.jackrabbit.usermanager.DeleteUser;
+import org.apache.sling.jackrabbit.usermanager.UpdateGroup;
+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.ModifyAce;
+import org.apache.sling.servlets.post.Modification;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Basic test of AuthorizablePrivilegesInfo component
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class AuthorizablePrivilegesInfoIT extends UserManagerTestSupport {
+	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 CreateGroup createGroup;
+
+	@Inject
+	private UpdateUser updateUser;
+
+	@Inject
+	private UpdateGroup updateGroup;
+
+	@Inject
+	private DeleteUser deleteUser;
+
+	@Inject
+	private DeleteGroup deleteGroup;
+
+	@Inject
+	private ModifyAce modifyAce;
+
+	@Rule
+	public TestName testName = new TestName();
+
+    protected Session adminSession;
+    protected User user1;
+    protected Session user1Session;
+
+	@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);
+
+		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);
+    }
+
+    @After
+    public void teardown() {
+		if (user1 != null) {
+			try {
+				adminSession.refresh(false);
+				deleteUser.deleteUser(adminSession, user1.getID(), new ArrayList<>());
+			} catch (RepositoryException e) {
+				logger.warn("Failed to delete user: " + e.getMessage(), e);			
+			}
+		}
+    	user1Session.logout();
+        adminSession.logout();
+    }
+
+	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
+     * to add a new user.  Equivalent of: #canAddUser(Session, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty)
+     */
+    @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));
+
+			// 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<>());
+			}
+		}
+    }
+
+    /**
+     * Checks whether the current user has been granted privileges
+     * to add a new group.  Equivalent of: #canAddGroup(Session, PropertyUpdateTypes.addProperty, PropertyUpdateTypes.addNestedProperty)
+     */
+    @Test
+    public void canAddGroup() throws Exception {
+		assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
+		
+		workaroundMissingGroupsPath();
+
+		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));
+	}
+
+    /**
+     * Checks whether the current user has been granted privileges
+     * to update the properties of the specified user or group.
+     */
+    @Test
+    public void canUpdateProperties() throws Exception {
+		assertNotNull("Expected privilegesInfo to not be null", privilegesInfo);
+
+		workaroundMissingGroupsPath();
+
+		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);
+
+			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() };
+
+			// 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));
+			}
+
+			// + 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");
+			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();
+				fail("Expected AccessDenied exception when adding nested property");
+			} catch (RepositoryException 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");
+			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();
+				fail("Expected AccessDenied exception when adding nested property");
+			} catch (RepositoryException 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<>());
+			}
+		}
+    }
+
+    /**
+     * Checks whether the current user has been granted privileges
+     * to remove the specified user or group.
+     */
+    @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);
+
+			group1 = createGroup.createGroup(adminSession, createUniqueName("group"), 
+					Collections.singletonMap("prop1", "value1"), new ArrayList<Modification>());
+			assertNotNull("Expected group1 to not be null", group1);
+
+			// 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<>());
+			}
+		}
+    }
+
+    /**
+     * Checks whether the current user has been granted privileges
+     * to update the membership of the specified group.
+     */
+    @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);
+
+			// 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<>());
+			}
+		}
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/UserManagerTestSupport.java b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/UserManagerTestSupport.java
new file mode 100644
index 0000000..fed4cb9
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/UserManagerTestSupport.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.jcr.jackrabbit.usermanager.it;
+
+import static org.apache.sling.testing.paxexam.SlingOptions.sling;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
+
+import org.apache.sling.testing.paxexam.SlingOptions;
+import org.apache.sling.testing.paxexam.TestSupport;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.options.ModifiableCompositeOption;
+import org.ops4j.pax.exam.options.extra.VMOption;
+
+/**
+ * Base class for UserManager related paxexam tests
+ */
+public abstract class UserManagerTestSupport extends TestSupport {
+
+    public ModifiableCompositeOption baseConfiguration() {
+        final Option usermanager = mavenBundle()
+        		.groupId("org.apache.sling")
+        		.artifactId("org.apache.sling.jcr.jackrabbit.usermanager")
+        		.version(SlingOptions.versionResolver.getVersion("org.apache.sling", "org.apache.sling.jcr.jackrabbit.usermanager"));
+        return composite(
+            super.baseConfiguration(),
+            optionalRemoteDebug(),
+            quickstart(),
+            sling(),
+            // Sling JCR UserManager
+            testBundle("bundle.filename"),
+            mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.jcr.jackrabbit.accessmanager").versionAsInProject(),
+            junitBundles()
+        ).remove(
+            usermanager
+        );
+    }
+
+    /**
+     * Optionally configure remote debugging on the port supplied by the "debugPort"
+     * system property.
+     */
+    protected ModifiableCompositeOption optionalRemoteDebug() {
+    	VMOption option = null;
+    	String property = System.getProperty("debugPort");
+		if (property != null) {			
+    		option = vmOption(String.format("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%s", property));
+    	}
+    	return composite(option);
+    }
+    protected ModifiableCompositeOption quickstart() {
+        final int httpPort = findFreePort();
+        final String workingDirectory = workingDirectory();
+        return slingQuickstartOakTar(workingDirectory, httpPort);
+    }
+
+}