Author: angela
Date: Thu Oct  2 13:11:36 2014
New Revision: 1628964

URL: http://svn.apache.org/r1628964
Log:
OAK-2156 : UserAuthentication: Allow Password Change Via SimpleCredentials 
Attribute

Added:
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java?rev=1628964&r1=1628963&r2=1628964&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
 Thu Oct  2 13:11:36 2014
@@ -36,6 +36,7 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.oak.api.AuthInfo;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
@@ -119,7 +120,11 @@ class UserAuthentication implements Auth
                 checkSuccess(success, "UserId/Password mismatch.");
 
                 if (isPasswordExpired(user)) {
-                    throw new CredentialExpiredException("User password has 
expired");
+                    // change the password if the credentials object has the
+                    // UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD 
attribute set
+                    if (!changePassword(user, creds)) {
+                        throw new CredentialExpiredException("User password 
has expired");
+                    }
                 }
             } else if (credentials instanceof ImpersonationCredentials) {
                 ImpersonationCredentials ipCreds = (ImpersonationCredentials) 
credentials;
@@ -143,6 +148,30 @@ class UserAuthentication implements Auth
         }
     }
 
+    private boolean changePassword(User user, SimpleCredentials credentials) {
+        try {
+            Object newPasswordObject = 
credentials.getAttribute(CREDENTIALS_ATTRIBUTE_NEWPASSWORD);
+            if (newPasswordObject != null) {
+                if (newPasswordObject instanceof String) {
+                    user.changePassword((String) newPasswordObject);
+                    root.commit();
+                    log.debug("User " + userId + ": changed user password");
+                    return true;
+                } else {
+                    log.warn("Aborted password change for user " + userId
+                            + ": provided new password is of incompatible type 
"
+                            + newPasswordObject.getClass().getName());
+                }
+            }
+        } catch (RepositoryException e) {
+            log.error("Failed to change password for user " + userId, 
e.getMessage());
+        } catch (CommitFailedException e) {
+            root.refresh();
+            log.error("Failed to change password for user " + userId, 
e.getMessage());
+        }
+        return false;
+    }
+
     private boolean equalUserId(ImpersonationCredentials creds) {
         Credentials base = creds.getBaseCredentials();
         return (base instanceof SimpleCredentials) && 
userId.equals(((SimpleCredentials) base).getUserID());

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java?rev=1628964&r1=1628963&r2=1628964&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java
 Thu Oct  2 13:11:36 2014
@@ -222,4 +222,10 @@ public interface UserConstants {
      * Default value for {@link #PARAM_PASSWORD_INITIAL_CHANGE}
      */
     boolean DEFAULT_PASSWORD_INITIAL_CHANGE = false;
+
+    /**
+     * Name of the {@link javax.jcr.SimpleCredentials} attribute containing 
the new password.
+     * This may be used change the password via the credentials object.
+     */
+    String CREDENTIALS_ATTRIBUTE_NEWPASSWORD = "user.newpassword";
 }

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java?rev=1628964&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ResetExpiredPasswordTest.java
 Thu Oct  2 13:11:36 2014
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.user;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.CredentialExpiredException;
+import javax.security.auth.login.LoginException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @see <a href="https://issues.apache.org/jira/browse/OAK-2156";>OAK-2156</a>
+ */
+public class ResetExpiredPasswordTest extends AbstractSecurityTest implements 
UserConstants {
+
+    private String userId;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        User testuser = getTestUser();
+        userId = testuser.getID();
+
+        // set password last modified to beginning of epoch
+        
root.getTree(testuser.getPath()).getChild(REP_PWD).setProperty(REP_PASSWORD_LAST_MODIFIED,
 0);
+        root.commit();
+    }
+
+    @Override
+    protected ConfigurationParameters getSecurityConfigParameters() {
+        ConfigurationParameters userConfig = 
ConfigurationParameters.of(UserConstants.PARAM_PASSWORD_MAX_AGE, 10);
+        return 
ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, userConfig));
+    }
+
+    private void authenticate(String expiredPw, Object newPw) throws 
LoginException {
+        SimpleCredentials creds = new SimpleCredentials(userId, 
expiredPw.toCharArray());
+        creds.setAttribute(UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD, 
newPw);
+
+        Authentication a = new UserAuthentication(getUserConfiguration(), 
root, userId);
+        a.authenticate(creds);
+    }
+
+    @Test
+    public void testPasswordChangePersisted() throws Exception {
+        authenticate(userId, "newPw");
+
+        // check that the password has been persisted and has the value of the 
new password
+        Root rootBasedOnSeparateSession = 
login(getAdminCredentials()).getLatestRoot();
+        Tree userTree = 
rootBasedOnSeparateSession.getTree(getTestUser().getPath());
+        
assertTrue(PasswordUtil.isSame(userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING),
 "newPw"));
+    }
+
+    @Test
+    public void testAuthenticatePasswordExpiredThenChanged() throws Exception {
+        authenticate(userId, userId);
+    }
+
+    @Test
+    public void testChangeWithWrongPw() throws Exception {
+        try {
+            authenticate("wrongPw", "newPw");
+            fail("Authentication with wrong expired password should fail and 
should not reset pw.");
+        } catch (LoginException e) {
+            // success
+        } finally {
+            Tree userTree = root.getTree(getTestUser().getPath());
+            
assertTrue(PasswordUtil.isSame(userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING),
 userId));
+            assertEquals(0, 
userTree.getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getValue(Type.LONG).longValue());
+        }
+    }
+
+    @Test
+    public void testChangeWithNonStringAttribute() throws Exception {
+        try {
+            authenticate(userId, new Long(1));
+            fail("Authentication with non-string attribute should fail.");
+        } catch (CredentialExpiredException e) {
+            // success
+        } finally {
+            Tree userTree = root.getTree(getTestUser().getPath());
+            
assertTrue(PasswordUtil.isSame(userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING),
 userId));
+            assertEquals(0, 
userTree.getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getValue(Type.LONG).longValue());
+        }
+    }
+}


Reply via email to