From 1d28a8ff9caa4819c47476dd3ce1af462ea0bac5 Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 13 Aug 2025 17:22:26 -0700 Subject: [PATCH 1/2] Issue 53498: reject string attachment values for query insert/update api --- src/org/labkey/test/BaseWebDriverTest.java | 53 +++++++++++++++++++ .../test/tests/InlineImagesListTest.java | 8 +++ 2 files changed, 61 insertions(+) diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index cec151afd0..20f5be1fe4 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -70,6 +70,7 @@ import org.labkey.test.teamcity.TeamCityUtils; import org.labkey.test.util.APIAssayHelper; import org.labkey.test.util.APIContainerHelper; +import org.labkey.test.util.APITestHelper; import org.labkey.test.util.AbstractAssayHelper; import org.labkey.test.util.AbstractContainerHelper; import org.labkey.test.util.ApiPermissionsHelper; @@ -80,6 +81,7 @@ import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.DebugUtils; import org.labkey.test.util.DeferredErrorCollector; +import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.Ext4Helper; import org.labkey.test.util.FileBrowserHelper; import org.labkey.test.util.ListHelper; @@ -147,6 +149,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.labkey.test.TestProperties.isHeapDumpCollectionEnabled; @@ -2739,6 +2742,56 @@ public void dismissReleaseBanner(String productName, boolean dismiss) executeScript("localStorage.removeItem('" + dismissBannerKey + "')"); } + protected void verifyQueryAPI(String schema, String dataType, Map row, boolean isInsert, String... errorMsg) + { + String action = isInsert ? "insertRows" : "updateRows"; + String updateScript = "LABKEY.Query." + action + "({ schemaName: \"" + schema + "\", "+ + "queryName: " + EscapeUtil.toJSONStr(dataType) + ", " + + "success: callback," + + "failure: callback," + + "rows: [" + EscapeUtil.toJSONRow(row) + "]" + + "})"; + executeAndVerifyScript(updateScript, errorMsg); + } + + protected void executeAndVerifyScript(String script, @Nullable String... altErrors) + { + List errors = new ArrayList<>(); + if (altErrors != null) + errors = Arrays.stream(altErrors).filter(Objects::nonNull).toList(); + + Map result = (Map)executeAsyncScript(script); + + String failureResult = APITestHelper.parseScriptResult(result); + + if (altErrors == null || errors.isEmpty()) + { + assertNull(failureResult); // null here means there was an unhandled server-side exception + checker().verifyNull("Unexpected error message", result.get("exception")); + } + else + { + Object exception = result.get("exception"); + checker().verifyNotNull("Error is empty", exception); + + if (exception instanceof String exceptionStr) + { + if (errors.size() == 1) + checker().verifyEquals("Unexpected error message", errors.get(0), exception); + else + { + for (String error : altErrors) + { + if (exceptionStr.equals(error)) + return; + + } + checker().error("Unexpected error message: " + exception); + } + } + } + } + @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited diff --git a/src/org/labkey/test/tests/InlineImagesListTest.java b/src/org/labkey/test/tests/InlineImagesListTest.java index c6f941cc33..2720e4efd7 100644 --- a/src/org/labkey/test/tests/InlineImagesListTest.java +++ b/src/org/labkey/test/tests/InlineImagesListTest.java @@ -369,6 +369,14 @@ public final void testList() throws Exception importFilePathError(listImportPage, "1", "absent.txt"); importFilePathError(listImportPage, "1", PDF_FILE.getName()); importFilePathError(listImportPage, "5", PDF_FILE.getName()); + + String attachmentPdfError = "Can't upload '" + PDF_FILE.getName() + "' to field " + LIST_ATTACHMENT01_NAME + " with type Attachment."; + String attachmentAbsentError = "Can't upload 'Absent.txt'" + " to field " + LIST_ATTACHMENT01_NAME + " with type Attachment."; + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, PDF_FILE.getName()), true, "Row 1: " + attachmentPdfError); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, "Absent.txt"), true, "Row 1: " + attachmentAbsentError); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, PDF_FILE.getName()), false, attachmentPdfError); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, "Absent.txt"), false, attachmentAbsentError); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, ""/*can remove attachment*/), false); } private void importFilePathError(ImportDataPage listImportPage, String key, String attachmentValue) From 9fd964f7bc8ef65dc87d02c88ff3d5af96df6d40 Mon Sep 17 00:00:00 2001 From: XingY Date: Sun, 17 Aug 2025 16:51:08 -0700 Subject: [PATCH 2/2] Non string type file path / attachment value --- src/org/labkey/test/tests/InlineImagesListTest.java | 13 +++++++++++-- src/org/labkey/test/util/EscapeUtil.java | 9 ++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/tests/InlineImagesListTest.java b/src/org/labkey/test/tests/InlineImagesListTest.java index 2720e4efd7..fab04e0bb9 100644 --- a/src/org/labkey/test/tests/InlineImagesListTest.java +++ b/src/org/labkey/test/tests/InlineImagesListTest.java @@ -362,20 +362,29 @@ public final void testList() throws Exception ImportDataPage listImportPage = _listHelper.clickImportData(); importFilePathError(listImportPage, "5", "absent.txt"); importFilePathError(listImportPage, "5", PDF_FILE.getName()); + importFilePathError(listImportPage, "5", "123"); listImportPage.setCopyPasteMerge(false, true); importFilePathError(listImportPage, "1", "absent.txt"); importFilePathError(listImportPage, "1", PDF_FILE.getName()); + importFilePathError(listImportPage, "1", "true"); listImportPage.setCopyPasteMerge(true, true); importFilePathError(listImportPage, "1", "absent.txt"); importFilePathError(listImportPage, "1", PDF_FILE.getName()); importFilePathError(listImportPage, "5", PDF_FILE.getName()); - String attachmentPdfError = "Can't upload '" + PDF_FILE.getName() + "' to field " + LIST_ATTACHMENT01_NAME + " with type Attachment."; - String attachmentAbsentError = "Can't upload 'Absent.txt'" + " to field " + LIST_ATTACHMENT01_NAME + " with type Attachment."; + String attachmentError = "Can't upload '%s' to field %s with type Attachment."; + String attachmentPdfError = String.format(attachmentError, PDF_FILE.getName(), LIST_ATTACHMENT01_NAME); + String attachmentAbsentError = String.format(attachmentError, "Absent.txt", LIST_ATTACHMENT01_NAME); verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, PDF_FILE.getName()), true, "Row 1: " + attachmentPdfError); verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, "Absent.txt"), true, "Row 1: " + attachmentAbsentError); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, 123), true, "Row 1: " + String.format(attachmentError, "123", LIST_ATTACHMENT01_NAME)); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, 123.456), true, "Row 1: " + String.format(attachmentError, "123.456", LIST_ATTACHMENT01_NAME)); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, true), true, "Row 1: " + String.format(attachmentError, "true", LIST_ATTACHMENT01_NAME)); verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, PDF_FILE.getName()), false, attachmentPdfError); verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, "Absent.txt"), false, attachmentAbsentError); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, 123), false, String.format(attachmentError, "123", LIST_ATTACHMENT01_NAME)); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, 123.456), false, String.format(attachmentError, "123.456", LIST_ATTACHMENT01_NAME)); + verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, true), false, String.format(attachmentError, "true", LIST_ATTACHMENT01_NAME)); verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 1, LIST_ATTACHMENT01_NAME, ""/*can remove attachment*/), false); } diff --git a/src/org/labkey/test/util/EscapeUtil.java b/src/org/labkey/test/util/EscapeUtil.java index 08ac4bbfc2..71093031f0 100644 --- a/src/org/labkey/test/util/EscapeUtil.java +++ b/src/org/labkey/test/util/EscapeUtil.java @@ -54,6 +54,13 @@ static public String toJSONStr(String str) return "\"" + escaped + "\""; } + static public String toJSONStr(Object value) + { + if (value instanceof String strVal) + return toJSONStr(strVal); + return String.valueOf(value); + } + static public String toJSONRow(Map row) { StringBuilder sb = new StringBuilder("{"); @@ -63,7 +70,7 @@ static public String toJSONRow(Map row) sb.append(comma); Object value = entry.getValue(); sb.append(EscapeUtil.toJSONStr(entry.getKey())) - .append(": ").append(value instanceof String ? EscapeUtil.toJSONStr((String) entry.getValue()) : value); + .append(": ").append(toJSONStr(value)); comma = ","; } sb.append("}");