From a90b8bb2586b642d50febd10cccc8718a7a6153b Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 13 Jul 2026 17:21:09 -0700 Subject: [PATCH 1/6] GitHub Issue 1300: BulkUpgradeGroupAction scoping changes (#3099) ## Rationale BulkUpgradeGroupAction is inconsistent with other actions for its permission checks ## Related Pull Requests - https://github.com/LabKey/platform/pull/7836 ## Changes - Test coverage for other projects and adding users --- .../remoteapi/BulkUpdateGroupApiTest.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/org/labkey/test/tests/remoteapi/BulkUpdateGroupApiTest.java b/src/org/labkey/test/tests/remoteapi/BulkUpdateGroupApiTest.java index bdef4aabf0..720a3f335d 100644 --- a/src/org/labkey/test/tests/remoteapi/BulkUpdateGroupApiTest.java +++ b/src/org/labkey/test/tests/remoteapi/BulkUpdateGroupApiTest.java @@ -29,6 +29,7 @@ import org.labkey.test.util.APIUserHelper; import org.labkey.test.util.ApiPermissionsHelper; import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.PermissionsHelper; import org.labkey.test.util.PermissionsHelper.PrincipalType; import java.util.ArrayList; @@ -61,11 +62,15 @@ public class BulkUpdateGroupApiTest extends BaseWebDriverTest private static Integer group1Id; private static Integer group2Id; private static final String siteGroup = "createdSiteGroup"; + private static final String OTHER_PROJECT = "BulkUpdateGroupApiTest Other Project"; + private static final String OTHER_GROUP = "otherProjectGroup"; + private static Integer otherProjectGroupId; @Override protected void doCleanup(boolean afterTest) throws TestTimeoutException { _containerHelper.deleteProject(getProjectName(), afterTest); + _containerHelper.deleteProject(OTHER_PROJECT, afterTest); deleteTestUsers(EMAIL_SUFFIX); _permissionsHelper.deleteGroup(siteGroup); } @@ -101,6 +106,9 @@ private void doSetup() user2Id = _userHelper.createUser(USER2).getUserId(); group1Id = _permissionsHelper.createProjectGroup(GROUP1, getProjectName()); group2Id = _permissionsHelper.createProjectGroup(GROUP2, getProjectName()); + + _containerHelper.createProject(OTHER_PROJECT, null); + otherProjectGroupId = _permissionsHelper.createProjectGroup(OTHER_GROUP, OTHER_PROJECT); } @Test @@ -499,6 +507,60 @@ public void testBulkUpdateAuditing() throws Exception AuditLogTest.verifyAuditEvent(this, AuditLogTest.GROUP_AUDIT_EVENT, "Comment", GROUP1, 2); } + // A project group may only be modified through the container it belongs to; targeting another project's group must be rejected. + @Test + public void testGroupFromDifferentContainerError() throws Exception + { + // otherProjectGroupId belongs to OTHER_PROJECT, but the command targets this test's project + BulkUpdateGroupCommand command = new BulkUpdateGroupCommand(otherProjectGroupId); + command.addMemberUser(USER1); + Connection connection = createDefaultConnection(); + + try + { + String message = command.execute(connection, getProjectName()).getText(); + fail("Expected CommandException modifying a group from another container\nResponse:\n" + message); + } + catch (CommandException e) + { + assertTrue("Expected cross-container error. Actual error: " + e.getMessage(), e.getMessage().contains("does not belong to this project")); + } + + _permissionsHelper.assertUserNotInGroup(USER1, OTHER_GROUP, OTHER_PROJECT, PrincipalType.USER); + } + + // Adding an existing user to a group requires only AdminPermission, but creating a brand-new user additionally requires AddUserPermission. + @Test + public void testCreateUserWithoutAddUserPermission() throws Exception + { + // A Folder Administrator has AdminPermission (so the action's @RequiresPermission passes) but lacks AddUserPermission + String folderAdmin = genTestEmail("folderadminnoadduser"); + String newUser = genTestEmail("shouldnotbecreated"); + _userHelper.createUser(folderAdmin); + _permissionsHelper.addMemberToRole(folderAdmin, PermissionsHelper.FOLDER_ADMIN_ROLE, PermissionsHelper.MemberType.user, getProjectName()); + + Connection connection = createDefaultConnection(); + connection.impersonate(folderAdmin, getProjectName()); + try + { + BulkUpdateGroupCommand command = new BulkUpdateGroupCommand(group1Id); + command.addMemberUser(USER1); // pre-existing user: should be added successfully + command.addMemberUser(newUser); // new user: folder admin lacks AddUserPermission + BulkUpdateGroupResponse response = command.execute(connection, getProjectName()); + + List errors = collectErrors(response); + assertEquals("Expected exactly one member error:\n" + String.join("\n", errors), 1, errors.size()); + assertTrue("Expected AddUserPermission error. Actual error: " + errors.get(0), errors.get(0).contains("do not have permission to create new users")); + } + finally + { + connection.stopImpersonating(); + } + + assertNull("New user was created despite the caller lacking AddUserPermission", _userHelper.getUserId(newUser)); + _permissionsHelper.assertUserInGroup(USER1, GROUP1, getProjectName(), PrincipalType.USER); + } + protected List collectErrors(BulkUpdateGroupResponse response) { Map errors = response.getErrors(); From 53c8fe3818a90053de78edaafe3aa06f9db5809d Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Tue, 14 Jul 2026 13:41:51 -0700 Subject: [PATCH 2/6] Use the project when removing members for a project group (#3104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Rationale Recent improvements to the APIs for adding or removing members from a project group mean that we should use the project as the target URL when making those changes. ## Changes - Pass in the correct project group when making changes ## Tasks 📍 - [x] Claude Code Review - ~Manual Testing~ - [x] Test Automation --- src/org/labkey/test/tests/UserPermissionsTest.java | 2 +- src/org/labkey/test/util/ApiPermissionsHelper.java | 10 +++++----- src/org/labkey/test/util/PermissionsHelper.java | 2 +- src/org/labkey/test/util/UIPermissionsHelper.java | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/org/labkey/test/tests/UserPermissionsTest.java b/src/org/labkey/test/tests/UserPermissionsTest.java index 4ec3da23c6..bd0b3d1cb8 100644 --- a/src/org/labkey/test/tests/UserPermissionsTest.java +++ b/src/org/labkey/test/tests/UserPermissionsTest.java @@ -272,7 +272,7 @@ public void testAuditLogForGroupUpdates() log("Remove user from group and verify logs"); goToProjectHome(); - permissionsHelper.removeUserFromGroup(GAMMA_SUBMITTER_GROUP_NAME, GAMMA_SUBMITTER_USER); + permissionsHelper.removeUserFromGroup(GAMMA_SUBMITTER_GROUP_NAME, GAMMA_SUBMITTER_USER, getProjectName()); verifyAuditLog("User: " + GAMMA_SUBMITTER_USER + " was deleted from Group: " + GAMMA_SUBMITTER_GROUP_NAME); } diff --git a/src/org/labkey/test/util/ApiPermissionsHelper.java b/src/org/labkey/test/util/ApiPermissionsHelper.java index 63e4e6d093..71752ce49d 100644 --- a/src/org/labkey/test/util/ApiPermissionsHelper.java +++ b/src/org/labkey/test/util/ApiPermissionsHelper.java @@ -658,12 +658,12 @@ private List getGroupNames(String project) } @Override - public void removeUserFromGroup(String groupName, String userName) + public void removeUserFromGroup(String groupName, String userName, String projectPath) { Integer groupId = getGroupId(groupName); if (groupId == null) throw new IllegalArgumentException("Attempting to remove members from non-existent site group: " + groupName); - removeMembersFromGroup(groupId, userName); + removeMembersFromGroup(groupId, projectPath, userName); } @Override @@ -672,10 +672,10 @@ public void removeUserFromSiteGroup(String groupName, String userName) Integer groupId = getSiteGroupId(groupName); if (groupId == null) throw new IllegalArgumentException("Attempting to remove members from non-existent group: " + groupName); - removeMembersFromGroup(groupId, userName); + removeMembersFromGroup(groupId, "/", userName); } - private void removeMembersFromGroup(Integer groupId, String... members) + private void removeMembersFromGroup(Integer groupId, String projectPath, String... members) { BulkUpdateGroupCommand command = new BulkUpdateGroupCommand(groupId); command.setCreateGroup(false); @@ -685,7 +685,7 @@ private void removeMembersFromGroup(Integer groupId, String... members) try { Connection connection = getConnection(); - command.execute(connection, "/"); + command.execute(connection, projectPath); } catch (IOException | CommandException e) { diff --git a/src/org/labkey/test/util/PermissionsHelper.java b/src/org/labkey/test/util/PermissionsHelper.java index 8a4bc85741..576f405495 100644 --- a/src/org/labkey/test/util/PermissionsHelper.java +++ b/src/org/labkey/test/util/PermissionsHelper.java @@ -175,7 +175,7 @@ public void deleteGroup(String groupName) @LogMethod(quiet = true) public abstract void deleteGroup(@LoggedParam String groupName, boolean failIfNotFound); - public abstract void removeUserFromGroup(String groupName, String userName); + public abstract void removeUserFromGroup(String groupName, String userName, String projectName); public abstract void removeUserFromSiteGroup(String groupName, String userName); public abstract boolean doesGroupExist(String groupName, String projectName); diff --git a/src/org/labkey/test/util/UIPermissionsHelper.java b/src/org/labkey/test/util/UIPermissionsHelper.java index b28dc66512..a59816f158 100644 --- a/src/org/labkey/test/util/UIPermissionsHelper.java +++ b/src/org/labkey/test/util/UIPermissionsHelper.java @@ -316,11 +316,11 @@ public void deleteGroup(@LoggedParam String groupName, boolean failIfNotFound) @Override public void removeUserFromSiteGroup(String groupName, String userEmail) { - removeUserFromGroup(groupName, userEmail); + removeUserFromGroup(groupName, userEmail, "/"); } @Override - public void removeUserFromGroup(String groupName, String userEmail) + public void removeUserFromGroup(String groupName, String userEmail, String projectPath) { if (!_driver.isTextPresent("Group " + groupName)) selectGroup(groupName); From 8dac5eacc034d632dac4a8e40924aa0a3b44e7a5 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Tue, 14 Jul 2026 16:00:31 -0700 Subject: [PATCH 3/6] Use the project when removing members for a project group (#3105) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Rationale Recent improvements to the APIs for adding or removing members from a project group mean that we should use the project as the target URL when making those changes. ## Changes - Pass in the correct project group when making changes ## Tasks 📍 - [x] Claude Code Review - ~Manual Testing~ - [x] Test Automation --- src/org/labkey/test/tests/UserPermissionsTest.java | 2 +- src/org/labkey/test/util/ApiPermissionsHelper.java | 10 +++++----- src/org/labkey/test/util/PermissionsHelper.java | 2 +- src/org/labkey/test/util/UIPermissionsHelper.java | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/org/labkey/test/tests/UserPermissionsTest.java b/src/org/labkey/test/tests/UserPermissionsTest.java index 3218c427fa..48ab03dc42 100644 --- a/src/org/labkey/test/tests/UserPermissionsTest.java +++ b/src/org/labkey/test/tests/UserPermissionsTest.java @@ -267,7 +267,7 @@ public void testAuditLogForGroupUpdates() log("Remove user from group and verify logs"); goToProjectHome(); - permissionsHelper.removeUserFromGroup(GAMMA_SUBMITTER_GROUP_NAME, GAMMA_SUBMITTER_USER); + permissionsHelper.removeUserFromGroup(GAMMA_SUBMITTER_GROUP_NAME, GAMMA_SUBMITTER_USER, getProjectName()); verifyAuditLog("User: " + GAMMA_SUBMITTER_USER + " was deleted from Group: " + GAMMA_SUBMITTER_GROUP_NAME); } diff --git a/src/org/labkey/test/util/ApiPermissionsHelper.java b/src/org/labkey/test/util/ApiPermissionsHelper.java index 4353faf07e..a510197c43 100644 --- a/src/org/labkey/test/util/ApiPermissionsHelper.java +++ b/src/org/labkey/test/util/ApiPermissionsHelper.java @@ -658,12 +658,12 @@ private List getGroupNames(String project) } @Override - public void removeUserFromGroup(String groupName, String userName) + public void removeUserFromGroup(String groupName, String userName, String projectPath) { Integer groupId = getGroupId(groupName); if (groupId == null) throw new IllegalArgumentException("Attempting to remove members from non-existent site group: " + groupName); - removeMembersFromGroup(groupId, userName); + removeMembersFromGroup(groupId, projectPath, userName); } @Override @@ -672,10 +672,10 @@ public void removeUserFromSiteGroup(String groupName, String userName) Integer groupId = getSiteGroupId(groupName); if (groupId == null) throw new IllegalArgumentException("Attempting to remove members from non-existent group: " + groupName); - removeMembersFromGroup(groupId, userName); + removeMembersFromGroup(groupId, "/", userName); } - private void removeMembersFromGroup(Integer groupId, String... members) + private void removeMembersFromGroup(Integer groupId, String projectPath, String... members) { BulkUpdateGroupCommand command = new BulkUpdateGroupCommand(groupId); command.setCreateGroup(false); @@ -685,7 +685,7 @@ private void removeMembersFromGroup(Integer groupId, String... members) try { Connection connection = getConnection(); - command.execute(connection, "/"); + command.execute(connection, projectPath); } catch (IOException | CommandException e) { diff --git a/src/org/labkey/test/util/PermissionsHelper.java b/src/org/labkey/test/util/PermissionsHelper.java index 3d01274dd9..319ff8d50c 100644 --- a/src/org/labkey/test/util/PermissionsHelper.java +++ b/src/org/labkey/test/util/PermissionsHelper.java @@ -161,7 +161,7 @@ public void deleteGroup(String groupName) @LogMethod(quiet = true) public abstract void deleteGroup(@LoggedParam String groupName, boolean failIfNotFound); - public abstract void removeUserFromGroup(String groupName, String userName); + public abstract void removeUserFromGroup(String groupName, String userName, String projectName); public abstract void removeUserFromSiteGroup(String groupName, String userName); public abstract boolean doesGroupExist(String groupName, String projectName); diff --git a/src/org/labkey/test/util/UIPermissionsHelper.java b/src/org/labkey/test/util/UIPermissionsHelper.java index a362b07252..eaab87422c 100644 --- a/src/org/labkey/test/util/UIPermissionsHelper.java +++ b/src/org/labkey/test/util/UIPermissionsHelper.java @@ -317,11 +317,11 @@ public void deleteGroup(@LoggedParam String groupName, boolean failIfNotFound) @Override public void removeUserFromSiteGroup(String groupName, String userEmail) { - removeUserFromGroup(groupName, userEmail); + removeUserFromGroup(groupName, userEmail, "/"); } @Override - public void removeUserFromGroup(String groupName, String userEmail) + public void removeUserFromGroup(String groupName, String userEmail, String projectPath) { if (!_driver.isTextPresent("Group " + groupName)) selectGroup(groupName); From 2015510561b185c26380bbf32493d93c6edc9a83 Mon Sep 17 00:00:00 2001 From: Binal Patel Date: Wed, 15 Jul 2026 14:57:18 -0600 Subject: [PATCH 4/6] Fix ListTest.testAuditDetailRejectsRowIdFromOtherList (#3111) - Test failure fix for list audit detail --- src/org/labkey/test/tests/list/ListTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 01352ffc90..fec469a594 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -903,7 +903,7 @@ public void testAuditDetailRejectsRowIdFromOtherList() throws Exception .setText(NAME_FIELD + "\n" + LIST_Y_ROW_VALUE) .submit(); DataRegionTable yTable = new DataRegionTable("query", getDriver()); - yTable.clickEditRow(yTable.getRowIndex(LIST_Y_ROW_VALUE)); + yTable.clickEditRow(yTable.getRowIndex(NAME_FIELD, LIST_Y_ROW_VALUE)); setFormElement(Locator.name("quf_" + NAME_FIELD), LIST_Y_ROW_EDITED); clickButton("Submit"); @@ -934,13 +934,13 @@ public void testAuditDetailRejectsRowIdFromOtherList() throws Exception private int lookupListId(Connection cn, String listName) throws Exception { - SelectRowsCommand cmd = new SelectRowsCommand("exp", "Lists"); - cmd.setColumns(List.of("RowId", "Name")); + SelectRowsCommand cmd = new SelectRowsCommand("ListManager", "ListManager"); + cmd.setColumns(List.of("ListId", "Name")); cmd.addFilter(new Filter("Name", listName, Filter.Operator.EQUAL)); SelectRowsResponse rs = cmd.execute(cn, getProjectName()); if (rs.getRows().isEmpty()) - throw new AssertionError("No exp.Lists row for " + listName); - return ((Number) rs.getRows().get(0).get("RowId")).intValue(); + throw new AssertionError("No ListManager row for " + listName); + return ((Number) rs.getRows().get(0).get("ListId")).intValue(); } private int lookupListAuditRowId(Connection cn, String listName) throws Exception From 37e176fbba7ef6bb123f92f3aaa1af30309b54d4 Mon Sep 17 00:00:00 2001 From: Dan Duffek Date: Wed, 15 Jul 2026 16:11:38 -0700 Subject: [PATCH 5/6] Update the version of LabKey API for tests (#3113) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 14a8217d69..327056d065 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ seleniumVersion=4.45.0 mockserverNettyVersion=5.15.0 -labkeySchemasTestVersion=26.3-SNAPSHOT +labkeySchemasTestVersion=26.7-SNAPSHOT From 959dac8b692b4e9f6fab70dab38897e2d45a8d2d Mon Sep 17 00:00:00 2001 From: Karl Lum Date: Mon, 20 Jul 2026 16:45:53 -0700 Subject: [PATCH 6/6] Project user page automated test. (#3121) --- .../test/tests/UserDetailsPermissionTest.java | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/UserDetailsPermissionTest.java b/src/org/labkey/test/tests/UserDetailsPermissionTest.java index 0e96de74ed..789e1d9207 100644 --- a/src/org/labkey/test/tests/UserDetailsPermissionTest.java +++ b/src/org/labkey/test/tests/UserDetailsPermissionTest.java @@ -18,7 +18,10 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.labkey.api.security.permissions.Permission; +import org.labkey.api.security.permissions.ReadPermission; import org.labkey.remoteapi.CommandException; +import org.labkey.remoteapi.CommandResponse; import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.SimpleGetCommand; import org.labkey.test.BaseWebDriverTest; @@ -29,22 +32,30 @@ import org.labkey.test.components.DomainDesignerPage; import org.labkey.test.components.domain.DomainFormPanel; import org.labkey.test.pages.query.ExecuteQueryPage; +import org.labkey.test.pages.user.ShowUsersPage; import org.labkey.test.pages.user.UpdateUserDetailsPage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.util.ApiPermissionsHelper; +import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.DataRegionTable.DataRegionFinder; import org.labkey.test.util.LogMethod; import org.labkey.test.util.PasswordUtil; import org.labkey.test.util.PortalHelper; import java.io.IOException; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.labkey.test.util.PermissionsHelper.PROJECT_ADMIN_ROLE; import static org.labkey.test.util.PermissionsHelper.READER_ROLE; import static org.labkey.test.util.PermissionsHelper.SITE_ADMIN_ROLE; @@ -57,6 +68,8 @@ public class UserDetailsPermissionTest extends BaseWebDriverTest private static final String USER_INFO_VIEWER = "user_info_viewer@usertable.test"; private static final String IMPERSONATED_USER = "impersonated_user@usertable.test"; private static final String CHECKED_USER = "checked_user@usertable.test"; + private static final String PROJECT_ADMIN = "project_admin@usertable.test"; + private static final String NON_MEMBER = "non_member@usertable.test"; private static final String EMAIL_TEST_LIST = "My Users"; private static final String CUSTOM_USER_COLUMN = "UserTablePermTest"; private static final String HIDDEN_COL_VIEW = "hiddenColView"; @@ -73,7 +86,7 @@ protected void doCleanup(boolean afterTest) throws TestTimeoutException { super.doCleanup(afterTest); - _userHelper.deleteUsers(false, USER_INFO_VIEWER, IMPERSONATED_USER, CHECKED_USER, ADMIN_USER); + _userHelper.deleteUsers(false, USER_INFO_VIEWER, IMPERSONATED_USER, CHECKED_USER, ADMIN_USER, PROJECT_ADMIN, NON_MEMBER); } @Override @@ -116,6 +129,14 @@ private void doSetup() apiPermissionsHelper.setPermissions(TEST_GROUP, READER_ROLE); apiPermissionsHelper.setSiteRoleUserPermissions(USER_INFO_VIEWER, "See User and Group Details"); + // A project administrator (no site-wide User Management permission) is used to verify that user details + // render from the Project Users page. + _userHelper.createUser(PROJECT_ADMIN, true, true); + new ApiPermissionsHelper("/" + getProjectName()).setUserPermissions(PROJECT_ADMIN, PROJECT_ADMIN_ROLE); + + // A user with no role in the project (no read access) that should never appear on the Project Users page. + _userHelper.createUser(NON_MEMBER, true, true); + impersonate(ADMIN_USER); { UpdateUserDetailsPage page = goToMyAccount().clickEdit(); @@ -208,6 +229,52 @@ public void testUserVisibilityViaContactsWebPart() assertTextPresent(CHECKED_USER, ADMIN_USER, HIDDEN_STRING, TEST_GROUP); } + @Test + public void testProjectUsers() throws Exception + { + // The list of project users will be all users with read access to the folder. + Set projectUsers = getUsersWithAccess(getProjectName(), ReadPermission.class); + assertFalse("A user with no project role must not have read access", + projectUsers.contains(NON_MEMBER)); + + // Both a site administrator and a project administrator should see the project's users and be able to open + // the details view for every one of them. + for (String admin : List.of(ADMIN_USER, PROJECT_ADMIN)) + { + impersonate(admin); + goToProjectHome(); + + log("Verify the Project Users page, impersonating " + admin); + ShowUsersPage projectUsersPage = goToProjectUsers(); + assertEquals("Project Users page displaying the wrong set of users.", + projectUsers, new HashSet<>(projectUsersPage.getUsersTable().getColumnDataAsText("Email"))); + + log("Verify " + admin + " can view the details page of every project user"); + for (String userEmail : projectUsers) + { + DataRegionTable usersTable = goToProjectUsers().getUsersTable(); + clickAndWait(usersTable.detailsLink(usersTable.getRowIndexStrict("Email", userEmail))); + assertTextPresent(userEmail, _userHelper.getDisplayNameForEmail(userEmail)); + } + + stopImpersonating(); + } + } + + /** + * Returns the set of user emails who have permission to the folder + */ + private Set getUsersWithAccess(String folderPath, Class perm) throws IOException, CommandException + { + SimpleGetCommand command = new SimpleGetCommand("user", "getUsersWithPermissions"); + command.setParameters(Map.of("permissions", perm.getName())); + CommandResponse response = command.execute(createDefaultConnection(), folderPath); + List> users = response.getProperty("users"); + + return users.stream(). + map(m -> (String) m.get("email")).collect(Collectors.toSet()); + } + @Test public void testUserVisibilityAutoCompleteApi() throws Exception {