From 0d49d677fb994b013e63d38a1b2f3dd4407b55cf Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 30 Jun 2025 16:35:17 -0700 Subject: [PATCH 1/4] Add object-src CSP directive for Knitr tests --- src/org/labkey/test/pages/admin/ExternalSourcesPage.java | 1 + src/org/labkey/test/tests/AbstractKnitrReportTest.java | 1 + src/org/labkey/test/util/CspLogUtil.java | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/pages/admin/ExternalSourcesPage.java b/src/org/labkey/test/pages/admin/ExternalSourcesPage.java index 25d1da4db3..25d3c41075 100644 --- a/src/org/labkey/test/pages/admin/ExternalSourcesPage.java +++ b/src/org/labkey/test/pages/admin/ExternalSourcesPage.java @@ -207,6 +207,7 @@ public enum Directive implements OptionSelect.SelectOption Frame("frame-src"), Image("image-src"), Style("style-src"), + Object("object-src"), ; private final String directiveId; diff --git a/src/org/labkey/test/tests/AbstractKnitrReportTest.java b/src/org/labkey/test/tests/AbstractKnitrReportTest.java index d40ece66e7..b7f5e032fd 100644 --- a/src/org/labkey/test/tests/AbstractKnitrReportTest.java +++ b/src/org/labkey/test/tests/AbstractKnitrReportTest.java @@ -81,6 +81,7 @@ protected void setupProject() try { new CspConfigHelper(this).setAllowedHosts(Map.of( + ExternalSourcesPage.Directive.Object, List.of("'self'"), // Issue 53226: reports-streamFile is blocked by object-src CSP directive ExternalSourcesPage.Directive.Style, List.of("https://cdn.datatables.net"), ExternalSourcesPage.Directive.Font, List.of("https://mathjax.rstudio.com"))); } diff --git a/src/org/labkey/test/util/CspLogUtil.java b/src/org/labkey/test/util/CspLogUtil.java index 62cf9d042c..4279659801 100644 --- a/src/org/labkey/test/util/CspLogUtil.java +++ b/src/org/labkey/test/util/CspLogUtil.java @@ -19,6 +19,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -28,8 +29,7 @@ public class CspLogUtil "/_rstudio/", "/_rstudioReport/" ); - // Issue 53226: reports-streamFile is blocked by object-src CSP directive - private static final Set ignoredDirectives = Set.of("object-src"); + private static final Set ignoredDirectives = Collections.emptySet(); private static final String logName = "csp-report.log"; private static final File logFile = new File(TestFileUtils.getServerLogDir(), logName); From 18e17204da213677d82def5073c8a8cdb73b57e7 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 1 Jul 2025 09:30:15 -0700 Subject: [PATCH 2/4] Add initial plotly test --- data/reports/plotly.rmd | 12 ++++++++++++ src/org/labkey/test/TestFileUtils.java | 6 +++--- .../test/tests/AbstractKnitrReportTest.java | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 data/reports/plotly.rmd diff --git a/data/reports/plotly.rmd b/data/reports/plotly.rmd new file mode 100644 index 0000000000..17e2483893 --- /dev/null +++ b/data/reports/plotly.rmd @@ -0,0 +1,12 @@ +--- +title: "Plotly Example" +output: html_document +--- + +```{r testing, echo=FALSE, message=FALSE} +library(plotly, warn.conflicts = FALSE) + +fig <- plot_ly(x = ~rnorm(50), type = "box") +fig <- fig %>% add_trace(x = ~rnorm(50, 1)) +fig +``` \ No newline at end of file diff --git a/src/org/labkey/test/TestFileUtils.java b/src/org/labkey/test/TestFileUtils.java index 6a4a3ae4eb..b9a1228ceb 100644 --- a/src/org/labkey/test/TestFileUtils.java +++ b/src/org/labkey/test/TestFileUtils.java @@ -109,7 +109,7 @@ public static String getFileContents(Path path) { try { - return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + return Files.readString(path); } catch (IOException fail) { @@ -306,7 +306,7 @@ public static Set getSampleDataDirs() if (sampledataDirsFile.exists()) { String path = getFileContents(sampledataDirsFile); - _sampledataDirs.addAll(Arrays.stream(path.split(";")).map(File::new).collect(Collectors.toList())); + _sampledataDirs.addAll(Arrays.stream(path.split(";")).map(File::new).toList()); } else { @@ -317,7 +317,7 @@ public static Set getSampleDataDirs() // We know where the modules live; no reason to insist that sampledata.dirs exists. Files.walkFileTree(modulesDir, Collections.emptySet(), 2, new SimpleFileVisitor<>(){ @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException + public @NotNull FileVisitResult preVisitDirectory(@NotNull Path dir, @NotNull BasicFileAttributes attrs) { if (dir.equals(modulesDir)) { diff --git a/src/org/labkey/test/tests/AbstractKnitrReportTest.java b/src/org/labkey/test/tests/AbstractKnitrReportTest.java index b7f5e032fd..4f43bae975 100644 --- a/src/org/labkey/test/tests/AbstractKnitrReportTest.java +++ b/src/org/labkey/test/tests/AbstractKnitrReportTest.java @@ -17,6 +17,7 @@ import org.junit.Assume; import org.junit.BeforeClass; +import org.junit.Test; import org.labkey.remoteapi.CommandException; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; @@ -54,6 +55,8 @@ public abstract class AbstractKnitrReportTest extends BaseWebDriverTest protected static final Path rmdReport_no_scriptpad = TestFileUtils.getSampleData("reports/knitr_no_scriptpad.rmd").toPath(); private static final Path rhtmlReport = scriptpadReports.resolve("script_rhtml.rhtml"); private static final Path rhtmlReport_no_scriptpad = TestFileUtils.getSampleData("reports/knitr_no_scriptpad.rhtml").toPath(); + protected static final Path rmdReport_embedded_script = TestFileUtils.getSampleData("reports/plotly.rmd").toPath(); + protected final RReportHelper _rReportHelper = new RReportHelper(this); private static String readReport(final Path reportFile) @@ -225,4 +228,18 @@ protected void moduleReportDependencies() _ext4Helper.waitForMaskToDisappear(3 * BaseWebDriverTest.WAIT_FOR_JAVASCRIPT); waitForElement(Locator.id("mtcars_table")); } + + /** + * Issue 53211: CSP reports when an R/Plotly graph is displayed in Reports web part, same thing wrapped in a wiki works fine with strict csp + */ + @Test + public void reportEmbeddedScript() + { + Locator[] reportContains = {}; + + String[] reportNotContains = {}; + + createAndVerifyKnitrReport(rmdReport_embedded_script, RReportHelper.ReportOption.knitrMarkdown, reportContains, + reportNotContains, true); + } } From 7405519080bef51a7db9718a6c282ca345c2aec8 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 2 Jul 2025 14:49:57 -0700 Subject: [PATCH 3/4] Add test for report with embedded script --- data/reports/knitr_no_scriptpad.rhtml | 6 +++ data/reports/knitr_no_scriptpad.rmd | 6 +++ data/reports/nonce_check.rhtml | 15 ++++++ data/reports/plotly.rmd | 12 ----- .../reports/schemas/script_rhtml.rhtml | 6 +++ .../resources/reports/schemas/script_rmd.rmd | 6 +++ src/org/labkey/test/pages/wiki/EditPage.java | 38 ++++++++++++++ .../test/tests/AbstractKnitrReportTest.java | 50 +++++++++++++++---- src/org/labkey/test/tests/TabTest.java | 2 +- src/org/labkey/test/util/PortalHelper.java | 18 ++----- src/org/labkey/test/util/WikiHelper.java | 28 +---------- 11 files changed, 124 insertions(+), 63 deletions(-) create mode 100644 data/reports/nonce_check.rhtml delete mode 100644 data/reports/plotly.rmd diff --git a/data/reports/knitr_no_scriptpad.rhtml b/data/reports/knitr_no_scriptpad.rhtml index 1442cd2065..1b3d5c69a1 100644 --- a/data/reports/knitr_no_scriptpad.rhtml +++ b/data/reports/knitr_no_scriptpad.rhtml @@ -93,5 +93,11 @@ end.rcode-->

Well, everything seems to be working. Let's ask R what is the value of π? Of course it is .

+Nonce check: FAIL + + + \ No newline at end of file diff --git a/data/reports/knitr_no_scriptpad.rmd b/data/reports/knitr_no_scriptpad.rmd index 691a947b9c..78ba8eb048 100644 --- a/data/reports/knitr_no_scriptpad.rmd +++ b/data/reports/knitr_no_scriptpad.rmd @@ -113,6 +113,12 @@ library(knitr) knit('knitr-minimal.Rmd') ``` +Nonce check: FAIL + + + ## Conclusion Markdown is super easy to write. Go to **knitr** [homepage](http://yihui.name/knitr) for details. \ No newline at end of file diff --git a/data/reports/nonce_check.rhtml b/data/reports/nonce_check.rhtml new file mode 100644 index 0000000000..7efb90bd76 --- /dev/null +++ b/data/reports/nonce_check.rhtml @@ -0,0 +1,15 @@ + + + + Test script nonce in Knitr HTML + + + +Nonce check: FAIL + + + + + \ No newline at end of file diff --git a/data/reports/plotly.rmd b/data/reports/plotly.rmd deleted file mode 100644 index 17e2483893..0000000000 --- a/data/reports/plotly.rmd +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Plotly Example" -output: html_document ---- - -```{r testing, echo=FALSE, message=FALSE} -library(plotly, warn.conflicts = FALSE) - -fig <- plot_ly(x = ~rnorm(50), type = "box") -fig <- fig %>% add_trace(x = ~rnorm(50, 1)) -fig -``` \ No newline at end of file diff --git a/modules/scriptpad/resources/reports/schemas/script_rhtml.rhtml b/modules/scriptpad/resources/reports/schemas/script_rhtml.rhtml index 77e8cfec5d..f9bc5fc814 100644 --- a/modules/scriptpad/resources/reports/schemas/script_rhtml.rhtml +++ b/modules/scriptpad/resources/reports/schemas/script_rhtml.rhtml @@ -87,5 +87,11 @@ end.rcode-->

Well, everything seems to be working. Let's ask R what is the value of π? Of course it is .

+Nonce check: FAIL + + + \ No newline at end of file diff --git a/modules/scriptpad/resources/reports/schemas/script_rmd.rmd b/modules/scriptpad/resources/reports/schemas/script_rmd.rmd index 5729f20b1c..f136b7616b 100644 --- a/modules/scriptpad/resources/reports/schemas/script_rmd.rmd +++ b/modules/scriptpad/resources/reports/schemas/script_rmd.rmd @@ -108,6 +108,12 @@ library(knitr) knit('knitr-minimal.Rmd') ``` +Nonce check: FAIL + + + ## Conclusion Markdown is super easy to write. Go to **knitr** [homepage](http://yihui.name/knitr) for details. \ No newline at end of file diff --git a/src/org/labkey/test/pages/wiki/EditPage.java b/src/org/labkey/test/pages/wiki/EditPage.java index 032f0c2734..9e4d76a535 100644 --- a/src/org/labkey/test/pages/wiki/EditPage.java +++ b/src/org/labkey/test/pages/wiki/EditPage.java @@ -100,10 +100,48 @@ public EditPage setTitle(String title) public EditPage setBody(String body) { + switchWikiToSourceView(); elementCache().bodyTextArea.set(body); return this; } + /** + * Switches the wiki edit page to source view when the format type is HTML. + */ + public void switchWikiToSourceView() + { + String curFormat = executeScript("return LABKEY._wiki.getProps().rendererType;", String.class); + if (curFormat.equalsIgnoreCase("HTML")) + { + if (isElementPresent(Locator.css("#wiki-tab-source.labkey-tab-inactive"))) + { + Locator tab = Locator.css("#wiki-tab-source > a"); + waitForElementToBeVisible(tab); + click(tab); + waitForElement(Locator.css("#wiki-tab-source.labkey-tab-active")); + } + } + } + + public void switchWikiToVisualView() + { + String curFormat = (String) executeScript("return LABKEY._wiki.getProps().rendererType;"); + if (curFormat.equalsIgnoreCase("HTML")) + { + if (isElementPresent(Locator.css("#wiki-tab-visual.labkey-tab-inactive"))) + { + Locator tab = Locator.css("#wiki-tab-visual > a"); + waitForElementToBeVisible(tab); + click(tab); + + Locator yesButton = Locator.tagWithText("span","Yes"); + waitForElementToBeVisible(yesButton); + waitAndClick(yesButton); + waitForElement(Locator.css("#wiki-tab-visual.labkey-tab-active")); + } + } + } + public EditPage setShouldIndex(boolean shouldIndex) { elementCache().shouldIndexCheckbox.set(shouldIndex); diff --git a/src/org/labkey/test/tests/AbstractKnitrReportTest.java b/src/org/labkey/test/tests/AbstractKnitrReportTest.java index 4f43bae975..e74976c0e1 100644 --- a/src/org/labkey/test/tests/AbstractKnitrReportTest.java +++ b/src/org/labkey/test/tests/AbstractKnitrReportTest.java @@ -27,11 +27,13 @@ import org.labkey.test.pages.core.admin.logger.ManagerPage; import org.labkey.test.pages.reports.ManageViewsPage; import org.labkey.test.util.CodeMirrorHelper; +import org.labkey.test.util.CspLogUtil; import org.labkey.test.util.Log4jUtils; import org.labkey.test.util.LogMethod; import org.labkey.test.util.LoggedParam; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.RReportHelper; +import org.labkey.test.util.WikiHelper; import org.labkey.test.util.core.admin.CspConfigHelper; import org.openqa.selenium.WebElement; @@ -55,7 +57,9 @@ public abstract class AbstractKnitrReportTest extends BaseWebDriverTest protected static final Path rmdReport_no_scriptpad = TestFileUtils.getSampleData("reports/knitr_no_scriptpad.rmd").toPath(); private static final Path rhtmlReport = scriptpadReports.resolve("script_rhtml.rhtml"); private static final Path rhtmlReport_no_scriptpad = TestFileUtils.getSampleData("reports/knitr_no_scriptpad.rhtml").toPath(); - protected static final Path rmdReport_embedded_script = TestFileUtils.getSampleData("reports/plotly.rmd").toPath(); + private static final Path rhtmlNonceCheck = TestFileUtils.getSampleData("reports/nonce_check.rhtml").toPath(); + private static final Locator.XPathLocator nonceCheckLoc = Locator.id("nonce-check-result"); + private static final Locator.XPathLocator nonceCheckSuccessLoc = nonceCheckLoc.withText("SUCCESS"); protected final RReportHelper _rReportHelper = new RReportHelper(this); @@ -170,7 +174,9 @@ protected void htmlFormat() Locator.tag("pre").containing("## \"1\",249318596,\"2008-05-17\",86,36,129,76,64"), Locator.tag("pre").withText("## knitr says hello to HTML!"), Locator.tag("pre").startsWith("## Error").containing(": non-numeric argument to binary operator"), - Locator.tag("p").startsWith("Well, everything seems to be working. Let's ask R what is the value of \u03C0? Of course it is 3.141")}; + Locator.tag("p").startsWith("Well, everything seems to be working. Let's ask R what is the value of \u03C0? Of course it is 3.141"), + nonceCheckSuccessLoc // Inline script should run + }; String[] reportNotContains = {"", // Uninterpreted html "