From db54a4ee8c756e079284f74517c7178efef23f68 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 21 Jan 2026 09:47:54 -0800 Subject: [PATCH 1/3] Expand TroubleshooterRoleTest --- .../test/tests/AbstractAdminConsoleTest.java | 3 +- .../tests/AdminConsoleNavigationTest.java | 11 ++-- .../core/admin/CspResourceHostsTest.java | 11 ++-- .../ImpersonatingTroubleshooterRoleTest.java | 6 +-- .../core/security/TroubleshooterRoleTest.java | 51 +++++++++++++++---- .../test/util/ApiPermissionsHelper.java | 24 ++++----- .../labkey/test/util/PermissionsHelper.java | 1 + 7 files changed, 71 insertions(+), 36 deletions(-) diff --git a/src/org/labkey/test/tests/AbstractAdminConsoleTest.java b/src/org/labkey/test/tests/AbstractAdminConsoleTest.java index 76b431308d..4a53be921a 100644 --- a/src/org/labkey/test/tests/AbstractAdminConsoleTest.java +++ b/src/org/labkey/test/tests/AbstractAdminConsoleTest.java @@ -25,6 +25,7 @@ import java.util.List; import static org.labkey.test.util.PermissionsHelper.APP_ADMIN_ROLE; +import static org.labkey.test.util.PermissionsHelper.TROUBLESHOOTER_ROLE; public abstract class AbstractAdminConsoleTest extends BaseWebDriverTest { @@ -79,7 +80,7 @@ protected void createTestUsers() int troubleshooterId = _userHelper.createUser(TROUBLESHOOTER_USER, true, false).getUserId(); setInitialPassword(troubleshooterId); - apiPermissionsHelper.addMemberToRole(TROUBLESHOOTER_USER, "Troubleshooter", PermissionsHelper.MemberType.user, "/"); + apiPermissionsHelper.addMemberToRole(TROUBLESHOOTER_USER, TROUBLESHOOTER_ROLE, PermissionsHelper.MemberType.user, "/"); } @Override diff --git a/src/org/labkey/test/tests/AdminConsoleNavigationTest.java b/src/org/labkey/test/tests/AdminConsoleNavigationTest.java index 26e796b7e8..546f12dbd2 100644 --- a/src/org/labkey/test/tests/AdminConsoleNavigationTest.java +++ b/src/org/labkey/test/tests/AdminConsoleNavigationTest.java @@ -33,12 +33,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.labkey.test.util.PermissionsHelper.TROUBLESHOOTER_ROLE; @Category({Git.class}) @BaseWebDriverTest.ClassTimeout(minutes = 6) public class AdminConsoleNavigationTest extends BaseWebDriverTest { - private static final String TROUBLESHOOTER = "troubleshooter@adminconsolelinks.test"; + private static final String TROUBLESHOOTER_USER = "troubleshooter@adminconsolelinks.test"; private static final String NON_ADMIN = "nonadmin@adminconsolelinks.test"; public ApiPermissionsHelper _apiPermissionsHelper = new ApiPermissionsHelper(this); @@ -52,8 +53,8 @@ public static void setupProject() private void doSetup() { - _userHelper.createUser(TROUBLESHOOTER); - _apiPermissionsHelper.addMemberToRole(TROUBLESHOOTER, "Troubleshooter", PermissionsHelper.MemberType.user, "/"); + _userHelper.createUser(TROUBLESHOOTER_USER); + _apiPermissionsHelper.addMemberToRole(TROUBLESHOOTER_USER, TROUBLESHOOTER_ROLE, PermissionsHelper.MemberType.user, "/"); _userHelper.createUser(NON_ADMIN); } @@ -61,7 +62,7 @@ private void doSetup() @Override protected void doCleanup(boolean afterTest) { - _userHelper.deleteUsers(false, TROUBLESHOOTER, NON_ADMIN); + _userHelper.deleteUsers(false, TROUBLESHOOTER_USER, NON_ADMIN); } @Test @@ -120,7 +121,7 @@ public void testTroubleshooterLinkAccess() "Profiler" //Profiler can be edited by the troubleshooter )); ShowAdminPage adminConsole = goToAdminConsole(); - impersonate(TROUBLESHOOTER); + impersonate(TROUBLESHOOTER_USER); Map linkHrefs = new LinkedHashMap<>(); List troubleshooterLinks = adminConsole.getAllAdminConsoleLinks(); assertTrue(String.format("Failed sanity check. Only found %s admin links. There should be more.", troubleshooterLinks.size()), troubleshooterLinks.size() > 10); diff --git a/src/org/labkey/test/tests/core/admin/CspResourceHostsTest.java b/src/org/labkey/test/tests/core/admin/CspResourceHostsTest.java index 7b00b7fac4..59ede47666 100644 --- a/src/org/labkey/test/tests/core/admin/CspResourceHostsTest.java +++ b/src/org/labkey/test/tests/core/admin/CspResourceHostsTest.java @@ -27,19 +27,20 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.labkey.test.util.PermissionsHelper.MemberType.user; +import static org.labkey.test.util.PermissionsHelper.TROUBLESHOOTER_ROLE; @Category({Daily.class}) public class CspResourceHostsTest extends BaseWebDriverTest { private static final String APP_ADMIN = "csp_app_admin@cspresourcehoststest.test"; - private static final String TROUBLESHOOTER = "csp_troubleshooter@cspresourcehoststest.test"; + private static final String TROUBLESHOOTER_USER = "csp_troubleshooter@cspresourcehoststest.test"; private final CspConfigHelper _cspConfigHelper = new CspConfigHelper(this); @Override protected void doCleanup(boolean afterTest) { - _userHelper.deleteUsers(afterTest, APP_ADMIN, TROUBLESHOOTER); + _userHelper.deleteUsers(afterTest, APP_ADMIN, TROUBLESHOOTER_USER); } @BeforeClass @@ -53,10 +54,10 @@ public static void setupProject() private void doSetup() { _userHelper.createUser(APP_ADMIN); - _userHelper.createUser(TROUBLESHOOTER); + _userHelper.createUser(TROUBLESHOOTER_USER); ApiPermissionsHelper apiPermissionsHelper = new ApiPermissionsHelper(this); apiPermissionsHelper.addUserAsAppAdmin(APP_ADMIN); - apiPermissionsHelper.addMemberToRole(TROUBLESHOOTER, "Troubleshooter", user, null); + apiPermissionsHelper.addMemberToRole(TROUBLESHOOTER_USER, TROUBLESHOOTER_ROLE, user, null); } @Before @@ -84,7 +85,7 @@ public void testTroubleshooterPermissions() throws Exception checker().verifyEquals("Defined directives", expectedHosts, externalSourcesPage.getExistingHosts()); checker().screenShotIfNewError("site_admin_csp"); - impersonate(TROUBLESHOOTER); + impersonate(TROUBLESHOOTER_USER); externalSourcesPage = ShowAdminPage.beginAt(this).clickAllowedExternalResourceHosts(); buttons = getTexts(Locator.lkButton().findElements(getDriver())); diff --git a/src/org/labkey/test/tests/core/security/ImpersonatingTroubleshooterRoleTest.java b/src/org/labkey/test/tests/core/security/ImpersonatingTroubleshooterRoleTest.java index 2844065604..3b50fe654c 100644 --- a/src/org/labkey/test/tests/core/security/ImpersonatingTroubleshooterRoleTest.java +++ b/src/org/labkey/test/tests/core/security/ImpersonatingTroubleshooterRoleTest.java @@ -70,7 +70,7 @@ public void testModifyPrivilegedPermission() throws Exception public void testAdminConsoleVisibility() { signOut(); - signIn(TROUBLESHOOTER); + signIn(TROUBLESHOOTER_USER); log("Verify permissions from troubleshooter"); verifySitePermissionSetting(false); @@ -81,12 +81,12 @@ public void testAdminConsoleVisibility() private ApiPermissionsHelper apiAsTroubleshooter() { - return new ApiPermissionsHelper(this, () -> new Connection(WebTestHelper.getBaseURL(), TROUBLESHOOTER, PasswordUtil.getPassword())); + return new ApiPermissionsHelper(this, () -> new Connection(WebTestHelper.getBaseURL(), TROUBLESHOOTER_USER, PasswordUtil.getPassword())); } private ApiPermissionsHelper apiAsImpersonatingSiteAdmin() throws IOException, CommandException { - Connection connection = new Connection(WebTestHelper.getBaseURL(), TROUBLESHOOTER, PasswordUtil.getPassword()); + Connection connection = new Connection(WebTestHelper.getBaseURL(), TROUBLESHOOTER_USER, PasswordUtil.getPassword()); new ImpersonateRolesCommand(toRole(SITE_ADMIN_ROLE)).execute(connection, "/"); return new ApiPermissionsHelper(this, () -> connection); } diff --git a/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java b/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java index 807c435bed..14bfd0f7a7 100644 --- a/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java +++ b/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java @@ -12,21 +12,24 @@ import org.labkey.test.pages.core.admin.ShowAdminPage; import org.labkey.test.pages.core.admin.ShowAuditLogPage; import org.labkey.test.params.list.IntListDefinition; +import org.labkey.test.util.AbstractDataRegionExportOrSignHelper; import org.labkey.test.util.ApiPermissionsHelper; +import org.labkey.test.util.DataRegionExportHelper; import org.labkey.test.util.DataRegionTable; -import org.labkey.test.util.PermissionsHelper; +import org.labkey.test.util.SummaryStatisticsHelper; import java.io.File; import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertTrue; +import static org.labkey.test.util.PermissionsHelper.TROUBLESHOOTER_ROLE; @Category({Git.class}) @BaseWebDriverTest.ClassTimeout(minutes = 4) public class TroubleshooterRoleTest extends BaseWebDriverTest { - protected static final String TROUBLESHOOTER = "troubleshooter@troubleshooter.test"; + protected static final String TROUBLESHOOTER_USER = "troubleshooter@troubleshooter.test"; protected int _troubleShooterId; @BeforeClass @@ -39,30 +42,30 @@ public static void setupProject() @Override protected void doCleanup(boolean afterTest) throws TestTimeoutException { - _userHelper.deleteUsers(false, TROUBLESHOOTER); + _userHelper.deleteUsers(false, TROUBLESHOOTER_USER); _containerHelper.deleteProject(getProjectName(), afterTest); } protected void doSetup() { - _troubleShooterId = _userHelper.createUser(TROUBLESHOOTER).getUserId(); + _troubleShooterId = _userHelper.createUser(TROUBLESHOOTER_USER).getUserId(); ApiPermissionsHelper apiPermissionsHelper = new ApiPermissionsHelper(this); - apiPermissionsHelper.addMemberToRole(TROUBLESHOOTER, getRole(), PermissionsHelper.MemberType.user,"/"); + apiPermissionsHelper.addMemberToRole(_troubleShooterId, getRole(), "/"); _containerHelper.createProject(getProjectName()); } protected String getRole() { - return "Troubleshooter"; + return TROUBLESHOOTER_ROLE; } @Test public void testAuditLogsIsAccessible() throws Exception { - // Ensure that there is at least on event to see + // Ensure that there is at least one event to see new IntListDefinition("AuditList", "id").create(createDefaultConnection(), getProjectName()); - impersonate(TROUBLESHOOTER); + impersonate(TROUBLESHOOTER_USER); ShowAdminPage showAdminPage = goToAdminConsole().goToSettingsSection(); log("Verifying audit log link is present"); @@ -82,7 +85,7 @@ public void testAuditLogsIsAccessible() throws Exception @Test public void testAdminConsoleVisibility() { - impersonate(TROUBLESHOOTER); + impersonate(TROUBLESHOOTER_USER); log("Verify permissions from troubleshooter"); verifySitePermissionSetting(false); @@ -100,7 +103,7 @@ public void testAdminConsoleVisibility() @Test public void testAllAuditTableVisibility() { - impersonate(TROUBLESHOOTER); + impersonate(TROUBLESHOOTER_USER); ShowAdminPage showAdminPage = goToAdminConsole().goToSettingsSection(); log("Verify the export file is non empty"); @@ -132,6 +135,34 @@ protected void verifySitePermissionSetting(boolean canSave) isElementPresent(Locator.button("Save and Finish"))); } + // Verifications for GitHub Issue #785 - Troubleshooters should have read access in the root (but not elsewhere) + @Test + public void testQueryAccessInRoot() + { + goToAdminConsole(); + impersonate(TROUBLESHOOTER_USER); + + // Verify that Troubleshooters can access the schema browser and view an arbitrary query in the root + goToSchemaBrowser(); + DataRegionTable dataRegionTable = viewQueryData("core", "Modules"); + int rowCount = dataRegionTable.getDataRowCount(); + assertTrue(rowCount > 6); + + // Verify that basic summary statistics work + dataRegionTable.setSummaryStatistic("Name", SummaryStatisticsHelper.BASE_STAT_COUNT, String.valueOf(rowCount)); + + // Verify that exports using POST work + DataRegionExportHelper exportHelper = new DataRegionExportHelper(dataRegionTable); + exportHelper.exportExcel(AbstractDataRegionExportOrSignHelper.ExcelFileType.XLSX); + exportHelper.exportScript(DataRegionExportHelper.ScriptExportType.JAVA); + + // Troubleshooters should NOT have read access outside the root + goToProjectHome(); + assertTextPresent("User does not have permission to perform this operation."); + goToSchemaBrowser(); + assertTextPresent("User does not have permission to perform this operation."); + } + @Override protected String getProjectName() { diff --git a/src/org/labkey/test/util/ApiPermissionsHelper.java b/src/org/labkey/test/util/ApiPermissionsHelper.java index 4353faf07e..63e4e6d093 100644 --- a/src/org/labkey/test/util/ApiPermissionsHelper.java +++ b/src/org/labkey/test/util/ApiPermissionsHelper.java @@ -444,11 +444,16 @@ public void addMemberToRole(String userOrGroupName, String roleName, MemberType } public void addMemberToRole(String userOrGroupName, String roleName, MemberType memberType, String container) + { + Integer principalId = getPrincipalId(userOrGroupName, memberType, container); + addMemberToRole(principalId, roleName, container); + } + + public void addMemberToRole(Integer principalId, String roleName, String container) { AddAssignmentCommand command = new AddAssignmentCommand(); Connection connection = getConnection(); - Integer principalId = getPrincipalId(userOrGroupName, memberType, container); command.setPrincipalId(principalId); command.setRoleClassName(toRole(roleName)); @@ -464,22 +469,17 @@ public void addMemberToRole(String userOrGroupName, String roleName, MemberType public void addMemberToRoles(String userOrGroupName, List roleNames, MemberType memberType) { - roleNames.forEach(roleName -> {addMemberToRole(userOrGroupName, roleName, memberType);}); + roleNames.forEach(roleName -> addMemberToRole(userOrGroupName, roleName, memberType)); } protected Integer getPrincipalId(String userOrGroupName, MemberType principalType, String project) { - switch (principalType) + return switch (principalType) { - case user: - return getUserId(userOrGroupName); - case group: - return getProjectGroupId(userOrGroupName, project); - case siteGroup: - return getSiteGroupId(userOrGroupName); - default: - throw new IllegalArgumentException("Unknown principal type: " + principalType); - } + case user -> getUserId(userOrGroupName); + case group -> getProjectGroupId(userOrGroupName, project); + case siteGroup -> getSiteGroupId(userOrGroupName); + }; } @Override diff --git a/src/org/labkey/test/util/PermissionsHelper.java b/src/org/labkey/test/util/PermissionsHelper.java index 64c4ff6b7b..cf1ba40fa0 100644 --- a/src/org/labkey/test/util/PermissionsHelper.java +++ b/src/org/labkey/test/util/PermissionsHelper.java @@ -39,6 +39,7 @@ public abstract class PermissionsHelper public static final String APP_ADMIN_ROLE = "Application Admin"; public static final String DEVELOPER_ROLE = "Platform Developer"; public static final String IMP_TROUBLESHOOTER_ROLE = "Impersonating Troubleshooter"; + public static final String TROUBLESHOOTER_ROLE = "Troubleshooter"; public static final String PROJECT_ADMIN_ROLE = "Project Administrator"; public static final String FOLDER_ADMIN_ROLE = "Folder Administrator"; public static final String READER_ROLE = "Reader"; From ed38dd254a6868828315216ab56b2745dafa9234 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 26 Jan 2026 13:17:21 -0800 Subject: [PATCH 2/3] Impersonate Troubleshooter role in addition to a Troubleshooter user. Fix schema browser navigation. --- .../core/security/TroubleshooterRoleTest.java | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java b/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java index 14bfd0f7a7..5f04e7a56a 100644 --- a/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java +++ b/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java @@ -2,12 +2,15 @@ import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.io.FileUtils; +import org.jetbrains.annotations.Nullable; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; +import org.labkey.test.Locators; import org.labkey.test.TestTimeoutException; +import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Git; import org.labkey.test.pages.core.admin.ShowAdminPage; import org.labkey.test.pages.core.admin.ShowAuditLogPage; @@ -17,6 +20,8 @@ import org.labkey.test.util.DataRegionExportHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.SummaryStatisticsHelper; +import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.ExpectedConditions; import java.io.File; import java.util.Arrays; @@ -141,9 +146,19 @@ public void testQueryAccessInRoot() { goToAdminConsole(); impersonate(TROUBLESHOOTER_USER); + testQueryAccess(); + stopImpersonating(); + + goToAdminConsole(); + impersonateRole(TROUBLESHOOTER_ROLE); + testQueryAccess(); + stopImpersonating(); + } + private void testQueryAccess() + { // Verify that Troubleshooters can access the schema browser and view an arbitrary query in the root - goToSchemaBrowser(); + goToSchemaBrowser(null, false); DataRegionTable dataRegionTable = viewQueryData("core", "Modules"); int rowCount = dataRegionTable.getDataRowCount(); assertTrue(rowCount > 6); @@ -159,8 +174,23 @@ public void testQueryAccessInRoot() // Troubleshooters should NOT have read access outside the root goToProjectHome(); assertTextPresent("User does not have permission to perform this operation."); - goToSchemaBrowser(); - assertTextPresent("User does not have permission to perform this operation."); + goToSchemaBrowser(getProjectName(), true); + } + + // Troubleshooters don't get the "Go To Module" menu item, so can't use goToSchemaBrowser() + public void goToSchemaBrowser(@Nullable String container, boolean expectPermissionError) + { + beginAt(WebTestHelper.buildRelativeUrl("query", container,"begin")); + + if (expectPermissionError) + { + assertTextPresent("User does not have permission to perform this operation."); + } + else + { + shortWait().until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("div.lk-sb-instructions"))); + waitForElement(Locators.pageSignal("queryTreeRendered")); + } } @Override From 9dbd8bc867dbbe1080b58d50e5d4eb443ac791bb Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 26 Jan 2026 14:42:03 -0800 Subject: [PATCH 3/3] Looks like I fixed GitHub Issue #26 --- .../tests/core/security/TroubleshooterRoleTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java b/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java index 5f04e7a56a..9b59617967 100644 --- a/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java +++ b/src/org/labkey/test/tests/core/security/TroubleshooterRoleTest.java @@ -101,20 +101,19 @@ public void testAdminConsoleVisibility() verifySitePermissionSetting(true); } - /** - * Issue 47508: auditLog table visibility is inconsistent - * Assert broken behavior to prompt a test update once issue is fixed. - */ + // Verify fix for Issue 47508 / GitHub Issue #26: auditLog table visibility is inconsistent @Test public void testAllAuditTableVisibility() { impersonate(TROUBLESHOOTER_USER); ShowAdminPage showAdminPage = goToAdminConsole().goToSettingsSection(); - log("Verify the export file is non empty"); + log("Verify \"Group and role\" audit event table is viewable"); ShowAuditLogPage auditLogPage = showAdminPage.clickAuditLog(); auditLogPage.selectView("Group and role events"); - assertTextPresent("You do not have permission to see this data."); + assertTextNotPresent("You do not have permission to see this data."); + DataRegionTable logTable = auditLogPage.getLogTable(); + assertTrue(logTable.getDataRowCount() > 0); } protected void verifySitePermissionSetting(boolean canSave)