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 2015/07/17 14:18:09 UTC
svn commit: r1691532 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/
oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/
oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/ oak-cor...
Author: angela
Date: Fri Jul 17 12:18:09 2015
New Revision: 1691532
URL: http://svn.apache.org/r1691532
Log:
OAK-2445 : Password History Support (path provided by dominique jaeggi, thanks a lot)
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryTest.java
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/history.md
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportHistoryTest.java
- copied, changed from r1691382, 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/UserConfigurationImpl.java
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/spi/security/user/UserConstants.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/package-info.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/expiry.md
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java?rev=1691532&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java Fri Jul 17 12:18:09 2015
@@ -0,0 +1,137 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+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.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.apache.jackrabbit.oak.util.TreeUtil;
+
+/**
+ * Helper class for the password history feature.
+ */
+final class PasswordHistory implements UserConstants {
+
+ private static final int HISTORY_MAX_SIZE = 1000;
+
+ private final int maxSize;
+ private final boolean isEnabled;
+
+ public PasswordHistory(@Nonnull ConfigurationParameters config) {
+ maxSize = Math.min(HISTORY_MAX_SIZE, config.getConfigValue(UserConstants.PARAM_PASSWORD_HISTORY_SIZE, UserConstants.PASSWORD_HISTORY_DISABLED_SIZE));
+ isEnabled = maxSize > UserConstants.PASSWORD_HISTORY_DISABLED_SIZE;
+ }
+
+ /**
+ * If password history is enabled this method validates the new password and
+ * updated the history; otherwise it returns {@code false}.
+ *
+ * @param userTree The user tree.
+ * @param password The new password to be validated.
+ * @return {@code true} if the history is enabled, the new password is not
+ * included in the history and the history was successfully updated;
+ * {@code false} otherwise.
+ * @throws javax.jcr.nodetype.ConstraintViolationException If the feature
+ * is enabled and the new password is found in the history.
+ * @throws javax.jcr.AccessDeniedException If the rep:pwd tree cannot be
+ * accessed.
+ */
+ boolean updatePasswordHistory(@Nonnull Tree userTree, @Nonnull String password) throws ConstraintViolationException, AccessDeniedException {
+ boolean updated = false;
+ if (isEnabled) {
+ checkPasswordInHistory(userTree, password);
+ shiftPasswordHistory(userTree);
+ updated = true;
+ }
+ return updated;
+ }
+
+ /**
+ * Update the history property with the current pw-hash stored in rep:password
+ * and trim the list of hashes in the list according to the configured maxSize.
+ *
+ * @param userTree The user tree.
+ * @throws AccessDeniedException If the editing session cannot access or
+ * create the rep:pwd node.
+ */
+ private void shiftPasswordHistory(@Nonnull Tree userTree) throws AccessDeniedException {
+ String currentPasswordHash = TreeUtil.getString(userTree, UserConstants.REP_PASSWORD);
+ if (currentPasswordHash != null) {
+ Tree passwordTree = getPasswordTree(userTree, true);
+ PropertyState historyProp = passwordTree.getProperty(UserConstants.REP_PWD_HISTORY);
+
+ // insert the current (old) password at the beginning of the password history
+ List<String> historyEntries = (historyProp == null) ? new ArrayList<String>() : Lists.newArrayList(historyProp.getValue(Type.STRINGS));
+ historyEntries.add(0, currentPasswordHash);
+
+ /* remove oldest history entries exceeding configured history max size (e.g. after
+ * a configuration change)
+ */
+ if (historyEntries.size() > maxSize) {
+ historyEntries = historyEntries.subList(0, maxSize);
+ }
+
+ passwordTree.setProperty(UserConstants.REP_PWD_HISTORY, historyEntries, Type.STRINGS);
+ }
+ }
+
+ /**
+ * Verify that the specified new password is not contained in the history.
+ *
+ * @param userTree The user tree.
+ * @param newPassword The new password
+ * @throws ConstraintViolationException If the passsword is found in the history
+ * @throws AccessDeniedException If the editing session cannot access the rep:pwd node.
+ */
+ private void checkPasswordInHistory(@Nonnull Tree userTree, @Nonnull String newPassword) throws ConstraintViolationException, AccessDeniedException {
+ if (PasswordUtil.isSame(TreeUtil.getString(userTree, UserConstants.REP_PASSWORD), newPassword)) {
+ throw new ConstraintViolationException("New password is identical to the current password.");
+ }
+ Tree pwTree = getPasswordTree(userTree, false);
+ if (pwTree.exists()) {
+ PropertyState pwHistoryProperty = pwTree.getProperty(UserConstants.REP_PWD_HISTORY);
+ if (pwHistoryProperty != null) {
+ for (String historyPwHash : Iterables.limit(pwHistoryProperty.getValue(Type.STRINGS), maxSize)) {
+ if (PasswordUtil.isSame(historyPwHash, newPassword)) {
+ throw new ConstraintViolationException("New password was found in password history.");
+ }
+ }
+ }
+ }
+ }
+
+ @Nonnull
+ private static Tree getPasswordTree(@Nonnull Tree userTree, boolean doCreate) throws AccessDeniedException {
+ if (doCreate) {
+ return new NodeUtil(userTree).getOrAddChild(UserConstants.REP_PWD, UserConstants.NT_REP_PASSWORD).getTree();
+ } else {
+ return userTree.getChild(UserConstants.REP_PWD);
+ }
+ }
+}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java?rev=1691532&r1=1691531&r2=1691532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java Fri Jul 17 12:18:09 2015
@@ -105,7 +105,11 @@ import org.apache.jackrabbit.oak.spi.xml
@Property(name = UserConstants.PARAM_PASSWORD_INITIAL_CHANGE,
label = "Change Password On First Login",
description = "When enabled, forces users to change their password upon first login.",
- boolValue = UserConstants.DEFAULT_PASSWORD_INITIAL_CHANGE)
+ boolValue = UserConstants.DEFAULT_PASSWORD_INITIAL_CHANGE),
+ @Property(name = UserConstants.PARAM_PASSWORD_HISTORY_SIZE,
+ label = "Maximum Password History Size",
+ description = "Maximum number of passwords recorded for a user after changing her password (NOTE: upper limit is 1000). When changing the password the new password must not be present in the password history. A value of 0 indicates no password history is recorded.",
+ intValue = UserConstants.PASSWORD_HISTORY_DISABLED_SIZE)
})
public class UserConfigurationImpl extends ConfigurationBase implements UserConfiguration, SecurityConfiguration {
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=1691532&r1=1691531&r2=1691532&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 Fri Jul 17 12:18:09 2015
@@ -41,11 +41,13 @@ import static org.apache.jackrabbit.oak.
class UserImpl extends AuthorizableImpl implements User {
private final boolean isAdmin;
+ private final PasswordHistory pwHistory;
UserImpl(String id, Tree tree, UserManagerImpl userManager) throws RepositoryException {
super(id, tree, userManager);
isAdmin = UserUtil.isAdmin(userManager.getConfig(), id);
+ pwHistory = new PasswordHistory(userManager.getConfig());
}
//---------------------------------------------------< AuthorizableImpl >---
@@ -107,6 +109,9 @@ class UserImpl extends AuthorizableImpl
}
UserManagerImpl userManager = getUserManager();
userManager.onPasswordChange(this, password);
+
+ pwHistory.updatePasswordHistory(getTree(), password);
+
userManager.setPassword(getTree(), getID(), password, true);
}
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=1691532&r1=1691531&r2=1691532&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 Fri Jul 17 12:18:09 2015
@@ -49,6 +49,7 @@ public interface UserConstants {
String REP_MEMBERS_LIST = "rep:membersList";
String REP_IMPERSONATORS = "rep:impersonators";
String REP_PWD = "rep:pwd";
+ String REP_PWD_HISTORY = "rep:pwdHistory";
Collection<String> GROUP_PROPERTY_NAMES = ImmutableSet.of(
REP_PRINCIPAL_NAME,
@@ -228,4 +229,21 @@ public interface UserConstants {
* This may be used change the password via the credentials object.
*/
String CREDENTIALS_ATTRIBUTE_NEWPASSWORD = "user.newpassword";
+
+ /**
+ * Optional configuration parameter indicating the maximum number of passwords recorded for a user after
+ * password changes. If the value specified is > 0, password history checking during password change is implicitly
+ * enabled and the new password provided during a password change must not be found in the already recorded
+ * history.
+ *
+ * @since Oak 1.3.3
+ */
+ String PARAM_PASSWORD_HISTORY_SIZE = "passwordHistorySize";
+
+ /**
+ * Constant to indicate disabled password history, which is the default.
+ *
+ * @since Oak 1.3.3
+ */
+ int PASSWORD_HISTORY_DISABLED_SIZE = 0;
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/package-info.java?rev=1691532&r1=1691531&r2=1691532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/package-info.java Fri Jul 17 12:18:09 2015
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("2.0")
+@Version("2.1.0")
@Export(optional = "provide:=true")
package org.apache.jackrabbit.oak.spi.security.user;
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryTest.java?rev=1691532&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryTest.java Fri Jul 17 12:18:09 2015
@@ -0,0 +1,310 @@
+/*
+ * 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 java.lang.reflect.Field;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.Tree;
+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.security.user.util.PasswordUtil;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.apache.jackrabbit.oak.util.TreeUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @see OAK-2445
+ */
+public class PasswordHistoryTest extends AbstractSecurityTest implements UserConstants {
+
+ private static final String[] PASSWORDS = {
+ "abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz0", "123", "456", "789"
+ };
+
+ private static final ConfigurationParameters CONFIG = ConfigurationParameters.of(PARAM_PASSWORD_HISTORY_SIZE, 10);
+
+ @Override
+ protected ConfigurationParameters getSecurityConfigParameters() {
+ return ConfigurationParameters.of(ImmutableMap.of(UserConfiguration.NAME, CONFIG));
+ }
+
+ @Nonnull
+ private List<String> getHistory(@Nonnull User user) throws RepositoryException {
+ return ImmutableList.copyOf(TreeUtil.getStrings(
+ root.getTree(user.getPath()).getChild(REP_PWD),
+ REP_PWD_HISTORY)).reverse();
+ }
+
+ /**
+ * Use reflection to access the private fields stored in the PasswordHistory
+ */
+ private static Integer getMaxSize(@Nonnull PasswordHistory history) throws Exception {
+ Field maxSize = history.getClass().getDeclaredField("maxSize");
+ maxSize.setAccessible(true);
+ return (Integer) maxSize.get(history);
+ }
+
+ /**
+ * Use reflection to access the private fields stored in the PasswordHistory
+ */
+ private static boolean isEnabled(@Nonnull PasswordHistory history) throws Exception {
+ Field isEnabled = history.getClass().getDeclaredField("isEnabled");
+ isEnabled.setAccessible(true);
+ return (Boolean) isEnabled.get(history);
+ }
+
+ @Test
+ public void testNoPwdTreeOnUserCreation() throws Exception {
+ User user = getTestUser();
+ assertFalse(root.getTree(user.getPath()).hasChild(REP_PWD));
+ }
+
+ @Test
+ public void testHistoryEmptyOnUserCreationWithPassword() throws Exception {
+ User user = getTestUser(); // the user is created with a password set
+
+ // the rep:pwd child must not exist. without the rep:pwd child no password history can exist.
+ assertFalse(root.getTree(user.getPath()).hasChild(REP_PWD));
+ }
+
+ @Test
+ public void testHistoryWithSinglePasswordChange() throws Exception {
+ // the user must be able to change the password
+ User user = getTestUser();
+ String oldPassword = TreeUtil.getString(root.getTree(user.getPath()), REP_PASSWORD);
+ user.changePassword("newPwd");
+ root.commit();
+
+ // after changing the password, 1 password history entry should be present and the
+ // recorded password should be equal to the user's initial password
+ // however, the user's current password must not match the old password.
+ assertTrue(root.getTree(user.getPath()).hasChild(REP_PWD));
+
+ Tree pwTree = root.getTree(user.getPath()).getChild(REP_PWD);
+ assertTrue(pwTree.hasProperty(REP_PWD_HISTORY));
+
+ List<String> history = getHistory(user);
+ assertEquals(1, history.size());
+ assertEquals(oldPassword, history.iterator().next());
+
+ String currentPw = TreeUtil.getString(root.getTree(user.getPath()), REP_PASSWORD);
+ assertNotSame(currentPw, oldPassword);
+ }
+
+ @Test
+ public void testHistoryMaxSize() throws Exception {
+ User user = getTestUser();
+
+ // we're changing the password 12 times, history max is 10
+ for (String pw : PASSWORDS) {
+ user.changePassword(pw);
+ root.commit();
+ }
+
+ assertEquals(10, getHistory(user).size());
+ }
+
+ @Test
+ public void testHistoryOrder() throws Exception {
+ User user = getTestUser();
+
+ // we're changing the password 12 times, history max is 10
+ for (String pw : PASSWORDS) {
+ user.changePassword(pw);
+ }
+
+ // we skip the first entry in the password list as it was shifted out
+ // due to max history size = 10.
+ int i = 1;
+ for (String pwHash : getHistory(user)) {
+ assertTrue(PasswordUtil.isSame(pwHash, PASSWORDS[i++]));
+ }
+ }
+
+ @Test
+ public void testRepeatedPwAfterHistorySizeReached() throws Exception {
+ User user = getTestUser();
+ for (String pw : PASSWORDS) {
+ user.changePassword(pw);
+ }
+
+ // changing pw back to the original value (as used for creation) must succeed
+ user.changePassword(user.getID());
+ // now, using all old passwords must also succeed as they get shifted out
+ for (String pw : PASSWORDS) {
+ user.changePassword(pw);
+ }
+ }
+
+ @Test(expected = ConstraintViolationException.class)
+ public void testHistoryViolationAtFirstChange() throws Exception {
+ User user = getTestUser();
+ user.changePassword(user.getID());
+ }
+
+ @Test(expected = ConstraintViolationException.class)
+ public void testHistoryViolation() throws Exception {
+ User user = getTestUser();
+ user.changePassword("abc");
+ user.changePassword("def");
+ user.changePassword("abc");
+ }
+
+ @Test
+ public void testNoHistoryUpdateOnViolation() throws Exception {
+ User user = getTestUser();
+ try {
+ user.changePassword("abc");
+ user.changePassword("def");
+ user.changePassword("abc");
+ fail("history violation not detected");
+ } catch (ConstraintViolationException e) {
+ String[] expected = new String[] {user.getID(), "abc"};
+ int i = 0;
+ for (String pwHash : getHistory(user)) {
+ assertTrue(PasswordUtil.isSame(pwHash, expected[i++]));
+ }
+ }
+ }
+
+ @Test
+ public void testEnabledPasswordHistory() throws Exception {
+ PasswordHistory history = new PasswordHistory(CONFIG);
+ assertTrue(isEnabled(history));
+ assertEquals(10, getMaxSize(history).longValue());
+ }
+
+ @Test
+ public void testHistoryUpperLimit() throws Exception {
+ PasswordHistory history = new PasswordHistory(ConfigurationParameters.of(PARAM_PASSWORD_HISTORY_SIZE, Integer.MAX_VALUE));
+
+ assertTrue(isEnabled(history));
+ assertEquals(1000, getMaxSize(history).longValue());
+ }
+
+ @Test
+ public void testDisabledPasswordHistory() throws Exception {
+ User user = getTestUser();
+ Tree userTree = root.getTree(user.getPath());
+
+ List<ConfigurationParameters> configs = ImmutableList.of(
+ ConfigurationParameters.EMPTY,
+ ConfigurationParameters.of(PARAM_PASSWORD_HISTORY_SIZE, PASSWORD_HISTORY_DISABLED_SIZE),
+ ConfigurationParameters.of(PARAM_PASSWORD_HISTORY_SIZE, -1),
+ ConfigurationParameters.of(PARAM_PASSWORD_HISTORY_SIZE, Integer.MIN_VALUE)
+ );
+
+ for (ConfigurationParameters config : configs) {
+ PasswordHistory disabledHistory = new PasswordHistory(config);
+
+ assertFalse(isEnabled(disabledHistory));
+ assertFalse(disabledHistory.updatePasswordHistory(userTree, user.getID()));
+ }
+ }
+
+ @Test(expected = ConstraintViolationException.class)
+ public void testCheckPasswordHistory() throws Exception {
+ Tree userTree = root.getTree(getTestUser().getPath());
+
+ PasswordHistory history = new PasswordHistory(CONFIG);
+ assertTrue(isEnabled(history));
+ assertEquals(10, getMaxSize(history).longValue());
+
+ history.updatePasswordHistory(userTree, getTestUser().getID());
+ }
+
+ @Test
+ public void testConfigurationChange() throws Exception {
+ User user = getTestUser();
+ Tree userTree = root.getTree(user.getPath());
+
+ PasswordHistory history = new PasswordHistory(CONFIG);
+ for (String pw : PASSWORDS) {
+ assertTrue(history.updatePasswordHistory(userTree, pw));
+ }
+ assertEquals(10, getHistory(user).size());
+
+ // change configuration to a smaller size
+ history = new PasswordHistory(ConfigurationParameters.of(PARAM_PASSWORD_HISTORY_SIZE, 5));
+ List<String> oldPwds = getHistory(user);
+ assertEquals(10, oldPwds.size());
+
+ // only the configured max-size number of entries in the history must be
+ // checked. additional entries in the history must be ignored
+ Iterables.skip(oldPwds, 6);
+ history.updatePasswordHistory(userTree, oldPwds.iterator().next());
+
+ // after chaning the pwd again however the rep:pwdHistory property must
+ // only contain the max-size number of passwords
+ assertEquals(5, getHistory(user).size());
+
+ history = new PasswordHistory(CONFIG);
+ history.updatePasswordHistory(userTree, "newPwd");
+ assertEquals(6, getHistory(user).size());
+ }
+
+ @Test
+ public void testEnableDisable() throws Exception {
+ User user = getTestUser();
+ Tree userTree = root.getTree(user.getPath());
+
+ PasswordHistory history = new PasswordHistory(CONFIG);
+ for (String pw : PASSWORDS) {
+ assertTrue(history.updatePasswordHistory(userTree, pw));
+ }
+ assertEquals(10, getHistory(user).size());
+
+ // disable the password history : changing the pw now must not
+ // modify the rep:pwdHistory property.
+ history = new PasswordHistory(ConfigurationParameters.EMPTY);
+ history.updatePasswordHistory(userTree, PASSWORDS[8]);
+
+ assertEquals(10, getHistory(user).size());
+ }
+
+ @Test
+ public void testSingleTypeHistoryProperty() throws Exception {
+ Tree userTree = root.getTree(getTestUser().getPath());
+ Tree pwdNode = new NodeUtil(userTree).getOrAddChild(REP_PWD, NT_REP_PASSWORD).getTree();
+
+ pwdNode.setProperty(REP_PWD_HISTORY, "singleValuedProperty");
+ assertFalse(pwdNode.getProperty(REP_PWD_HISTORY).isArray());
+ assertFalse(pwdNode.getProperty(REP_PWD_HISTORY).getType().isArray());
+
+ PasswordHistory history = new PasswordHistory(CONFIG);
+ assertTrue(history.updatePasswordHistory(userTree, "anyOtherPassword"));
+
+ assertTrue(pwdNode.getProperty(REP_PWD_HISTORY).isArray());
+ assertTrue(pwdNode.getProperty(REP_PWD_HISTORY).getType().isArray());
+ }
+}
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java?rev=1691532&r1=1691531&r2=1691532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java Fri Jul 17 12:18:09 2015
@@ -41,6 +41,7 @@ public class UserConfigurationImplTest e
private static final boolean SUPPORT_AUTOSAVE = true;
private static final Integer MAX_AGE = 10;
private static final boolean INITIAL_PASSWORD_CHANGE = true;
+ private static final Integer PASSWORD_HISTORY_SIZE = 12;
@Override
protected ConfigurationParameters getSecurityConfigParameters() {
@@ -71,6 +72,7 @@ public class UserConfigurationImplTest e
assertEquals(parameters.getConfigValue(UserConstants.PARAM_SUPPORT_AUTOSAVE, false), SUPPORT_AUTOSAVE);
assertEquals(parameters.getConfigValue(UserConstants.PARAM_PASSWORD_MAX_AGE, UserConstants.DEFAULT_PASSWORD_MAX_AGE), MAX_AGE);
assertEquals(parameters.getConfigValue(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, UserConstants.DEFAULT_PASSWORD_INITIAL_CHANGE), INITIAL_PASSWORD_CHANGE);
+ assertEquals(parameters.getConfigValue(UserConstants.PARAM_PASSWORD_HISTORY_SIZE, UserConstants.PASSWORD_HISTORY_DISABLED_SIZE), PASSWORD_HISTORY_SIZE);
}
private ConfigurationParameters getParams() {
@@ -85,6 +87,7 @@ public class UserConfigurationImplTest e
put(UserConstants.PARAM_SUPPORT_AUTOSAVE, SUPPORT_AUTOSAVE);
put(UserConstants.PARAM_PASSWORD_MAX_AGE, MAX_AGE);
put(UserConstants.PARAM_PASSWORD_INITIAL_CHANGE, INITIAL_PASSWORD_CHANGE);
+ put(UserConstants.PARAM_PASSWORD_HISTORY_SIZE, PASSWORD_HISTORY_SIZE);
}});
return params;
}
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java?rev=1691532&r1=1691531&r2=1691532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/user/action/PasswordChangeActionTest.java Fri Jul 17 12:18:09 2015
@@ -25,8 +25,6 @@ import org.apache.jackrabbit.oak.spi.sec
import org.junit.Before;
import org.junit.Test;
-import static org.junit.Assert.fail;
-
public class PasswordChangeActionTest extends AbstractSecurityTest {
private PasswordChangeAction pwChangeAction;
@@ -38,26 +36,16 @@ public class PasswordChangeActionTest ex
pwChangeAction.init(getSecurityProvider(), ConfigurationParameters.EMPTY);
}
- @Test
+ @Test(expected = ConstraintViolationException.class)
public void testNullPassword() throws Exception {
- try {
- pwChangeAction.onPasswordChange(getTestUser(), null, root, getNamePathMapper());
- fail("ConstraintViolationException expected.");
- } catch (ConstraintViolationException e) {
- // success
- }
+ pwChangeAction.onPasswordChange(getTestUser(), null, root, getNamePathMapper());
}
- @Test
+ @Test(expected = ConstraintViolationException.class)
public void testSamePassword() throws Exception {
- try {
- User user = getTestUser();
- String pw = user.getID();
- pwChangeAction.onPasswordChange(user, pw, root, getNamePathMapper());
- fail("ConstraintViolationException expected.");
- } catch (ConstraintViolationException e) {
- // success
- }
+ User user = getTestUser();
+ String pw = user.getID();
+ pwChangeAction.onPasswordChange(user, pw, root, getNamePathMapper());
}
@Test
@@ -69,7 +57,10 @@ public class PasswordChangeActionTest ex
public void testUserWithoutPassword() throws Exception {
String uid = "testUser" + UUID.randomUUID();
User user = getUserManager(root).createUser(uid, null);
-
- pwChangeAction.onPasswordChange(user, "changedPassword", root, getNamePathMapper());
+ try {
+ pwChangeAction.onPasswordChange(user, "changedPassword", root, getNamePathMapper());
+ } finally {
+ user.remove();
+ }
}
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md?rev=1691532&r1=1691531&r2=1691532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user.md Fri Jul 17 12:18:09 2015
@@ -241,6 +241,17 @@ are
See section [Password Expiry and Force Initial Password Change](user/expiry.html)
for details.
+#### Password History
+
+Since Oak 1.3.3 the default user management implementation provides password
+history support.
+
+By default this feature is disabled. The corresponding configuration option is
+
+- `PARAM_PASSWORD_HISTORY_SIZE`: number of changed passwords to remember.
+
+See section [Password History](user/history.html) for details.
+
#### Utilities
`org.apache.jackrabbit.oak.spi.security.user.*`
@@ -283,6 +294,7 @@ as of OAK 1.0:
| `PARAM_IMPORT_BEHAVIOR` | String ("abort", "ignore", "besteffort") | "ignore" |
| `PARAM_PASSWORD_MAX_AGE` | int | 0 |
| `PARAM_PASSWORD_INITIAL_CHANGE` | boolean | false |
+| `PARAM_PASSWORD_HISTORY_SIZE` | int (upper limit: 1000) | 0 |
| | | |
The following configuration parameters present with the default implementation in Jackrabbit 2.x are no longer supported and will be ignored:
@@ -317,6 +329,7 @@ implementation on various levels:
- [Authorizable Node Name](user/authorizablenodename.html)
- [Searching Users and Groups](user/query.html)
- [Password Expiry and Force Initial Password Change](user/expiry.html)
+- [Password History](user/history.html)
<!-- hidden references -->
[everyone]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/principal/EveryonePrincipal.html#NAME
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=1691532&r1=1691531&r2=1691532&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 Fri Jul 17 12:18:09 2015
@@ -74,10 +74,10 @@ been introduced, leaving open future enh
- * (UNDEFINED) protected
- * (UNDEFINED) protected multiple
-##### Node rep:passwords and Property rep:passwordLastModified
+##### Node rep:pwd and Property rep:passwordLastModified
[rep:User] > rep:Authorizable, rep:Impersonatable
- + rep:pw (rep:Password) = rep:Password protected
+ + rep:pwd (rep:Password) = rep:Password protected
...
The _rep:pw_ node and the _rep:passwordLastModified_ property are defined
Added: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/history.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/history.md?rev=1691532&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/history.md (added)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/user/history.md Fri Jul 17 12:18:09 2015
@@ -0,0 +1,85 @@
+<!--
+ 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.
+-->
+
+Password History
+--------------------------------------------------------------------------------
+
+### General
+
+Oak provides functionality to remember a configurable number of
+passwords after password changes and to prevent a password to
+be set during changing a user's password if found in said history.
+
+### Configuration
+
+An administrator may enable password history via the
+_org.apache.jackrabbit.oak.security.user.UserConfigurationImpl_
+OSGi configuration. By default the history is disabled.
+
+The following configuration option is supported:
+
+- Maximum Password History Size (_passwordHistorySize_, number of passwords): When greater 0 enables password
+ history and sets feature to remember the specified number of passwords for a user.
+
+Note, that the current implementation has a limit of at most 1000 passwords
+remembered in the history.
+
+### How it works
+
+#### Recording of Passwords
+
+If the feature is enabled, during a user changing her password, the old password
+hash is recorded in the password history.
+
+The old password hash is only recorded if a password was set (non-empty).
+Therefore setting a password for a user for the first time (i.e. during creation
+or if the user doesn't have a password set before) does not result in a history
+record, as there is no old password.
+
+The old password hash is copied to the password history *after* the provided new
+password has been validated but *before* the new password hash is written to the
+user's _rep:password_ property.
+
+The history operates as a FIFO list. A new password history record exceeding the
+configured max history size, results in the oldest recorded password from being
+removed from the history.
+
+Also, if the configuration parameter for the history size is changed to a non-zero
+but smaller value than before, upon the next password change the oldest records
+exceeding the new history size are removed.
+
+History password hashes are recorded in a multi-value property _rep:pwdHistory_ on
+the user's _rep:pwd_ node.
+
+The _rep:pwdHistory_ property is defined protected in order to guard against the
+user modifying (overcoming) her password history limitations.
+
+
+#### Evaluation of Password History
+
+Upon a user changing her password and if the password history feature is enabled
+(configured password history size > 0), implementation checks if the current
+password or any of the password hashes recorded in the history matches the new
+password.
+
+If any record is a match, a _ConstraintViolationException_ is thrown and the
+user's password is *NOT* changed.
+
+#### Oak JCR XML Import
+
+When users are imported via the Oak JCR XML importer, password history is imported
+irrespective on whether the password history feature is enabled or not.
Copied: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportHistoryTest.java (from r1691382, 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/UserImportHistoryTest.java?p2=jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportHistoryTest.java&p1=jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/UserImportPwExpiryTest.java&r1=1691382&r2=1691532&rev=1691532&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/UserImportHistoryTest.java Fri Jul 17 12:18:09 2015
@@ -16,16 +16,9 @@
*/
package org.apache.jackrabbit.oak.jcr.security.user;
-import java.util.HashMap;
-import javax.annotation.CheckForNull;
import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Session;
import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.api.security.user.User;
-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.junit.Test;
@@ -34,12 +27,10 @@ import static org.junit.Assert.assertTru
/**
* 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 pw-history content: test that the history is imported irrespective of the
+ * configuration.
*/
-public class UserImportPwExpiryTest extends AbstractImportTest {
+public class UserImportHistoryTest extends AbstractImportTest {
@Override
protected String getTargetPath() {
@@ -51,91 +42,11 @@ public class UserImportPwExpiryTest exte
return null;
}
- @CheckForNull
- protected ConfigurationParameters getConfigurationParameters() {
- HashMap<String, Object> userParams = new HashMap<String, Object>() {{
- put(UserConstants.PARAM_PASSWORD_MAX_AGE, 10);
- }};
- return ConfigurationParameters.of(UserConfiguration.NAME, ConfigurationParameters.of(userParams));
- }
-
- /**
- * @since Oak 1.1
- */
- @Test
- public void testImportUserCreatesPasswordLastModified() 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>";
-
- doImport(USERPATH, xml);
-
- Authorizable authorizable = getUserManager().getAuthorizable("x");
- 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());
- }
-
- /**
- * @since Oak 1.1
- */
- @Test
- public void testImportUserCreatesPasswordLastModified2() throws Exception {
- // import user without rep:pwd child node defined
- 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>";
-
- doImport(USERPATH, xml);
-
- // verify that the pwd node has still been created
- Authorizable authorizable = getUserManager().getAuthorizable("x");
- 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());
- }
-
/**
- * @since Oak 1.1
+ * @since Oak 1.3.3
*/
@Test
- public void testImportUserWithPwdProperties() throws Exception {
+ public void testImportUserWithPwdHistory() 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\">" +
@@ -155,11 +66,8 @@ public class UserImportPwExpiryTest exte
" <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:name=\"" + UserConstants.REP_PWD_HISTORY + "\" sv:type=\"String\" sv:multiple=\"true\">" +
+ " <sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value>" +
" </sv:property>" +
" </sv:node>" +
"</sv:node>";
@@ -171,55 +79,7 @@ public class UserImportPwExpiryTest exte
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());
-
- assertTrue(pwdNode.hasProperty("customProp"));
- Property custom = pwdNode.getProperty("customProp");
- assertTrue(custom.getDefinition().isProtected());
- assertEquals("abc", custom.getString());
- }
-
- /**
- * @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
- 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>";
-
- doImport(USERPATH, xml);
-
- Authorizable authorizable = getUserManager().getAuthorizable(uid);
- userNode = s.getNode(authorizable.getPath());
- assertTrue(userNode.hasNode(UserConstants.REP_PWD));
-
- pwdNode = userNode.getNode(UserConstants.REP_PWD);
- assertTrue(pwdNode.hasProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED));
+ assertTrue(pwdNode.hasProperty(UserConstants.REP_PWD_HISTORY));
+ assertEquals("{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375", pwdNode.getProperty(UserConstants.REP_PWD_HISTORY).getString());
}
}