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 <[email protected]>
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.
  */
 
[email protected]("2.2.1")
[email protected]("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);
+    }
+
+}

Reply via email to