You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2019/06/25 14:54:41 UTC

svn commit: r1862074 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/ oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ oak-doc/src/site/markdown/security/user/ oak-jcr/src/test/java/org/apache/j...

Author: angela
Date: Tue Jun 25 14:54:40 2019
New Revision: 1862074

URL: http://svn.apache.org/viewvc?rev=1862074&view=rev
Log:
OAK-8408: UserImporter must not trigger creation of rep:pwd node unless included in xml (initial-pw-change)

Added:
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportInitialPwChangeTest.java
      - copied, changed from r1861142, jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordForceInitialPasswordChangeTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.java
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/expiry.md

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java Tue Jun 25 14:54:40 2019
@@ -113,7 +113,7 @@ class UserImpl extends AuthorizableImpl
 
         pwHistory.updatePasswordHistory(getTree(), password);
 
-        userManager.setPassword(getTree(), getID(),  password, true);
+        userManager.setPassword(getTree(), getID(),  password, false);
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImporter.java Tue Jun 25 14:54:40 2019
@@ -280,7 +280,7 @@ class UserImporter implements ProtectedP
                 }
 
                 String pw = propInfo.getTextValue().getString();
-                userManager.setPassword(parent, a.getID(), pw, false);
+                userManager.setPassword(parent, a.getID(), pw, true);
                 currentPw = pw;
 
                 return true;

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserManagerImpl.java Tue Jun 25 14:54:40 2019
@@ -164,7 +164,7 @@ public class UserManagerImpl implements
         Tree userTree = userProvider.createUser(userID, intermediatePath);
         setPrincipal(userTree, principal);
         if (password != null) {
-            setPassword(userTree, userID, password, true);
+            setPassword(userTree, userID, password, false);
         }
 
         User user = new UserImpl(userID, userTree, this);
@@ -454,9 +454,9 @@ public class UserManagerImpl implements
         authorizableTree.setProperty(UserConstants.REP_PRINCIPAL_NAME, principal.getName());
     }
 
-    void setPassword(@NotNull Tree userTree, @NotNull String userId, @NotNull String password, boolean forceHash) throws RepositoryException {
+    void setPassword(@NotNull Tree userTree, @NotNull String userId, @NotNull String password, boolean isImport) throws RepositoryException {
         String pwHash;
-        if (forceHash || PasswordUtil.isPlainTextPassword(password)) {
+        if (!isImport || PasswordUtil.isPlainTextPassword(password)) {
             try {
                 pwHash = PasswordUtil.buildPasswordHash(password, config);
             } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
@@ -469,24 +469,26 @@ public class UserManagerImpl implements
 
         // set last-modified property if pw-expiry is enabled and the user is not
         // admin. if initial-pw-change is enabled, we don't set the last modified
-        // for new users, in order to force a pw change upon the next login
-        boolean expiryEnabled = passwordExpiryEnabled();
-        boolean forceInitialPwChange = forceInitialPasswordChangeEnabled();
-        boolean isNewUser = userTree.getStatus() == Tree.Status.NEW;
-
-        if (Utils.canHavePasswordExpired(userId, config)
-                // only expiry is enabled, set in all cases
-                && ((expiryEnabled && !forceInitialPwChange)
-                // as soon as force initial pw is enabled, we set in all cases except new users,
-                // irrespective of password expiry being enabled or not
-                || (forceInitialPwChange && !isNewUser))) {
-
+        // for new or imported users, in order to force a pw change upon the next login
+        if (Utils.canHavePasswordExpired(userId, config) && setPasswordLastModified(userTree, isImport)) {
             Tree pwdTree = TreeUtil.getOrAddChild(userTree, UserConstants.REP_PWD, UserConstants.NT_REP_PASSWORD);
             // System.currentTimeMillis() may be inaccurate on windows. This is accepted for this feature.
             pwdTree.setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, System.currentTimeMillis(), Type.LONG);
         }
     }
 
+    private boolean setPasswordLastModified(@NotNull Tree userTree, boolean isImport) {
+        if (forceInitialPasswordChangeEnabled()) {
+            // initial-pw-change: set last-mod property except for new or imported users (irrespective of pw-expiry configuration)
+            return !(isImport || userTree.getStatus() == Tree.Status.NEW);
+        } else if (passwordExpiryEnabled()) {
+            // only expiry is enabled: set for all user mgt api calls. for user-import only set upon user creation (new tree)
+            return !isImport || userTree.getStatus() == Tree.Status.NEW;
+        } else {
+            return false;
+        }
+    }
+
     private boolean passwordExpiryEnabled() {
         return config.getConfigValue(UserConstants.PARAM_PASSWORD_MAX_AGE, UserConstants.DEFAULT_PASSWORD_MAX_AGE) > 0;
     }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAndForceInitialChangeTest.java Tue Jun 25 14:54:40 2019
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.oak.security.user;
 
 import java.util.UUID;
+import javax.jcr.RepositoryException;
 import javax.jcr.SimpleCredentials;
 import javax.security.auth.login.CredentialExpiredException;
 
@@ -24,14 +25,17 @@ import com.google.common.collect.Immutab
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
 import org.apache.jackrabbit.oak.api.PropertyState;
+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.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.REP_PWD;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -57,6 +61,11 @@ public class PasswordExpiryAndForceIniti
         return ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, parameters));
     }
 
