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 0b9fec1  SLING-9808 Add configuration option to always allow users to 
change their own password
0b9fec1 is described below

commit 0b9fec191d3990fa025092b59aeec8029e8b1f6d
Author: Eric Norman <[email protected]>
AuthorDate: Sat Oct 10 16:12:01 2020 -0700

    SLING-9808 Add configuration option to always allow users to change
    their own password
---
 .../impl/post/ChangeUserPasswordServlet.java       | 121 +++++++---
 .../sling/jcr/jackrabbit/usermanager/it/Retry.java |  69 ++++++
 .../usermanager/it/UserManagerTestSupport.java     |  72 +++++-
 .../usermanager/it/post/ChangeUserPasswordIT.java  | 251 +++++++++++++++++++++
 4 files changed, 473 insertions(+), 40 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/post/ChangeUserPasswordServlet.java
 
b/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/post/ChangeUserPasswordServlet.java
index 0e4b38b..639e4a8 100644
--- 
a/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/post/ChangeUserPasswordServlet.java
+++ 
b/src/main/java/org/apache/sling/jackrabbit/usermanager/impl/post/ChangeUserPasswordServlet.java
@@ -21,18 +21,22 @@ import java.util.Map;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
 import javax.servlet.Servlet;
 
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceNotFoundException;
 import org.apache.sling.commons.osgi.OsgiUtil;
 import org.apache.sling.jackrabbit.usermanager.ChangeUserPassword;
 import 
org.apache.sling.jackrabbit.usermanager.impl.resource.AuthorizableResourceProvider;
+import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.base.util.AccessControlUtil;
 import org.apache.sling.servlets.post.Modification;
 import org.apache.sling.servlets.post.PostResponse;
@@ -43,6 +47,9 @@ import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -86,18 +93,31 @@ import org.slf4j.LoggerFactory;
 
 @Component(service = {Servlet.class, ChangeUserPassword.class},
            property = {
-                          "sling.servlet.resourceTypes=sling/user",
-                          "sling.servlet.methods=POST",
-                          "sling.servlet.selectors=changePassword",
-                          AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=EEE MMM dd yyyy HH:mm:ss 'GMT'Z", 
-                          AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=yyyy-MM-dd'T'HH:mm:ss.SSSZ", 
-                          AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=yyyy-MM-dd'T'HH:mm:ss", 
-                          AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=yyyy-MM-dd", 
-                          AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=dd.MM.yyyy HH:mm:ss", 
-                          AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=dd.MM.yyyy",
-                          ChangeUserPasswordServlet.PAR_USER_ADMIN_GROUP_NAME 
+ "=" + ChangeUserPasswordServlet.DEFAULT_USER_ADMIN_GROUP_NAME
+                   "sling.servlet.resourceTypes=sling/user",
+                   "sling.servlet.methods=POST",
+                   "sling.servlet.selectors=changePassword",
+                   AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + "=EEE 
MMM dd yyyy HH:mm:ss 'GMT'Z",
+                   AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=yyyy-MM-dd'T'HH:mm:ss.SSSZ",
+                   AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=yyyy-MM-dd'T'HH:mm:ss",
+                   AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=yyyy-MM-dd",
+                   AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=dd.MM.yyyy HH:mm:ss",
+                   AbstractAuthorizablePostServlet.PROP_DATE_FORMAT + 
"=dd.MM.yyyy"
            })
+@Designate(ocd=ChangeUserPasswordServlet.Config.class)
 public class ChangeUserPasswordServlet extends AbstractAuthorizablePostServlet 
