From 3b83a36bd1766f4262d9d6c7fc36cb3b6bf319c0 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Wed, 7 Apr 2021 19:36:37 +0530 Subject: [PATCH] [Bug Fix Improvement] : Table widget keys are unescaped after walking through the DSL (#3908) * WIP : untested * Minor refactoring * Added test case to assert escaping and unescaping of the table widget primary column keys (cherry picked from commit 174ef284f06c3360b5086c32a843117374aea3ec) --- .../server/helpers/WidgetSpecificUtils.java | 35 ++++++++++------ .../services/LayoutActionServiceImpl.java | 41 ++++++++++++++++++- .../services/LayoutActionServiceTest.java | 40 ++++++++++++++++++ 3 files changed, 103 insertions(+), 13 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java index e98d83e816..8ec6ebcc9e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java @@ -1,12 +1,10 @@ package com.appsmith.server.helpers; import com.appsmith.server.constants.FieldName; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import net.minidev.json.JSONObject; import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; import java.util.HashMap; import java.util.Map; @@ -49,18 +47,31 @@ public class WidgetSpecificUtils { public static JSONObject unEscapeTableWidgetPrimaryColumns(JSONObject dsl) { - String dslAsString; - try { - dslAsString = objectMapper.writeValueAsString(dsl); - dslAsString = dslAsString.replaceAll(FieldName.MONGO_ESCAPE_ID, FieldName.MONGO_UNESCAPED_ID); - dslAsString = dslAsString.replaceAll(FieldName.MONGO_ESCAPE_CLASS, FieldName.MONGO_UNESCAPED_CLASS); + Set keySet = dsl.keySet(); - return (JSONObject) jsonParser.parse(dslAsString); + if (keySet.contains(FieldName.PRIMARY_COLUMNS)) { + Map primaryColumns = (Map) dsl.get(FieldName.PRIMARY_COLUMNS); - } catch (JsonProcessingException | ParseException e) { - // Something went wrong in parsing the DSL. Return as is - return dsl; + Map newPrimaryColumns = new HashMap(); + + Boolean updateRequired = false; + + for (String columnName : (Set) primaryColumns.keySet()) { + if (columnName.equals(FieldName.MONGO_ESCAPE_ID)) { + updateRequired = true; + newPrimaryColumns.put(FieldName.MONGO_UNESCAPED_ID, primaryColumns.get(columnName)); + } else if (columnName.equals(FieldName.MONGO_ESCAPE_CLASS)) { + updateRequired = true; + newPrimaryColumns.put(FieldName.MONGO_UNESCAPED_CLASS, primaryColumns.get(columnName)); + } else { + newPrimaryColumns.put(columnName, primaryColumns.get(columnName)); + } + } + if (updateRequired) { + dsl.put(FieldName.PRIMARY_COLUMNS, newPrimaryColumns); + } } - + return dsl; } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java index f2ff05684b..bde6860707 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java @@ -694,7 +694,46 @@ public class LayoutActionServiceImpl implements LayoutActionService { JSONObject dsl = layout.getDsl(); // Unescape specific widgets - dsl = WidgetSpecificUtils.unEscapeTableWidgetPrimaryColumns(dsl); + dsl = unEscapeDslKeys(dsl, layout.getMongoEscapedWidgetNames()); + + return dsl; + } + + private JSONObject unEscapeDslKeys(JSONObject dsl, Set escapedWidgetNames) { + + String widgetName = (String) dsl.get(FieldName.WIDGET_NAME); + + if (widgetName == null) { + // This isnt a valid widget configuration. No need to traverse further. + return dsl; + } + + if (escapedWidgetNames.contains(widgetName)) { + // We should escape the widget keys + String widgetType = dsl.getAsString(FieldName.WIDGET_TYPE); + if (widgetType.equals(FieldName.TABLE_WIDGET)) { + // UnEscape Table widget keys + // Since this is a table widget, it wouldnt have children. We can safely return from here with updated dsl + return WidgetSpecificUtils.unEscapeTableWidgetPrimaryColumns(dsl); + } + } + + // Fetch the children of the current node in the DSL and recursively iterate over them to extract bindings + ArrayList children = (ArrayList) dsl.get(FieldName.CHILDREN); + ArrayList newChildren = new ArrayList<>(); + if (children != null) { + for (int i = 0; i < children.size(); i++) { + Map data = (Map) children.get(i); + JSONObject object = new JSONObject(); + // If the children tag exists and there are entries within it + if (!CollectionUtils.isEmpty(data)) { + object.putAll(data); + JSONObject child = unEscapeDslKeys(object, escapedWidgetNames); + newChildren.add(child); + } + } + dsl.put(FieldName.CHILDREN, newChildren); + } return dsl; } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java index b0c6e780fd..3595792ea4 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java @@ -3,6 +3,7 @@ package com.appsmith.server.services; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Layout; @@ -38,6 +39,7 @@ import org.springframework.test.context.junit4.SpringRunner; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -444,4 +446,42 @@ public class LayoutActionServiceTest { }) .verifyComplete(); } + + @Test + @WithUserDetails(value = "api_user") + public void tableWidgetKeyEscape() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor())); + + JSONObject dsl = new JSONObject(); + dsl.put("widgetName", "Table1"); + dsl.put("type", "TABLE_WIDGET"); + Map primaryColumns = new HashMap(); + JSONObject jsonObject = new JSONObject(Map.of("key", "value")); + primaryColumns.put("_id", jsonObject); + primaryColumns.put("_class", jsonObject); + dsl.put("primaryColumns", primaryColumns); + Layout layout = testPage.getLayouts().get(0); + layout.setDsl(dsl); + + Mono updateLayoutMono = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).cache(); + + Mono pageFromRepoMono = updateLayoutMono.then(newPageService.findPageById(testPage.getId(), READ_PAGES, false)); + + StepVerifier + .create(Mono.zip(updateLayoutMono, pageFromRepoMono)) + .assertNext(tuple -> { + LayoutDTO updatedLayout = tuple.getT1(); + PageDTO pageFromRepo = tuple.getT2(); + + Map primaryColumns1 = (Map) updatedLayout.getDsl().get("primaryColumns"); + assertThat(primaryColumns1.keySet()).containsAll(Set.of(FieldName.MONGO_UNESCAPED_ID, FieldName.MONGO_UNESCAPED_CLASS)); + + Map primaryColumns2 = (Map) pageFromRepo.getLayouts().get(0).getDsl().get("primaryColumns"); + assertThat(primaryColumns2.keySet()).containsAll(Set.of(FieldName.MONGO_ESCAPE_ID, FieldName.MONGO_ESCAPE_CLASS)); + }) + .verifyComplete(); + + + } + }