+    @NotNull
+    private Tree getUserTree(@NotNull User user) throws RepositoryException {
+        return root.getTree(user.getPath());
+    }
+
     @Test
     public void testCreateUser() throws Exception {
         String newUserId = "newuser" + UUID.randomUUID();
@@ -65,7 +74,7 @@ public class PasswordExpiryAndForceIniti
             user = getUserManager(root).createUser(newUserId, newUserId);
             root.commit();
 
-            assertFalse(root.getTree(user.getPath()).hasChild(UserConstants.REP_PWD));
+            assertFalse(getUserTree(user).hasChild(UserConstants.REP_PWD));
             assertFalse(user.hasProperty(UserConstants.REP_PWD + "/" + UserConstants.REP_PASSWORD_LAST_MODIFIED));
         } finally {
             if (user != null) {
@@ -93,7 +102,7 @@ public class PasswordExpiryAndForceIniti
         User user = getTestUser();
         user.changePassword(userId);
         root.commit();
-        PropertyState p = root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
+        PropertyState p = getUserTree(user).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
         long newModTime = p.getValue(Type.LONG);
         assertTrue(newModTime > 0);
 
@@ -101,4 +110,31 @@ public class PasswordExpiryAndForceIniti
         // during user creation pw last modified is set, thus it shouldn't expire
         a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
     }
+
+    /**
+     * rep:passwordLastModified must NOT be created otherwise the user might never be forced to change pw upon first login.
+     */
+    @Test
+    public void testSetPasswordImportExistingUser() throws Exception {
+        UserManagerImpl userManager = (UserManagerImpl) getUserManager(root);
+        Tree userTree = getUserTree(getTestUser());
+        assertFalse(userTree.hasChild(REP_PWD));
+
+        userManager.setPassword(userTree, getTestUser().getID(), "pwd", true);
+        assertFalse(userTree.hasChild(REP_PWD));
+    }
+
+    /**
+     * rep:passwordLastModified must NOT be created in accordance to UserManager.createUser
+     */
+    @Test
+    public void testSetPasswordImportNewUser() throws Exception {
+        UserManagerImpl userManager = (UserManagerImpl) getUserManager(root);
+        User u = userManager.createUser("uNew", null);
+        Tree userTree = getUserTree(u);
+        assertFalse(userTree.hasChild(REP_PWD));
+
+        userManager.setPassword(userTree, "uNew", "pwd", true);
+        assertFalse(userTree.hasChild(REP_PWD));
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java Tue Jun 25 14:54:40 2019
@@ -16,28 +16,34 @@
  */
 package org.apache.jackrabbit.oak.security.user;
 
-import java.util.UUID;
-import javax.jcr.SimpleCredentials;
-import javax.security.auth.login.CredentialExpiredException;
-import javax.security.auth.login.LoginException;
-
 import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
 import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
+import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
+import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
 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.plugins.tree.TreeUtil;
+import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Test;
 
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.CredentialExpiredException;
+import javax.security.auth.login.LoginException;
+import java.util.UUID;
+
+import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.REP_PASSWORD_LAST_MODIFIED;
+import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.REP_PWD;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -61,6 +67,11 @@ public class PasswordExpiryTest extends
         return ConfigurationParameters.of(UserConfiguration.NAME, userConfig);
     }
 
+    @NotNull
+    private Tree getUserTree(@NotNull User user) throws RepositoryException {
+        return root.getTree(user.getPath());
+    }
+
     @Test
     public void testCreateUser() throws Exception {
         String newUserId = "newuser" + UUID.randomUUID();
@@ -70,7 +81,7 @@ public class PasswordExpiryTest extends
             user = getUserManager(root).createUser(newUserId, newUserId);
             root.commit();
 
-            Tree pwdTree = root.getTree(user.getPath()).getChild(UserConstants.REP_PWD);
+            Tree pwdTree = getUserTree(user).getChild(UserConstants.REP_PWD);
             assertTrue(pwdTree.exists());
             assertTrue(TreeUtil.isNodeType(pwdTree, UserConstants.NT_REP_PASSWORD, root.getTree(NodeTypeConstants.NODE_TYPES_PATH)));
 
@@ -95,13 +106,13 @@ public class PasswordExpiryTest extends
     @Test
     public void testChangePassword() throws Exception {
         User user = getTestUser();
-        PropertyState p1 = root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
+        PropertyState p1 = getUserTree(user).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
         long oldModTime = p1.getValue(Type.LONG, 0);
         assertTrue(oldModTime > 0);
         waitForSystemTimeIncrement(oldModTime);
         user.changePassword(userId);
         root.commit();
-        PropertyState p2 = root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
+        PropertyState p2 = getUserTree(user).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
         long newModTime = p2.getValue(Type.LONG, 0);
         assertTrue(newModTime > oldModTime);
     }
@@ -117,7 +128,7 @@ public class PasswordExpiryTest extends
     public void testAuthenticatePasswordExpired() throws Exception {
         Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
         // set password last modified to beginning of epoch
-        root.getTree(getTestUser().getPath()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
+        getUserTree(getTestUser()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
         root.commit();
         try {
             a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
@@ -131,7 +142,7 @@ public class PasswordExpiryTest extends
     public void testAuthenticateBeforePasswordExpired() throws Exception {
         Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
         // set password last modified to beginning of epoch
-        root.getTree(getTestUser().getPath()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
+        getUserTree(getTestUser()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
         root.commit();
         try {
             a.authenticate(new SimpleCredentials(userId, "wrong".toCharArray()));
@@ -146,7 +157,7 @@ public class PasswordExpiryTest extends
     public void testAuthenticatePasswordExpiredChangePassword() throws Exception {
         Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
         // set password last modified to beginning of epoch
-        root.getTree(getTestUser().getPath()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
+        getUserTree(getTestUser()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
         root.commit();
 
         // changing the password should reset the pw last mod and the pw no longer be expired
@@ -160,4 +171,40 @@ public class PasswordExpiryTest extends
         User adminUser = getUserManager(root).getAuthorizable(getUserConfiguration().getParameters().getConfigValue(UserConstants.PARAM_ADMIN_ID, UserConstants.DEFAULT_ADMIN_ID), User.class);
         assertFalse(root.getTree(adminUser.getPath()).getChild(UserConstants.REP_PWD).exists());
     }
+
+    /**
+     * import existing user: rep:passwordLastModified must not be updated (unless this property is explicitly included in the xml import)
+     */
+    @Test
+    public void testSetPasswordImportExistingUser() throws Exception {
+        UserManager userManager = getUserManager(root);
+        if (userManager instanceof UserManagerImpl) {
+            Tree userTree = getUserTree(getTestUser());
+            assertTrue(userTree.hasChild(REP_PWD));
+            PropertyState lastMod = userTree.getChild(REP_PWD).getProperty(REP_PASSWORD_LAST_MODIFIED);
+
+            waitForSystemTimeIncrement(lastMod.getValue(Type.LONG));
+
+            ((UserManagerImpl) userManager).setPassword(userTree, getTestUser().getID(), "pwd", true);
+            assertTrue(userTree.hasChild(REP_PWD));
+            assertEquals(lastMod.getValue(Type.LONG), userTree.getChild(REP_PWD).getProperty(REP_PASSWORD_LAST_MODIFIED).getValue(Type.LONG));
+        }
+    }
+
+    /**
+     * import new user: rep:passwordLastModified must be created as it would when calling UserManager.createUser
+     */
+    @Test
+    public void testSetPasswordImportNewUser() throws Exception {
+        UserManager userManager = getUserManager(root);
+        if (userManager instanceof UserManagerImpl) {
+            User u = userManager.createUser("uNew", null);
+            Tree userTree = getUserTree(u);
+            assertFalse(userTree.hasChild(REP_PWD));
+
+            ((UserManagerImpl) userManager).setPassword(userTree, "uNew", "pwd", true);
+            assertTrue(userTree.hasChild(REP_PWD));
+            assertTrue(userTree.getChild(REP_PWD).hasProperty(REP_PASSWORD_LAST_MODIFIED));
+        }
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordForceInitialPasswordChangeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordForceInitialPasswordChangeTest.java?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordForceInitialPasswordChangeTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordForceInitialPasswordChangeTest.java Tue Jun 25 14:54:40 2019
@@ -19,18 +19,22 @@ package org.apache.jackrabbit.oak.securi
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
 import org.apache.jackrabbit.oak.api.PropertyState;
+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.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Test;
 
+import javax.jcr.RepositoryException;
 import javax.jcr.SimpleCredentials;
 import javax.security.auth.login.CredentialExpiredException;
 import java.util.UUID;
 
+import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.REP_PWD;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -57,6 +61,11 @@ public class PasswordForceInitialPasswor
         return ConfigurationParameters.of(UserConfiguration.NAME, userConfig);
     }
 
+    @NotNull
+    private Tree getUserTree(@NotNull User user) throws RepositoryException {
+        return root.getTree(user.getPath());
+    }
+
     @Test
     public void testCreateUser() throws Exception {
         String newUserId = "newuser" + UUID.randomUUID();
@@ -66,8 +75,8 @@ public class PasswordForceInitialPasswor
             user = getUserManager(root).createUser(newUserId, newUserId);
             root.commit();
 
-            assertFalse(root.getTree(user.getPath()).hasChild(UserConstants.REP_PWD));
-            assertFalse(user.hasProperty(UserConstants.REP_PWD + "/" + UserConstants.REP_PASSWORD_LAST_MODIFIED));
+            assertFalse(getUserTree(user).hasChild(REP_PWD));
+            assertFalse(user.hasProperty(REP_PWD + "/" + UserConstants.REP_PASSWORD_LAST_MODIFIED));
         } finally {
             if (user != null) {
                 user.remove();
@@ -90,11 +99,11 @@ public class PasswordForceInitialPasswor
     @Test
     public void testChangePassword() throws Exception {
         User user = getTestUser();
-        PropertyState p1 = root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
+        PropertyState p1 = getUserTree(user).getChild(REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
         assertNull(p1);
         user.changePassword(userId);
         root.commit();
-        PropertyState p2 = root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
+        PropertyState p2 = getUserTree(user).getChild(REP_PWD).getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
         assertNotNull(p2);
         assertTrue(p2.getValue(Type.LONG) > 0);
 
@@ -102,4 +111,31 @@ public class PasswordForceInitialPasswor
         Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
         a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
     }
+
+    /**
+     * rep:passwordLastModified must NOT be created otherwise the user might never be forced to change pw upon first login.
+     */
+    @Test
+    public void testSetPasswordImportExistingUser() throws Exception {
+        UserManagerImpl userManager = (UserManagerImpl) getUserManager(root);
+        Tree userTree = getUserTree(getTestUser());
+        assertFalse(userTree.hasChild(REP_PWD));
+
+        userManager.setPassword(userTree, getTestUser().getID(), "pwd", true);
+        assertFalse(userTree.hasChild(REP_PWD));
+    }
+
+    /**
+     * rep:passwordLastModified must NOT be created in accordance to UserManager.createUser
+     */
+    @Test
+    public void testSetPasswordImportNewUser() throws Exception {
+        UserManagerImpl userManager = (UserManagerImpl) getUserManager(root);
+        User u = userManager.createUser("uNew", null);
+        Tree userTree = getUserTree(u);
+        assertFalse(userTree.hasChild(REP_PWD));
+
+        userManager.setPassword(userTree, "uNew", "pwd", true);
+        assertFalse(userTree.hasChild(REP_PWD));
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.java?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.java Tue Jun 25 14:54:40 2019
@@ -184,14 +184,14 @@ public class UserManagerImplTest extends
 
         Tree userTree = root.getTree(user.getPath());
         for (String pw : pwds) {
-            userMgr.setPassword(userTree, testUserId, pw, true);
+            userMgr.setPassword(userTree, testUserId, pw, false);
             String pwHash = userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING);
             assertNotNull(pwHash);
             assertTrue(PasswordUtil.isSame(pwHash, pw));
         }
 
         for (String pw : pwds) {
-            userMgr.setPassword(userTree, testUserId, pw, false);
+            userMgr.setPassword(userTree, testUserId, pw, true);
             String pwHash = userTree.getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING);
             assertNotNull(pwHash);
             if (!pw.startsWith("{")) {

Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/expiry.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/expiry.md?rev=1862074&r1=1862073&r2=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/expiry.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/expiry.md Tue Jun 25 14:54:40 2019
@@ -195,6 +195,15 @@ potential need to enable password expiry
 imported data to make sense, and/or the effect on already existing/overwritten
 data.
 
+With the changes made in the light of [OAK-8408](https://issues.apache.org/jira/browse/OAK-8408) 
+the following rules apply when importing a user without an extra `rep:pw` node:
+
+- if `initialPasswordChange` is enabled, `rep:passwordLastModified` will never be set irrespective if the user node is 
+  new or modified. i.e. the user will be force to change the pw upon login.
+- if `pw-expiry` is enabled, `rep:passwordLastModified` will only be set for a new user node (but not if node gets modified).
+  this ensures that the password will expire but doesn't reset the expiry when changing an existing user with XML import.
+- if both `initialPasswordChange` and `pw-expiry` are enabled, the rules for `initialPasswordChange` apply.
+
 <!-- hidden references -->
 [SimpleCredentials]: http://www.day.com/specs/javax.jcr/javadocs/jcr-2.0/javax/jcr/SimpleCredentials.html
 [CredentialExpiredException]: https://docs.oracle.com/javase/7/docs/api/javax/security/auth/login/CredentialExpiredException.html

Copied: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportInitialPwChangeTest.java (from r1861142, jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportInitialPwChangeTest.java?p2=jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportInitialPwChangeTest.java&p1=jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java&r1=1861142&r2=1862074&rev=1862074&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportInitialPwChangeTest.java Tue Jun 25 14:54:40 2019
@@ -16,30 +16,44 @@
  */
 package org.apache.jackrabbit.oak.jcr.security.user;
 
-import java.util.HashMap;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Session;
-
+import jdk.nashorn.internal.ir.annotations.Ignore;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
+import org.apache.jackrabbit.spi.Name;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.helpers.AttributesImpl;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.LoginException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+import javax.security.auth.login.CredentialExpiredException;
+import java.util.HashMap;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Testing user import with default {@link org.apache.jackrabbit.oak.spi.xml.ImportBehavior}
- * and pw-expiry content
- *
- * @see <a href="https://issues.apache.org/jira/browse/OAK-1922">OAK-1922</a>
- * @see <a href="https://issues.apache.org/jira/browse/OAK-1943">OAK-1943</a>
+ * and initial-pw-change option enabled
  */
-public class UserImportPwExpiryTest extends AbstractImportTest {
+public class UserImportInitialPwChangeTest extends AbstractImportTest {
 
     @Override
     protected String getTargetPath() {
@@ -48,178 +62,139 @@ public class UserImportPwExpiryTest exte
 
     @Override
     protected String getImportBehavior() {
-        return null;
+        return ImportBehavior.NAME_BESTEFFORT;
     }
 
     @Nullable
     protected ConfigurationParameters getConfigurationParameters() {
         HashMap<String, Object> userParams = new HashMap<String, Object>() {{
-            put(UserConstants.PARAM_PASSWORD_MAX_AGE, 10);
+            put(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, true);
         }};
         return ConfigurationParameters.of(UserConfiguration.NAME, ConfigurationParameters.of(userParams));
     }
 
-    /**
-     * @since Oak 1.1
-     */
     @Test
-    public void testImportUserCreatesPasswordLastModified() throws Exception {
+    public void testImportUserWithoutPwdNode() throws Exception {
         // import user
         String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-                "<sv:node sv:name=\"x\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
-                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
-                "      <sv:value>rep:User</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\">" +
-                "      <sv:value>9dd4e461-268c-3034-b5c8-564e155c67a6</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\">" +
-                "      <sv:value>pw</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
-                "      <sv:value>xPrincipal</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:node sv:name=\"" + UserConstants.REP_PWD + "\">" +
-                "      <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
-                "         <sv:value>"+ UserConstants.NT_REP_PASSWORD +"</sv:value>" +
-                "      </sv:property>" +
-                "   </sv:node>" +
+                "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>"+
                 "</sv:node>";
 
         doImport(USERPATH, xml);
 
-        Authorizable authorizable = getUserManager().getAuthorizable("x");
+        Authorizable authorizable = getUserManager().getAuthorizable("t");
         Node userNode = getImportSession().getNode(authorizable.getPath());
-        assertTrue(userNode.hasNode(UserConstants.REP_PWD));
-        Node pwdNode = userNode.getNode(UserConstants.REP_PWD);
-        assertTrue(pwdNode.getDefinition().isProtected());
-        assertTrue(pwdNode.hasProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED));
-        assertTrue(pwdNode.getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getDefinition().isProtected());
+        assertFalse(userNode.hasNode(UserConstants.REP_PWD));
+        getImportSession().save();
+
+        try {
+            getImportSession().getRepository().login(new SimpleCredentials("t", "t".toCharArray())).logout();
+            fail("must be prompted for initial pw change!");
+        } catch (LoginException e) {
+            assertTrue(e.getCause() instanceof CredentialExpiredException);
+        }
     }
 
-    /**
-     * @since Oak 1.1
-     */
     @Test
-    public void testImportUserCreatesPasswordLastModified2() throws Exception {
-        // import user without rep:pwd child node defined
+    public void testImportExistingUserWithoutPwdNode() throws Exception {
+        User user = getUserManager().createUser("t", "t");
+        getImportSession().save();
+        String userPath = user.getPath();
+        String uuid = getImportSession().getProperty(PathUtils.concat(userPath, JcrConstants.JCR_UUID)).getString();
+
+        // import user
         String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-                "<sv:node sv:name=\"x\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
-                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
-                "      <sv:value>rep:User</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\">" +
-                "      <sv:value>9dd4e461-268c-3034-b5c8-564e155c67a6</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\">" +
-                "      <sv:value>pw</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
-                "      <sv:value>xPrincipal</sv:value>" +
-                "   </sv:property>" +
+                "<sv:node sv:name=\""+PathUtils.getName(userPath)+"\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>"+uuid+"</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>"+
+                "   <sv:node sv:name=\"profile\">" +
+                "      <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>"+ JcrConstants.NT_UNSTRUCTURED +"</sv:value></sv:property>" +
+                "   </sv:node>" +
                 "</sv:node>";
 
-        doImport(USERPATH, xml);
+        doImport(PathUtils.getParentPath(userPath), xml, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
 
-        // verify that the pwd node has still been created
-        Authorizable authorizable = getUserManager().getAuthorizable("x");
+        Authorizable authorizable = getUserManager().getAuthorizable("t");
         Node userNode = getImportSession().getNode(authorizable.getPath());
-        assertTrue(userNode.hasNode(UserConstants.REP_PWD));
-        Node pwdNode = userNode.getNode(UserConstants.REP_PWD);
-        assertTrue(pwdNode.getDefinition().isProtected());
-        assertTrue(pwdNode.hasProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED));
-        assertTrue(pwdNode.getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getDefinition().isProtected());
+        assertFalse(userNode.hasNode(UserConstants.REP_PWD));
+        getImportSession().save();
+
+        try {
+            getImportSession().getRepository().login(new SimpleCredentials("t", "t".toCharArray())).logout();
+            fail("must be prompted for initial pw change!");
+        } catch (LoginException e) {
+            assertTrue(e.getCause() instanceof CredentialExpiredException);
+        }
     }
 
-    /**
-     * @since Oak 1.1
-     */
     @Test
-    public void testImportUserWithPwdProperties() throws Exception {
+    public void testImportUserWithPwdNodeMissingLastModified() throws Exception {
         // import user
         String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-                "<sv:node sv:name=\"y\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
-                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
-                "      <sv:value>rep:User</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\">" +
-                "      <sv:value>41529076-9594-360e-ae48-5922904f345d</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\">" +
-                "      <sv:value>pw</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
-                "      <sv:value>yPrincipal</sv:value>" +
-                "   </sv:property>" +
+                "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>"+
                 "   <sv:node sv:name=\"" + UserConstants.REP_PWD + "\">" +
-                "      <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
-                "         <sv:value>" + UserConstants.NT_REP_PASSWORD + "</sv:value>" +
-                "      </sv:property>" +
-                "      <sv:property sv:name=\"" + UserConstants.REP_PASSWORD_LAST_MODIFIED + "\" sv:type=\"Long\">" +
-                "         <sv:value>1404036716000</sv:value>" +
-                "      </sv:property>" +
-                "      <sv:property sv:name=\"customProp\" sv:type=\"String\">" +
-                "         <sv:value>abc</sv:value>" +
-                "      </sv:property>" +
+                "      <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>"+ UserConstants.NT_REP_PASSWORD +"</sv:value></sv:property>" +
                 "   </sv:node>" +
                 "</sv:node>";
 
         doImport(USERPATH, xml);
 
-        Authorizable authorizable = getUserManager().getAuthorizable("y");
+        Authorizable authorizable = getUserManager().getAuthorizable("t");
         Node userNode = getImportSession().getNode(authorizable.getPath());
         assertTrue(userNode.hasNode(UserConstants.REP_PWD));
-
         Node pwdNode = userNode.getNode(UserConstants.REP_PWD);
-        assertTrue(pwdNode.hasProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED));
-        assertEquals(1404036716000L, pwdNode.getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getLong());
+        assertFalse(pwdNode.hasProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED));
+        getImportSession().save();
 
-        assertTrue(pwdNode.hasProperty("customProp"));
-        Property custom = pwdNode.getProperty("customProp");
-        assertTrue(custom.getDefinition().isProtected());
-        assertEquals("abc", custom.getString());
+        try {
+            getImportSession().getRepository().login(new SimpleCredentials("t", "t".toCharArray())).logout();
+            fail("must be prompted for initial pw change!");
+        } catch (LoginException e) {
+            assertTrue(e.getCause() instanceof CredentialExpiredException);
+        }
     }
 
-    /**
-     * @since Oak 1.1
-     */
     @Test
-    public void testImportExistingUserWithoutExpiryProperty() throws Exception {
-
-        String uid = "existing";
-        User user = getUserManager().createUser(uid, uid);
-
-        Session s = getImportSession();
-        // change password to force existence of password last modified property
-        user.changePassword(uid);
-        s.save();
-
-        Node userNode = s.getNode(user.getPath());
-        assertTrue(userNode.hasNode(UserConstants.REP_PWD));
-        Node pwdNode = userNode.getNode(UserConstants.REP_PWD);
-        assertTrue(pwdNode.hasProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED));
-
-        // overwrite user via import
+    public void testImportUserWithPwdNodeWithLastModified() throws Exception {
+        // import user
+        long now = System.currentTimeMillis();
         String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-                "<sv:node sv:name=\"" + uid + "\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
-                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
-                "      <sv:value>rep:User</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\">" +
-                "      <sv:value>" + uid + "</sv:value>" +
-                "   </sv:property>" +
-                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\">" +
-                "      <sv:value>" + uid + "Principal</sv:value>" +
-                "   </sv:property>" +
+                "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>"+
+                "   <sv:node sv:name=\"" + UserConstants.REP_PWD + "\">" +
+                "      <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>"+ UserConstants.NT_REP_PASSWORD +"</sv:value></sv:property>" +
+                "      <sv:property sv:name=\"" + UserConstants.REP_PASSWORD_LAST_MODIFIED + "\" sv:type=\"Long\"><sv:value>"+now+"</sv:value></sv:property>" +
+                "   </sv:node>" +
                 "</sv:node>";
 
         doImport(USERPATH, xml);
 
-        Authorizable authorizable = getUserManager().getAuthorizable(uid);
-        userNode = s.getNode(authorizable.getPath());
+        Authorizable authorizable = getUserManager().getAuthorizable("t");
+        Node userNode = getImportSession().getNode(authorizable.getPath());
         assertTrue(userNode.hasNode(UserConstants.REP_PWD));
-
-        pwdNode = userNode.getNode(UserConstants.REP_PWD);
+        Node pwdNode = userNode.getNode(UserConstants.REP_PWD);
         assertTrue(pwdNode.hasProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED));
+        assertEquals(now, pwdNode.getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED).getLong());
+        getImportSession().save();
+
+        // login must succeed
+        getImportSession().getRepository().login(new SimpleCredentials("t", "t".toCharArray())).logout();
     }
 }