From cf01ed95c2a0cdffe363b9a05692844f6ca992eb Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 18 Jun 2025 10:00:50 -0700 Subject: [PATCH 1/3] Test more bad credentials messages --- .../test/tests/core/login/PasswordTest.java | 30 +++++++---- src/org/labkey/test/util/APIUserHelper.java | 53 +++++++++++++------ 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/org/labkey/test/tests/core/login/PasswordTest.java b/src/org/labkey/test/tests/core/login/PasswordTest.java index 5fea5c0fda..94010eb349 100644 --- a/src/org/labkey/test/tests/core/login/PasswordTest.java +++ b/src/org/labkey/test/tests/core/login/PasswordTest.java @@ -33,6 +33,7 @@ import org.labkey.test.pages.core.login.DatabaseAuthConfigureDialog; import org.labkey.test.pages.core.login.LoginConfigurePage; import org.labkey.test.params.login.DatabaseAuthenticationProvider; +import org.labkey.test.util.APIUserHelper; import org.labkey.test.util.LogMethod; import org.labkey.test.util.LoggedParam; import org.labkey.test.util.core.login.DbLoginUtils; @@ -256,7 +257,11 @@ public void testPasswordParameter() @Test public void testChooseNewPasswordMessages() throws IOException { - // Hold an admin connection open, allowing us to reset the config regardless of what happens during the test + // Test bad API key + signOut(); + signInShouldFailUiAndApi("apikey", "abc123", "The API key you provided is invalid."); + + // Hold an admin API connection open, allowing us to reset the config without the browser session interfering Connection adminConnection = WebTestHelper.getRemoteApiConnection(); DbLoginProperties savedProperties = DbLoginUtils.getDbLoginConfig(adminConnection); @@ -268,25 +273,28 @@ public void testChooseNewPasswordMessages() throws IOException ); // Set a weak password - signOut(); String resetUrl = userInitiatePasswordReset(USER); beginAt(resetUrl); new SetPasswordForm(getDriver()) .setNewPassword(WEAK_PASSWORD) .clickSubmit(); - // Test bogus password first - signOut(); - signInShouldFail("bogus", "The email address and password you entered did not match any accounts on file."); - signIn(USER, WEAK_PASSWORD); + // Test bogus password signOut(); + signInShouldFailUiAndApi(USER, "bogus", "The email address and password you entered did not match any accounts on file."); + + // Test deactivated user + APIUserHelper helper = new APIUserHelper(() -> adminConnection); + helper.deactivateUsers(_userId); + signInShouldFailUiAndApi(USER, WEAK_PASSWORD, "Your account has been deactivated."); + helper.activateUsers(_userId); // Change the configuration and test password that no longer meets complexity requirements DbLoginUtils.setDbLoginConfig(adminConnection, PasswordStrength.Strong, PasswordExpiration.Never ); - signInShouldFail(WEAK_PASSWORD, "Your password does not meet the complexity requirements; please choose a new password."); + signInShouldFailUiAndApi(USER, WEAK_PASSWORD, "Your password does not meet the complexity requirements; please choose a new password."); String strongPassword = VERY_STRONG_PASSWORD + "!"; changeInvalidPassword(WEAK_PASSWORD, strongPassword); @@ -297,7 +305,7 @@ public void testChooseNewPasswordMessages() throws IOException ); // Wait six seconds for expiration sleep(6000); - signInShouldFail(strongPassword, "Your password has expired; please choose a new password."); + signInShouldFailUiAndApi(USER, strongPassword, "Your password has expired; please choose a new password."); changeInvalidPassword(strongPassword, VERY_STRONG_PASSWORD + "@"); } finally @@ -307,10 +315,10 @@ public void testChooseNewPasswordMessages() throws IOException } // Attempt to sign in via UI and API, expecting both to fail with the specified message - private void signInShouldFail(String password, String expectedMessage) throws IOException + private void signInShouldFailUiAndApi(String email, String password, String expectedMessage) throws IOException { - signInShouldFail(USER, password, expectedMessage); - Connection userConnection = new Connection(WebTestHelper.getBaseURL(), USER, password); + signInShouldFail(email, password, expectedMessage); + Connection userConnection = new Connection(WebTestHelper.getBaseURL(), email, password); EnsureLoginCommand ensureLoginCommand = new EnsureLoginCommand(); try { diff --git a/src/org/labkey/test/util/APIUserHelper.java b/src/org/labkey/test/util/APIUserHelper.java index 9f8eac0b84..7bed22fa47 100644 --- a/src/org/labkey/test/util/APIUserHelper.java +++ b/src/org/labkey/test/util/APIUserHelper.java @@ -15,6 +15,7 @@ */ package org.labkey.test.util; +import org.apache.commons.lang3.ArrayUtils; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.core5.http.message.BasicNameValuePair; @@ -27,9 +28,9 @@ import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.PostCommand; import org.labkey.remoteapi.SimpleGetCommand; +import org.labkey.remoteapi.SimplePostCommand; import org.labkey.remoteapi.security.CreateUserCommand; import org.labkey.remoteapi.security.CreateUserResponse; -import org.labkey.remoteapi.security.DeleteUserCommand; import org.labkey.remoteapi.security.GetUsersCommand; import org.labkey.remoteapi.security.GetUsersResponse; import org.labkey.remoteapi.security.GetUsersResponse.UserInfo; @@ -52,7 +53,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; public class APIUserHelper extends AbstractUserHelper { @@ -222,31 +222,21 @@ protected void _deleteUser(String userEmail) deleteUsers(false, userEmail); } - private void deleteUser(@NotNull Integer userId) - { - DeleteUserCommand command = new DeleteUserCommand(userId); - try - { - command.execute(connectionSupplier.get(), "/"); - } - catch (IOException|CommandException e) - { - throw new RuntimeException(e); - } - } - @Override protected void _deleteUsers(boolean failIfNotFound, String... userEmails) { Map userIds = getUserIds(Arrays.asList(userEmails), true); + ArrayList idsToDelete = new ArrayList<>(); for (String userEmail : new HashSet<>(Arrays.asList(userEmails))) { Integer userId = userIds.get(userEmail); if (failIfNotFound) - assertTrue(userEmail + " was not present", userId != null); + assertNotNull(userEmail + " was not present", userId); if (userId != null) - deleteUser(userId); + idsToDelete.add(userId); } + int[] idsArray = ArrayUtils.toPrimitive(idsToDelete.toArray(new Integer[0])); + deleteUsers(idsArray); } private static final Pattern regEmailVerification = Pattern.compile("verification=([A-Za-z0-9]+)"); @@ -289,6 +279,35 @@ public String setInitialPassword(int userId) throw new RuntimeException(e); } } + + public void deleteUsers(int... userIds) + { + updateUsersState(false, true, userIds); + } + + public void deactivateUsers(int... userIds) + { + updateUsersState(false, false, userIds); + } + + public void activateUsers(int... userIds) + { + updateUsersState(true, false, userIds); + } + + private void updateUsersState(boolean activate, boolean delete, int... userIds) + { + SimplePostCommand updateUsers = new SimplePostCommand("user", "updateUsersStateApi"); + updateUsers.setJsonObject(new JSONObject(Map.of("userId",userIds, "activate", activate, "delete", delete))); + try + { + updateUsers.execute(connectionSupplier.get(), null); + } + catch (IOException | CommandException e) + { + throw new RuntimeException(e); + } + } } class SetPasswordCommand extends PostCommand From 1cf1eb135afeb20f8cd175c48d4ebb9d00f2d854 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 18 Jun 2025 11:47:35 -0700 Subject: [PATCH 2/3] Back to old deleteUser() approach for now --- src/org/labkey/test/util/APIUserHelper.java | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/test/util/APIUserHelper.java b/src/org/labkey/test/util/APIUserHelper.java index 7bed22fa47..d3ca0eebe5 100644 --- a/src/org/labkey/test/util/APIUserHelper.java +++ b/src/org/labkey/test/util/APIUserHelper.java @@ -15,7 +15,6 @@ */ package org.labkey.test.util; -import org.apache.commons.lang3.ArrayUtils; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.core5.http.message.BasicNameValuePair; @@ -31,6 +30,7 @@ import org.labkey.remoteapi.SimplePostCommand; import org.labkey.remoteapi.security.CreateUserCommand; import org.labkey.remoteapi.security.CreateUserResponse; +import org.labkey.remoteapi.security.DeleteUserCommand; import org.labkey.remoteapi.security.GetUsersCommand; import org.labkey.remoteapi.security.GetUsersResponse; import org.labkey.remoteapi.security.GetUsersResponse.UserInfo; @@ -222,21 +222,32 @@ protected void _deleteUser(String userEmail) deleteUsers(false, userEmail); } + private void deleteUser(@NotNull Integer userId) + { + DeleteUserCommand command = new DeleteUserCommand(userId); + try + { + command.execute(connectionSupplier.get(), "/"); + } + catch (IOException|CommandException e) + { + throw new RuntimeException(e); + } + } + @Override protected void _deleteUsers(boolean failIfNotFound, String... userEmails) { Map userIds = getUserIds(Arrays.asList(userEmails), true); - ArrayList idsToDelete = new ArrayList<>(); + // TODO: instead of calling deleteUser() one-by-one, should build an array of userIds and call deleteUsers(int...) for (String userEmail : new HashSet<>(Arrays.asList(userEmails))) { Integer userId = userIds.get(userEmail); if (failIfNotFound) assertNotNull(userEmail + " was not present", userId); if (userId != null) - idsToDelete.add(userId); + deleteUser(userId); } - int[] idsArray = ArrayUtils.toPrimitive(idsToDelete.toArray(new Integer[0])); - deleteUsers(idsArray); } private static final Pattern regEmailVerification = Pattern.compile("verification=([A-Za-z0-9]+)"); @@ -298,7 +309,7 @@ public void activateUsers(int... userIds) private void updateUsersState(boolean activate, boolean delete, int... userIds) { SimplePostCommand updateUsers = new SimplePostCommand("user", "updateUsersStateApi"); - updateUsers.setJsonObject(new JSONObject(Map.of("userId",userIds, "activate", activate, "delete", delete))); + updateUsers.setJsonObject(new JSONObject(Map.of("userId", userIds, "activate", activate, "delete", delete))); try { updateUsers.execute(connectionSupplier.get(), null); From d5a1a37f14ceef11f00622a1d159686c4e09d46a Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 18 Jun 2025 14:36:31 -0700 Subject: [PATCH 3/3] Try using bulk delete again --- src/org/labkey/test/util/APIUserHelper.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/util/APIUserHelper.java b/src/org/labkey/test/util/APIUserHelper.java index d3ca0eebe5..311b953b6c 100644 --- a/src/org/labkey/test/util/APIUserHelper.java +++ b/src/org/labkey/test/util/APIUserHelper.java @@ -15,6 +15,7 @@ */ package org.labkey.test.util; +import org.apache.commons.lang3.ArrayUtils; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.core5.http.message.BasicNameValuePair; @@ -239,15 +240,17 @@ private void deleteUser(@NotNull Integer userId) protected void _deleteUsers(boolean failIfNotFound, String... userEmails) { Map userIds = getUserIds(Arrays.asList(userEmails), true); - // TODO: instead of calling deleteUser() one-by-one, should build an array of userIds and call deleteUsers(int...) + List validUserIds = new ArrayList<>(); for (String userEmail : new HashSet<>(Arrays.asList(userEmails))) { Integer userId = userIds.get(userEmail); if (failIfNotFound) assertNotNull(userEmail + " was not present", userId); if (userId != null) - deleteUser(userId); + validUserIds.add(userId); } + if (!validUserIds.isEmpty()) + deleteUsers(ArrayUtils.toPrimitive(validUserIds.toArray(new Integer[0]))); } private static final Pattern regEmailVerification = Pattern.compile("verification=([A-Za-z0-9]+)");