implements ChangeUserPassword {
+
+    @ObjectClassDefinition(name ="Apache Sling Change User Password")
+    public @interface Config {
+
+        @AttributeDefinition(name = "User Admin Group Name",
+                description = "Specifies the name of the group whose members 
are allowed to reset the password of another user.")
+        String user_admin_group_name() default DEFAULT_USER_ADMIN_GROUP_NAME;
+
+        @AttributeDefinition(name = "Always Allow Self Password Change",
+                description = "Specifies whether a user is allowed to change 
their own password even if they haven't been granted the rep:userManagement 
privilege.")
+        boolean alwaysAllowSelfChangePassword() default false;
+    }
+
     private static final long serialVersionUID = 1923614318474654502L;
 
     /**
@@ -121,7 +141,13 @@ public class ChangeUserPasswordServlet extends 
AbstractAuthorizablePostServlet i
 
     private String userAdminGroupName = DEFAULT_USER_ADMIN_GROUP_NAME;
 
-    // ---------- SCR integration ---------------------------------------------
+    private boolean alwaysAllowSelfChangePassword = true;
+
+    /**
+     * The JCR Repository we access to resolve resources
+     */
+    @Reference
+    private SlingRepository repository;
 
     /**
      * Activates this component.
@@ -133,6 +159,8 @@ public class ChangeUserPasswordServlet extends 
AbstractAuthorizablePostServlet i
     protected void activate(final Map<String, Object> props) {
         super.activate(props);
 
+        alwaysAllowSelfChangePassword = 
OsgiUtil.toBoolean(props.get("alwaysAllowSelfChangePassword"), false);
+
         this.userAdminGroupName = 
OsgiUtil.toString(props.get(PAR_USER_ADMIN_GROUP_NAME),
                 DEFAULT_USER_ADMIN_GROUP_NAME);
         log.debug("User Admin Group Name {}", this.userAdminGroupName);
@@ -147,25 +175,25 @@ public class ChangeUserPasswordServlet extends 
AbstractAuthorizablePostServlet i
     /**
      * Overridden since the @Reference annotation is not inherited from the 
super method
      *  
-        * @see 
org.apache.sling.jackrabbit.usermanager.impl.post.AbstractPostServlet#bindPostResponseCreator(org.apache.sling.servlets.post.PostResponseCreator,
 java.util.Map)
-        */
-       @Override
+     * @see 
org.apache.sling.jackrabbit.usermanager.impl.post.AbstractPostServlet#bindPostResponseCreator(org.apache.sling.servlets.post.PostResponseCreator,
 java.util.Map)
+     */
+    @Override
     @Reference(service = PostResponseCreator.class,
-           cardinality = ReferenceCardinality.MULTIPLE,
-           policy = ReferencePolicy.DYNAMIC)
-       protected void bindPostResponseCreator(PostResponseCreator creator, 
Map<String, Object> properties) {
-               super.bindPostResponseCreator(creator, properties);
-       }
-       
-       /* (non-Javadoc)
-        * @see 
org.apache.sling.jackrabbit.usermanager.impl.post.AbstractPostServlet#unbindPostResponseCreator(org.apache.sling.servlets.post.PostResponseCreator,
 java.util.Map)
-        */
-       @Override
-       protected void unbindPostResponseCreator(PostResponseCreator creator, 
Map<String, Object> properties) {
-               super.unbindPostResponseCreator(creator, properties);
-       }
-
-       /*
+        cardinality = ReferenceCardinality.MULTIPLE,
+        policy = ReferencePolicy.DYNAMIC)
+    protected void bindPostResponseCreator(PostResponseCreator creator, 
Map<String, Object> properties) {
+        super.bindPostResponseCreator(creator, properties);
+    }
+
+    /* (non-Javadoc)
+     * @see 
org.apache.sling.jackrabbit.usermanager.impl.post.AbstractPostServlet#unbindPostResponseCreator(org.apache.sling.servlets.post.PostResponseCreator,
 java.util.Map)
+     */
+    @Override
+    protected void unbindPostResponseCreator(PostResponseCreator creator, 
Map<String, Object> properties) {
+        super.unbindPostResponseCreator(creator, properties);
+    }
+
+    /*
      * (non-Javadoc)
      * @see
      * 
org.apache.sling.jackrabbit.usermanager.post.AbstractAuthorizablePostServlet
@@ -174,7 +202,7 @@ public class ChangeUserPasswordServlet extends 
AbstractAuthorizablePostServlet i
      */
     @Override
     protected void handleOperation(SlingHttpServletRequest request,
-               PostResponse response, List<Modification> changes)
+            PostResponse response, List<Modification> changes)
             throws RepositoryException {
 
         Resource resource = request.getResource();
@@ -253,7 +281,38 @@ public class ChangeUserPasswordServlet extends 
AbstractAuthorizablePostServlet i
 
         if (oldPassword != null && oldPassword.length() > 0) {
             // verify old password
-            user.changePassword(newPassword, oldPassword);
+            if (alwaysAllowSelfChangePassword && 
jcrSession.getUserID().equals(name)) {
+                // first check if the current user has enough permissions to 
do this without
+                //   the aid of a service session
+                AccessControlManager acm = 
jcrSession.getAccessControlManager();
+                boolean hasRights = acm.hasPrivileges(authorizable.getPath(), 
new Privilege[] {
+                                        
acm.privilegeFromName(PrivilegeConstants.REP_USER_MANAGEMENT)
+                                });
+
+                if (hasRights) {
+                    // we are good to do this without an extra service session
+                    user.changePassword(newPassword, oldPassword);
+                } else {
+                    // the current user doesn't have enough permissions, so 
we'll need do
+                    //   do the work on their behalf as a service user
+                    Session svcSession = null;
+                    try {
+                        svcSession = repository.loginAdministrative(null);
+                        UserManager um = 
AccessControlUtil.getUserManager(svcSession);
+                        User user2 = (User) um.getAuthorizable(name);
+                        user2.changePassword(newPassword, oldPassword);
+                        if (svcSession.hasPendingChanges()) {
+                            svcSession.save();
+                        }
+                    } finally {
+                        if (svcSession != null) {
+                            svcSession.logout();
+                        }
+                    }
+                }
+            } else {
+                user.changePassword(newPassword, oldPassword);
+            }
         } else {
             user.changePassword(newPassword);
         }
diff --git 
a/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/Retry.java 
b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/Retry.java
new file mode 100644
index 0000000..f2454c3
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/Retry.java
@@ -0,0 +1,69 @@
+/*
+ * 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.fail;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Simple Retry loop for tests */
+public abstract class Retry {
+       private Logger logger = LoggerFactory.getLogger(getClass());
+    private long timeoutMsec;
+       private long nextIterationDelay;
+
+       public Retry(long timeoutMsec, long nextIterationDelay) throws 
InterruptedException {
+               this(timeoutMsec, nextIterationDelay, true);
+       }
+       
+       public Retry(long timeoutMsec, long nextIterationDelay, boolean 
autorun) throws InterruptedException {
+       this.timeoutMsec = timeoutMsec;
+       this.nextIterationDelay = nextIterationDelay;
+       if (autorun) {
+               run();
+       }
+    }
+
+       protected void run() throws InterruptedException {
+               final long timeout = System.currentTimeMillis() + timeoutMsec;
+        Throwable lastT = null;
+        while (System.currentTimeMillis() < timeout) {
+            try {
+                lastT = null;
+                exec();
+                break;
+            } catch(Throwable t) {
+               if (logger.isDebugEnabled()) {
+                       logger.warn("exec failed: " + t.getMessage(), t);       
        
+               } else {
+                       logger.warn("exec failed: " + t.getMessage());          
        
+               }
+                lastT = t;
+                Thread.sleep(nextIterationDelay);               
+            }
+        }
+
+        if (lastT != null) {
+            fail("Failed after " + timeoutMsec + " msec: " + lastT);
+        }
+       }
+
+    protected abstract void exec() throws Exception;
+}
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
index fed4cb9..11c1440 100644
--- 
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
@@ -20,27 +20,65 @@ 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 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 java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
 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;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 
 /**
  * Base class for UserManager related paxexam tests
  */
 public abstract class UserManagerTestSupport extends TestSupport {
 
+    /**
+     * Use after using ConfigurationAdmin to change the configuration of
+     * a service and you need to wait for the component to be re-activated
+     * with the new configuration.
+     */
+    public static final class WaitForServiceUpdated extends Retry {
+        private BundleContext bundleContext;
+        private String expectedKey;
+        private Object expectedValue;
+        private Class<?> serviceClass;
+
+        public WaitForServiceUpdated(long timeoutMsec, long 
nextIterationDelay, BundleContext bundleContext, 
+                Class<?> serviceClass, String expectedKey, Object 
expectedValue) throws InterruptedException {
+            super(timeoutMsec, nextIterationDelay, false);
+            this.bundleContext = bundleContext;
+            this.serviceClass = serviceClass;
+            this.expectedKey = expectedKey;
+            this.expectedValue = expectedValue;
+            run();
+        }
+
+        @Override
+        protected void exec() throws Exception {
+            ServiceReference<?> serviceReference = 
bundleContext.getServiceReference(serviceClass);
+            assertNotNull(serviceReference);
+            assertEquals(expectedValue, 
serviceReference.getProperty(expectedKey));
+        }
+    }
+
+
     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"));
+                .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(),
@@ -60,12 +98,12 @@ public abstract class UserManagerTestSupport extends 
TestSupport {
      * 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);
+        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();
@@ -73,4 +111,20 @@ public abstract class UserManagerTestSupport extends 
TestSupport {
         return slingQuickstartOakTar(workingDirectory, httpPort);
     }
 
+    protected Dictionary<String, Object> replaceConfigProp(Dictionary<String, 
Object> originalProps, String newPropKey, Object newPropValue) {
+        Hashtable<String, Object> newProps = new Hashtable<>();
+        if (originalProps != null) {
+            Enumeration<String> keys = originalProps.keys();
+            while (keys.hasMoreElements()) {
+                String key = keys.nextElement();
+                Object value = originalProps.get(key);
+                newProps.put(key, value);
+            }
+        }
+
+        newProps.put(newPropKey, newPropValue);
+        
+        return newProps;
+    }
+    
 }
diff --git 
a/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/post/ChangeUserPasswordIT.java
 
b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/post/ChangeUserPasswordIT.java
new file mode 100644
index 0000000..2b749bd
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/jcr/jackrabbit/usermanager/it/post/ChangeUserPasswordIT.java
@@ -0,0 +1,251 @@
+/*
+ * 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.post;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+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.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.inject.Inject;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+import org.apache.sling.jackrabbit.usermanager.ChangeUserPassword;
+import org.apache.sling.jackrabbit.usermanager.CreateUser;
+import org.apache.sling.jackrabbit.usermanager.DeleteUser;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.jackrabbit.accessmanager.DeleteAces;
+import org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce;
+import org.apache.sling.jcr.jackrabbit.usermanager.it.UserManagerTestSupport;
+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.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Basic test of ChangeUserPassword component
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class ChangeUserPasswordIT 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
+    protected ConfigurationAdmin configAdmin;
+    
+    @Inject
+    private CreateUser createUser;
+
+    @Inject
+    private ModifyAce modifyAce;
+
+    @Inject
+    private DeleteAces deleteAces;
+
+    @Inject
+    private DeleteUser deleteUser;
+
+    @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);
+        
+        //change the ACE for the user home folder to the minimum privileges
+        // and without rep:userManagement
+        deleteAces.deleteAces(adminSession, user1.getPath(), new String[] 
{user1.getID()});
+        Map<String, String> privileges = new HashMap<>();
+        privileges.put(String.format("privilege@%s", Privilege.JCR_READ), 
"granted");
+        privileges.put(String.format("privilege@%s", 
PrivilegeConstants.REP_ALTER_PROPERTIES), "granted");
+        modifyAce.modifyAce(adminSession, user1.getPath(), user1.getID(), 
+                privileges, 
+                "first");
+        if (adminSession.hasPendingChanges()) {
+               adminSession.save();
+        }
+    }
+
+    @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());
+    }
+
+    /**
+     * SLING-9808 test changing password when user doesn't have 
rep:userManagement privilege
+     */
+    @Test
+    public void changePasswordAsSelfGranted() throws Exception {
+        org.osgi.service.cm.Configuration configuration = 
configAdmin.getConfiguration("org.apache.sling.jackrabbit.usermanager.impl.post.ChangeUserPasswordServlet",
 null);
+        Dictionary<String, Object> originalServiceProps = 
configuration.getProperties();
+        ServiceReference<ChangeUserPassword> serviceReference = null;
+        try {
+            // update the service configuration to ensure the option is enabled
+            Dictionary<String, Object> newServiceProps = 
replaceConfigProp(originalServiceProps, "alwaysAllowSelfChangePassword", 
Boolean.TRUE);
+            configuration.update(newServiceProps);
+            new WaitForServiceUpdated(5000, 100, bundleContext, 
ChangeUserPassword.class, 
+                    "alwaysAllowSelfChangePassword", Boolean.TRUE);
+            
+            serviceReference = 
bundleContext.getServiceReference(ChangeUserPassword.class);
+            assertEquals(Boolean.TRUE, 
serviceReference.getProperty("alwaysAllowSelfChangePassword"));
+            ChangeUserPassword changeUserPassword = 
bundleContext.getService(serviceReference);
+            assertNotNull(changeUserPassword);
+            changeUserPassword.changePassword(user1Session, 
+                    user1.getID(), 
+                    "testPwd", 
+                    "testPwdChanged", 
+                    "testPwdChanged", 
+                    new ArrayList<>());
+            try {
+                user1Session.save();
+            } catch (AccessDeniedException e) {
+                logger.error("Did not expect AccessDeniedException when 
changing user passsword: " + e.getMessage(), e);
+                fail("Did not expect AccessDeniedException when changing user 
passsword: " + e.getMessage());
+            }
+        } finally {
+            if (serviceReference != null) {
+                // done with this.
+                bundleContext.ungetService(serviceReference);
+            }
+            
+            //put the original config back
+            configuration.update(originalServiceProps);
+            new WaitForServiceUpdated(5000, 100, bundleContext, 
ChangeUserPassword.class, "alwaysAllowSelfChangePassword", 
+                    originalServiceProps == null ? null 
:originalServiceProps.get("alwaysAllowSelfChangePassword"));
+        }
+    }
+
+    /**
+     * SLING-9808 test changing password when user doesn't have 
rep:userManagement privilege
+     */
+    @Test
+    public void changePasswordAsSelfDenied() throws Exception {
+        org.osgi.service.cm.Configuration configuration = 
configAdmin.getConfiguration("org.apache.sling.jackrabbit.usermanager.impl.post.ChangeUserPasswordServlet",
 null);
+        Dictionary<String, Object> originalServiceProps = 
configuration.getProperties();
+        ServiceReference<ChangeUserPassword> serviceReference = null;
+        try {
+            // update the service configuration to ensure the option is 
disabled
+            Dictionary<String, Object> newServiceProps = 
replaceConfigProp(originalServiceProps, "alwaysAllowSelfChangePassword", 
Boolean.FALSE);
+            configuration.update(newServiceProps);
+            new WaitForServiceUpdated(5000, 100, bundleContext, 
ChangeUserPassword.class, 
+                    "alwaysAllowSelfChangePassword", Boolean.FALSE);
+            
+            serviceReference = 
bundleContext.getServiceReference(ChangeUserPassword.class);
+            assertEquals(Boolean.FALSE, 
serviceReference.getProperty("alwaysAllowSelfChangePassword"));
+            ChangeUserPassword changeUserPassword = 
bundleContext.getService(serviceReference);
+            assertNotNull(changeUserPassword);
+            changeUserPassword.changePassword(user1Session, 
+                    user1.getID(), 
+                    "testPwd", 
+                    "testPwdChanged", 
+                    "testPwdChanged", 
+                    new ArrayList<>());
+            assertTrue(user1Session.hasPendingChanges());
+            try {
+                user1Session.save();
+                fail("Expected an AccessDeniedException when changing user 
passsword.");
+            } catch (AccessDeniedException e) {
+                // expected an Access is denied exception
+                Throwable cause = e.getCause();
+                assertTrue(cause instanceof CommitFailedException);
+                assertEquals("OakAccess0000: Access denied", 
cause.getMessage());
+            }
+        } finally {
+            if (serviceReference != null) {
+                // done with this.
+                bundleContext.ungetService(serviceReference);
+            }
+            
+            //put the original config back
+            configuration.update(originalServiceProps);
+            new WaitForServiceUpdated(5000, 100, bundleContext, 
ChangeUserPassword.class, "alwaysAllowSelfChangePassword", 
+                    originalServiceProps == null ? null 
:originalServiceProps.get("alwaysAllowSelfChangePassword"));
+        }
+    }
+
+}

Reply via email to