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 dj...@apache.org on 2015/10/07 15:44:08 UTC
svn commit: r1707304 - 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/
Author: dj
Date: Wed Oct 7 13:44:08 2015
New Revision: 1707304
URL: http://svn.apache.org/viewvc?rev=1707304&view=rev
Log:
OAK-3463: Communicate Password Change Failure Reason During Expiry + Pw History
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryException.java (with props)
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryHistoryTest.java
- copied, changed from r1707063, jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.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/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=1707304&r1=1707303&r2=1707304&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistory.java Wed Oct 7 13:44:08 2015
@@ -111,7 +111,7 @@ final class PasswordHistory implements U
*/
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.");
+ throw new PasswordHistoryException("New password is identical to the current password.");
}
Tree pwTree = getPasswordTree(userTree, false);
if (pwTree.exists()) {
@@ -119,7 +119,7 @@ final class PasswordHistory implements U
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.");
+ throw new PasswordHistoryException("New password was found in password history.");
}
}
}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryException.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryException.java?rev=1707304&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryException.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryException.java Wed Oct 7 13:44:08 2015
@@ -0,0 +1,26 @@
+/*
+ * 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 javax.jcr.nodetype.ConstraintViolationException;
+
+class PasswordHistoryException extends ConstraintViolationException {
+
+ PasswordHistoryException(String message) {
+ super(message);
+ }
+}
Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/PasswordHistoryException.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java?rev=1707304&r1=1707303&r2=1707304&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java Wed Oct 7 13:44:08 2015
@@ -168,6 +168,9 @@ class UserAuthentication implements Auth
+ newPasswordObject.getClass().getName());
}
}
+ } catch (PasswordHistoryException e) {
+ credentials.setAttribute(e.getClass().getName(), e.getMessage());
+ log.error("Failed to change password for user " + userId, e.getMessage());
} catch (RepositoryException e) {
log.error("Failed to change password for user " + userId, e.getMessage());
} catch (CommitFailedException e) {
Copied: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryHistoryTest.java (from r1707063, 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/PasswordExpiryHistoryTest.java?p2=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryHistoryTest.java&p1=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java&r1=1707063&r2=1707304&rev=1707304&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/PasswordExpiryHistoryTest.java Wed Oct 7 13:44:08 2015
@@ -16,36 +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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
-import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.Tree;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants;
-import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
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.util.TreeUtil;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction;
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider;
+import org.apache.jackrabbit.oak.spi.security.user.action.PasswordValidationAction;
import org.junit.Before;
import org.junit.Test;
+import javax.annotation.Nonnull;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.CredentialExpiredException;
+import java.util.List;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
- * @see <a href="https://issues.apache.org/jira/browse/OAK-1922">OAK-1922</a>
+ * @see <a href="https://issues.apache.org/jira/browse/OAK-3463">OAK-3463</a>
*/
-public class PasswordExpiryTest extends AbstractSecurityTest {
+public class PasswordExpiryHistoryTest extends AbstractSecurityTest {
private String userId;
@@ -57,101 +55,112 @@ public class PasswordExpiryTest extends
@Override
protected ConfigurationParameters getSecurityConfigParameters() {
- ConfigurationParameters userConfig = ConfigurationParameters.of(UserConstants.PARAM_PASSWORD_MAX_AGE, 10);
- return ConfigurationParameters.of(UserConfiguration.NAME, userConfig);
- }
-
- @Test
- public void testCreateUser() throws Exception {
- String newUserId = "newuser" + UUID.randomUUID();
- User user = null;
-
- try {
- user = getUserManager(root).createUser(newUserId, newUserId);
- root.commit();
-
- Tree pwdTree = root.getTree(user.getPath()).getChild(UserConstants.REP_PWD);
- assertTrue(pwdTree.exists());
- assertTrue(TreeUtil.isNodeType(pwdTree, UserConstants.NT_REP_PASSWORD, root.getTree(NodeTypeConstants.NODE_TYPES_PATH)));
-
- ReadOnlyNodeTypeManager ntMgr = ReadOnlyNodeTypeManager.getInstance(root, getNamePathMapper());
- assertTrue(ntMgr.getDefinition(pwdTree.getParent(), pwdTree).isProtected());
-
- PropertyState property = pwdTree.getProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED);
- assertNotNull(property);
- assertEquals(Type.LONG, property.getType());
- assertTrue(property.getValue(Type.LONG, 0) > 0);
-
- // protected properties must not be exposed by User#hasProperty
- assertFalse(user.hasProperty(UserConstants.REP_PWD + "/" + UserConstants.REP_PASSWORD_LAST_MODIFIED));
- } finally {
- if (user != null) {
- user.remove();
- root.commit();
+ final PasswordValidationAction pwAction = new PasswordValidationAction();
+ pwAction.init(null, ConfigurationParameters.of(
+ PasswordValidationAction.CONSTRAINT, "^.*(?=.{4,}).*"
+ ));
+ final AuthorizableActionProvider actionProvider = new AuthorizableActionProvider() {
+ @Nonnull
+ @Override
+ public List<? extends AuthorizableAction> getAuthorizableActions(@Nonnull SecurityProvider securityProvider) {
+ return ImmutableList.of(pwAction);
}
- }
- }
-
- @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);
- 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);
- long newModTime = p2.getValue(Type.LONG, 0);
- assertTrue(newModTime > oldModTime);
- }
+ };
- @Test
- public void testAuthenticatePasswordExpiredNewUser() throws Exception {
- Authentication a = new UserAuthentication(getUserConfiguration(), root, userId);
- // during user creation pw last modified is set, thus it shouldn't expire
- a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+ ConfigurationParameters userConfig = ConfigurationParameters.of(
+ ImmutableMap.of(
+ UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, actionProvider,
+ UserConstants.PARAM_PASSWORD_MAX_AGE, 10,
+ UserConstants.PARAM_PASSWORD_HISTORY_SIZE, 10
+ )
+ );
+ return ConfigurationParameters.of(UserConfiguration.NAME, userConfig);
}
@Test
- public void testAuthenticatePasswordExpired() throws Exception {
+ public void testAuthenticatePasswordExpiredAndSame() throws Exception {
+ User user = getTestUser();
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);
+ root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
root.commit();
try {
a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
fail("Credentials should be expired");
} catch (CredentialExpiredException e) {
- // success
+ // success, credentials are expired
+
+ // try to change password to the same one, this should fail due pw history
+ SimpleCredentials pwChangeCreds = new SimpleCredentials(userId, userId.toCharArray());
+ try {
+ pwChangeCreds.setAttribute(UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD, user.getID());
+ a.authenticate(pwChangeCreds);
+ fail("User password changed in spite of enabled pw history");
+ } catch (CredentialExpiredException c) {
+ // success, pw found in history
+ Object attr = pwChangeCreds.getAttribute(PasswordHistoryException.class.getName());
+ assertEquals(
+ "credentials should contain pw change failure reason",
+ "New password is identical to the current password.",
+ attr);
+ }
}
}
@Test
- public void testAuthenticateBeforePasswordExpired() throws Exception {
+ public void testAuthenticatePasswordExpiredAndInHistory() throws Exception {
+ User user = getTestUser();
+ user.changePassword("pw12345678");
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);
+ root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
root.commit();
try {
- a.authenticate(new SimpleCredentials(userId, "wrong".toCharArray()));
+ a.authenticate(new SimpleCredentials(userId, "pw12345678".toCharArray()));
+ fail("Credentials should be expired");
} catch (CredentialExpiredException e) {
- fail("Login should fail before expiry");
- } catch (LoginException e) {
- // success - userId/pw mismatch takes precedence over expiry
+ // success, credentials are expired
+
+ // try to change password to the same one, this should fail due pw history
+ SimpleCredentials pwChangeCreds = new SimpleCredentials(userId, "pw12345678".toCharArray());
+ try {
+ pwChangeCreds.setAttribute(UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD, user.getID());
+ a.authenticate(pwChangeCreds);
+ fail("User password changed in spite of enabled pw history");
+ } catch (CredentialExpiredException c) {
+ // success, pw found in history
+ Object attr = pwChangeCreds.getAttribute(PasswordHistoryException.class.getName());
+ assertEquals(
+ "credentials should contain pw change failure reason",
+ "New password was found in password history.",
+ attr);
+ }
}
}
@Test
- public void testAuthenticatePasswordExpiredChangePassword() throws Exception {
+ public void testAuthenticatePasswordExpiredAndValidationFailure() throws Exception {
+ User user = getTestUser();
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);
+ root.getTree(user.getPath()).getChild(UserConstants.REP_PWD).setProperty(UserConstants.REP_PASSWORD_LAST_MODIFIED, 0);
root.commit();
+ try {
+ a.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+ fail("Credentials should be expired");
+ } catch (CredentialExpiredException e) {
+ // success, credentials are expired
- // changing the password should reset the pw last mod and the pw no longer be expired
- getTestUser().changePassword(userId);
- root.commit();
- assertTrue(a.authenticate(new SimpleCredentials(userId, userId.toCharArray())));
+ // try to change password to the same one, this should fail due pw history
+ SimpleCredentials pwChangeCreds = new SimpleCredentials(userId, userId.toCharArray());
+ try {
+ pwChangeCreds.setAttribute(UserConstants.CREDENTIALS_ATTRIBUTE_NEWPASSWORD, "2");
+ a.authenticate(pwChangeCreds);
+ fail("User password changed in spite of expected validation failure");
+ } catch (CredentialExpiredException c) {
+ // success, pw found in history
+ assertNull(pwChangeCreds.getAttribute(PasswordHistoryException.class.getName()));
+ }
+ }
}
}
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=1707304&r1=1707303&r2=1707304&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 Wed Oct 7 13:44:08 2015
@@ -159,3 +159,15 @@ This way the user can change the passwor
This method of changing password via the normal login call only works if a
user's password is in fact expired and cannot be used for regular password
changes (attribute is ignored, use _User#changePassword_ directly instead).
+
+Should the [Password History feature](history.html) be enabled, and - for the
+above password change - a password already in the history be used, the change
+will fail and the login still throw a _CredentialExpiredException_. In order
+for consumers of the exception to become aware that the credentials are
+still considered expired, and that the password was not changed due to the
+new password having been found in the password history, the credentials object
+is fitted with an additional attribute with name _PasswordHistoryException_.
+This attribute may contain the following two values:
+
+_"New password was found in password history."_ or
+_""New password is identical to the current password."_