This is an automated email from the ASF dual-hosted git repository. enorman pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git
The following commit(s) were added to refs/heads/master by this push: new a27a118 SLING-12190 provide more real MockUser password handling (#32) a27a118 is described below commit a27a11874d605793579a2f951a2097a5829d0073 Author: Eric Norman <enor...@apache.org> AuthorDate: Sun Dec 10 13:48:18 2023 -0800 SLING-12190 provide more real MockUser password handling (#32) --- .../apache/sling/testing/mock/jcr/MockUser.java | 39 ++++++++++++++--- .../sling/testing/mock/jcr/MockUserManager.java | 6 ++- .../testing/mock/jcr/MockUserManagerTest.java | 19 +++++++- .../sling/testing/mock/jcr/MockUserTest.java | 51 ++++++++++++++++++++-- 4 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockUser.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockUser.java index ad0f2bb..88ed311 100644 --- a/src/main/java/org/apache/sling/testing/mock/jcr/MockUser.java +++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockUser.java @@ -16,8 +16,9 @@ */ package org.apache.sling.testing.mock.jcr; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; import java.security.Principal; -import java.util.Arrays; import javax.jcr.Credentials; import javax.jcr.Node; @@ -27,6 +28,9 @@ import javax.jcr.SimpleCredentials; import org.apache.jackrabbit.api.security.user.Impersonation; import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal; +import org.apache.jackrabbit.oak.spi.security.user.UserConstants; +import org.apache.jackrabbit.oak.spi.security.user.UserIdCredentials; +import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -37,7 +41,6 @@ import org.slf4j.LoggerFactory; */ class MockUser extends MockAuthorizable implements User { private Logger logger = LoggerFactory.getLogger(getClass()); - private char[] pwd = {}; private boolean disabled; private String disabledReason; @@ -68,7 +71,20 @@ class MockUser extends MockAuthorizable implements User { @Override public @NotNull Credentials getCredentials() throws RepositoryException { - return new SimpleCredentials(id, pwd); + char[] pwd; + if (homeNode.hasProperty(UserConstants.REP_PASSWORD)) { + pwd = homeNode.getProperty(UserConstants.REP_PASSWORD).getString().toCharArray(); + } else { + pwd = null; + } + + Credentials creds; + if (pwd == null) { + creds = new UserIdCredentials(id); + } else { + creds = new SimpleCredentials(id, pwd); + } + return creds; } @Override @@ -81,13 +97,24 @@ class MockUser extends MockAuthorizable implements User { if (password == null) { throw new RepositoryException("Attempt to set 'null' password for user " + getID()); } - - pwd = password.toCharArray(); + try { + char[] hashedPwd = PasswordUtil.buildPasswordHash(password).toCharArray(); + homeNode.setProperty(UserConstants.REP_PASSWORD, String.valueOf(hashedPwd)); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + throw new RepositoryException("Failed to build the password hash", e); + } } @Override public void changePassword(@Nullable String password, @NotNull String oldPassword) throws RepositoryException { - if (Arrays.equals(pwd, oldPassword.toCharArray())) { + String hashedPwd; + if (homeNode.hasProperty(UserConstants.REP_PASSWORD)) { + String pwd = homeNode.getProperty(UserConstants.REP_PASSWORD).getString(); + hashedPwd = String.valueOf(pwd); + } else { + hashedPwd = null; + } + if (PasswordUtil.isSame(hashedPwd, oldPassword.toCharArray())) { changePassword(password); } else { throw new RepositoryException("old password did not match"); diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockUserManager.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockUserManager.java index f8bd17a..ca21b11 100644 --- a/src/main/java/org/apache/sling/testing/mock/jcr/MockUserManager.java +++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockUserManager.java @@ -306,7 +306,11 @@ public class MockUserManager implements UserManager { } } Node node = ensureAuthorizablePathExists(intermediatePath, principalName, authorizableNodeType); - return (User)authorizables.computeIfAbsent(userID, id -> new MockUser(id, principal, node, this)); + User user = (User)authorizables.computeIfAbsent(userID, id -> new MockUser(id, principal, node, this)); + if (password != null) { + user.changePassword(password); + } + return user; } @Override diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockUserManagerTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockUserManagerTest.java index bc03f2a..33278ea 100644 --- a/src/test/java/org/apache/sling/testing/mock/jcr/MockUserManagerTest.java +++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockUserManagerTest.java @@ -23,12 +23,15 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; import java.util.Iterator; import java.util.Set; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.SimpleCredentials; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.ValueFactory; @@ -40,6 +43,7 @@ import org.apache.jackrabbit.api.security.user.Query; import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.oak.spi.security.user.UserConstants; +import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil; import org.apache.jackrabbit.value.ValueFactoryImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -87,13 +91,19 @@ public class MockUserManagerTest { } @Test - public void testLoadAlreadyExistingAuthorizables() throws RepositoryException { + public void testLoadAlreadyExistingAuthorizables() throws RepositoryException, NoSuchAlgorithmException, UnsupportedEncodingException { Node homeNode = session.getRootNode() .addNode("home", UserConstants.NT_REP_AUTHORIZABLE_FOLDER); Node usersNode = homeNode.addNode("users", UserConstants.NT_REP_AUTHORIZABLE_FOLDER); Node testuser1 = usersNode.addNode("testuser1", UserConstants.NT_REP_USER); testuser1.setProperty(UserConstants.REP_AUTHORIZABLE_ID, "testuser1"); testuser1.setProperty(UserConstants.REP_PRINCIPAL_NAME, "testuser1"); + testuser1.setProperty(UserConstants.REP_PASSWORD, PasswordUtil.buildPasswordHash("testPwd")); + + // user with no password set - for code coverage + Node testuser2 = usersNode.addNode("testuser2", UserConstants.NT_REP_USER); + testuser2.setProperty(UserConstants.REP_AUTHORIZABLE_ID, "testuser2"); + testuser2.setProperty(UserConstants.REP_PRINCIPAL_NAME, "testuser2"); Node testsystemuser1 = usersNode.addNode("testsystemuser1", UserConstants.NT_REP_SYSTEM_USER); testsystemuser1.setProperty(UserConstants.REP_AUTHORIZABLE_ID, "testsystemuser1"); @@ -106,7 +116,12 @@ public class MockUserManagerTest { userManager.loadAlreadyExistingAuthorizables(); - assertNotNull(userManager.getAuthorizable("testuser1")); + @Nullable Authorizable authorizable = userManager.getAuthorizable("testuser1"); + assertTrue(authorizable instanceof User); + // verify password state was stored + SimpleCredentials creds = (SimpleCredentials)((User)authorizable).getCredentials(); + assertTrue(PasswordUtil.isSame(String.valueOf(creds.getPassword()), "testPwd")); + assertNotNull(userManager.getAuthorizable("testuser2")); assertNotNull(userManager.getAuthorizable("testsystemuser1")); assertNotNull(userManager.getAuthorizable("testgroup1")); } diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockUserTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockUserTest.java index 519d5fc..a53ba15 100644 --- a/src/test/java/org/apache/sling/testing/mock/jcr/MockUserTest.java +++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockUserTest.java @@ -16,13 +16,15 @@ */ package org.apache.sling.testing.mock.jcr; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; + import javax.jcr.Credentials; import javax.jcr.Node; import javax.jcr.RepositoryException; @@ -31,8 +33,11 @@ import javax.jcr.UnsupportedRepositoryOperationException; import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.oak.spi.security.user.UserConstants; +import org.apache.jackrabbit.oak.spi.security.user.UserIdCredentials; +import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil; import org.jetbrains.annotations.NotNull; import org.junit.Test; +import org.mockito.MockedStatic; import org.mockito.Mockito; import ch.qos.logback.classic.Level; @@ -114,6 +119,15 @@ public class MockUserTest extends MockAuthorizableTest<User> { assertTrue(credentials instanceof SimpleCredentials); assertEquals(authorizable.getID(), ((SimpleCredentials)credentials).getUserID()); } + @Test + public void testGetCredentialsWithNullPassword() throws RepositoryException { + // create a user with no password + authorizable = userManager.createUser("user2", null); + + @NotNull Credentials credentials = authorizable.getCredentials(); + assertTrue(credentials instanceof UserIdCredentials); + assertEquals(authorizable.getID(), ((UserIdCredentials)credentials).getUserId()); + } /** * Test method for {@link org.apache.sling.testing.mock.jcr.MockUser#getImpersonation()}. @@ -133,10 +147,33 @@ public class MockUserTest extends MockAuthorizableTest<User> { assertThrows(RepositoryException.class, () -> authorizable.changePassword(null)); } + @Test + public void testChangePasswordStringFromNull() throws RepositoryException { + // start with a null password - for code coverage + authorizable = userManager.createUser("testuser2", null); + assertThrows(RepositoryException.class, () -> authorizable.changePassword("changed", "oldPwd")); + } + + @Test + public void testChangePasswordStringWithCaughtNoSuchAlgorithmException() throws RepositoryException { + try (MockedStatic<PasswordUtil> passwordUtilMock = Mockito.mockStatic(PasswordUtil.class);) { + passwordUtilMock.when(() -> PasswordUtil.buildPasswordHash("changed")) + .thenThrow(NoSuchAlgorithmException.class); + assertThrows(RepositoryException.class, () -> authorizable.changePassword("changed")); + } + } + @Test + public void testChangePasswordStringWithCaughtUnsupportedEncodingExceptionException() throws RepositoryException { + try (MockedStatic<PasswordUtil> passwordUtilMock = Mockito.mockStatic(PasswordUtil.class);) { + passwordUtilMock.when(() -> PasswordUtil.buildPasswordHash("changed")) + .thenThrow(UnsupportedEncodingException.class); + assertThrows(RepositoryException.class, () -> authorizable.changePassword("changed")); + } + } protected void assertPassword(char [] expectedPwd) throws RepositoryException { SimpleCredentials creds = (SimpleCredentials)authorizable.getCredentials(); - assertArrayEquals(expectedPwd, creds.getPassword()); + assertTrue(PasswordUtil.isSame(new String(creds.getPassword()), expectedPwd)); } /** @@ -144,7 +181,7 @@ public class MockUserTest extends MockAuthorizableTest<User> { */ @Test public void testChangePasswordStringString() throws RepositoryException { - authorizable.changePassword("changed", ""); + authorizable.changePassword("changed", "pwd"); assertPassword("changed".toCharArray()); assertThrows(RepositoryException.class, () -> authorizable.changePassword("changed2", "wrong")); @@ -194,4 +231,12 @@ public class MockUserTest extends MockAuthorizableTest<User> { node.getProperty(UserConstants.REP_PRINCIPAL_NAME).getString()); } + @Test + @Override + public void testGetPropertyNames() throws RepositoryException { + // create a user with no password so there are no initial properties + authorizable = userManager.createUser("user2", null); + super.testGetPropertyNames(); + } + }