Author: angela Date: Tue Oct 30 09:19:18 2018 New Revision: 1845206 URL: http://svn.apache.org/viewvc?rev=1845206&view=rev Log: OAK-7863 : Allow for admin password to expire
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAdminTest.java - copied, changed from r1844647, 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/UserAuthentication.java 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/UserManagerImpl.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.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/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=1845206&r1=1845205&r2=1845206&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 Tue Oct 30 09:19:18 2018 @@ -79,6 +79,8 @@ class UserAuthentication implements Auth private static final Logger log = LoggerFactory.getLogger(UserAuthentication.class); + public static final String PARAM_PASSWORD_EXPIRY_FOR_ADMIN = "passwordExpiryForAdmin"; + private final UserConfiguration config; private final Root root; private final String loginId; @@ -242,13 +244,13 @@ class UserAuthentication implements Auth } private boolean isPasswordExpired(@NotNull User user) throws RepositoryException { - // the password of the "admin" user never expires - if (user.isAdmin()) { + ConfigurationParameters params = config.getParameters(); + // unless PARAM_PASSWORD_EXPIRY_FOR_ADMIN is enabled, the password of the "admin" user never expires + if (!Utils.canHavePasswordExpired(user, params)) { return false; } boolean expired = false; - ConfigurationParameters params = config.getParameters(); int maxAge = params.getConfigValue(PARAM_PASSWORD_MAX_AGE, DEFAULT_PASSWORD_MAX_AGE); boolean forceInitialPwChange = params.getConfigValue(PARAM_PASSWORD_INITIAL_CHANGE, DEFAULT_PASSWORD_INITIAL_CHANGE); if (maxAge > 0) { 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=1845206&r1=1845205&r2=1845206&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 Tue Oct 30 09:19:18 2018 @@ -151,6 +151,12 @@ public class UserConfigurationImpl exten int passwordHistorySize() default UserConstants.PASSWORD_HISTORY_DISABLED_SIZE; @AttributeDefinition( + name = "Enable Password Expiry for Admin User", + description = "When enabled, the admin user will also be subject to password expiry. The default value is false for backwards compatibility." + ) + boolean passwordExpiryForAdmin() default false; + + @AttributeDefinition( name = "Principal Cache Expiration", description = "Optional configuration defining the number of milliseconds " + "until the principal cache expires (NOTE: currently only respected for principal resolution " + 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=1845206&r1=1845205&r2=1845206&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 Oct 30 09:19:18 2018 @@ -471,7 +471,7 @@ public class UserManagerImpl implements boolean forceInitialPwChange = forceInitialPasswordChangeEnabled(); boolean isNewUser = userTree.getStatus() == Tree.Status.NEW; - if (!UserUtil.isAdmin(config, userId) + 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, Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java?rev=1845206&r1=1845205&r2=1845206&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/Utils.java Tue Oct 30 09:19:18 2018 @@ -18,13 +18,16 @@ package org.apache.jackrabbit.oak.securi import javax.jcr.AccessDeniedException; +import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; +import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; +import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil; import org.apache.jackrabbit.util.Text; import org.jetbrains.annotations.NotNull; -class Utils { +final class Utils { private Utils() {} @@ -76,4 +79,20 @@ class Utils { } } } + + static boolean canHavePasswordExpired(@NotNull String userId, @NotNull ConfigurationParameters config) { + if (UserUtil.isAdmin(config, userId) && !config.getConfigValue(UserAuthentication.PARAM_PASSWORD_EXPIRY_FOR_ADMIN, false)) { + return false; + } else { + return true; + } + } + + static boolean canHavePasswordExpired(@NotNull User user, @NotNull ConfigurationParameters config) { + if (user.isAdmin() && !config.getConfigValue(UserAuthentication.PARAM_PASSWORD_EXPIRY_FOR_ADMIN, false)) { + return false; + } else { + return true; + } + } } Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java?rev=1845206&r1=1845205&r2=1845206&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java (original) +++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java Tue Oct 30 09:19:18 2018 @@ -16,12 +16,9 @@ */ package org.apache.jackrabbit.oak; -import static com.google.common.collect.Lists.newArrayList; - import java.util.Arrays; import java.util.List; import java.util.UUID; - import javax.jcr.Credentials; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.RepositoryException; @@ -71,6 +68,8 @@ import org.jetbrains.annotations.Nullabl import org.junit.After; import org.junit.Before; +import static com.google.common.collect.Lists.newArrayList; + /** * AbstractOakTest is the base class for oak test execution. */ @@ -108,7 +107,7 @@ public abstract class AbstractSecurityTe withEditors(oak); contentRepository = oak.createContentRepository(); - adminSession = login(getAdminCredentials()); + adminSession = createAdminSession(contentRepository); root = adminSession.getLatestRoot(); Configuration.setConfiguration(getConfiguration()); @@ -177,6 +176,11 @@ public abstract class AbstractSecurityTe return new SimpleCredentials(adminId, adminId.toCharArray()); } + @NotNull + protected ContentSession createAdminSession(@NotNull ContentRepository repository) throws LoginException, NoSuchWorkspaceException { + return repository.login(getAdminCredentials(), null); + } + protected NamePathMapper getNamePathMapper() { return namePathMapper; } Copied: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAdminTest.java (from r1844647, 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/PasswordExpiryAdminTest.java?p2=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryAdminTest.java&p1=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/PasswordExpiryTest.java&r1=1844647&r2=1845206&rev=1845206&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/PasswordExpiryAdminTest.java Tue Oct 30 09:19:18 2018 @@ -16,23 +16,30 @@ */ package org.apache.jackrabbit.oak.security.user; -import java.util.UUID; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import javax.jcr.NoSuchWorkspaceException; import javax.jcr.SimpleCredentials; +import javax.security.auth.Subject; import javax.security.auth.login.CredentialExpiredException; import javax.security.auth.login.LoginException; import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.oak.AbstractSecurityTest; +import org.apache.jackrabbit.oak.api.ContentRepository; +import org.apache.jackrabbit.oak.api.ContentSession; 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.authentication.SystemSubject; 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; @@ -42,64 +49,67 @@ import static org.junit.Assert.assertNot import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -/** - * @see <a href="https://issues.apache.org/jira/browse/OAK-1922">OAK-1922</a> - */ -public class PasswordExpiryTest extends AbstractSecurityTest { +public class PasswordExpiryAdminTest extends AbstractSecurityTest { + private User user; private String userId; @Before public void before() throws Exception { super.before(); - userId = getTestUser().getID(); + user = getUserManager(root).getAuthorizable(UserConstants.DEFAULT_ADMIN_ID, User.class); + userId = user.getID(); } + @Override protected ConfigurationParameters getSecurityConfigParameters() { - ConfigurationParameters userConfig = ConfigurationParameters.of(UserConstants.PARAM_PASSWORD_MAX_AGE, 10); + ConfigurationParameters userConfig = ConfigurationParameters.of( + UserConstants.PARAM_PASSWORD_MAX_AGE, 10, + UserAuthentication.PARAM_PASSWORD_EXPIRY_FOR_ADMIN, true); return ConfigurationParameters.of(UserConfiguration.NAME, userConfig); } - @Test - public void testCreateUser() throws Exception { - String newUserId = "newuser" + UUID.randomUUID(); - User user = null; - + @NotNull + @Override + protected ContentSession createAdminSession(@NotNull ContentRepository repository) throws LoginException, NoSuchWorkspaceException { 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(); - } + return Subject.doAs(SystemSubject.INSTANCE, new PrivilegedExceptionAction<ContentSession>() { + @Override + public ContentSession run() throws NoSuchWorkspaceException, LoginException { + return repository.login(null, null); + } + }); + } catch (PrivilegedActionException e) { + throw new RuntimeException(e); } } @Test + public void testUserNode() throws Exception { + 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)); + } + + @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); + user.changePassword(user.getID()); 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); @@ -117,7 +127,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); + 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())); @@ -131,7 +141,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); + 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())); @@ -146,11 +156,11 @@ 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); + root.getTree(user.getPath()).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 - getTestUser().changePassword(userId); + user.changePassword(userId); root.commit(); assertTrue(a.authenticate(new SimpleCredentials(userId, userId.toCharArray()))); } 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=1845206&r1=1845205&r2=1845206&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 Oct 30 09:19:18 2018 @@ -154,4 +154,10 @@ public class PasswordExpiryTest extends root.commit(); assertTrue(a.authenticate(new SimpleCredentials(userId, userId.toCharArray()))); } + + @Test + public void testByDefaultAdminHasNoPwNode() throws Exception { + 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()); + } } 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=1845206&r1=1845205&r2=1845206&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 Oct 30 09:19:18 2018 @@ -47,10 +47,11 @@ OSGi configuration. By default both feat The following configuration options are supported: -| Parameter | Type | Default | Description | -|------------------------------------- |-------- -|------------------------| -| `PARAM_PASSWORD_MAX_AGE` | int | 0 | Number of days until the password expires. | -| `PARAM_PASSWORD_INITIAL_CHANGE` | boolean | false | boolean flag to enable initial pw change. | +| Parameter | Type | Default | Description | +|-----------------------------------|---------|----------|--------------------------------------------| +| `PARAM_PASSWORD_MAX_AGE` | int | 0 | Number of days until the password expires. | +| `PARAM_PASSWORD_INITIAL_CHANGE` | boolean | false | boolean flag to enable initial pw change. | +| `PARAM_PASSWORD_EXPIRY_FOR_ADMIN` | boolean | false | flag to enable pw expiry for admin user. | Note: