From e9e30d1fa4e8487102ebce7fb9ff524fd10004b1 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Thu, 11 Feb 2021 11:02:29 +0530 Subject: [PATCH] Enable toggle for params encoding (#2968) Enable toggle for params encoding 1. Add toggle param to ActionConfiguration class. 2. Add TCs to test toggle on/off/null. --- .../external/models/ActionConfiguration.java | 1 + .../com/external/plugins/RestApiPlugin.java | 43 +++++-- .../external/plugins/RestApiPluginTest.java | 110 +++++++++++++++++- 3 files changed, 143 insertions(+), 11 deletions(-) diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java index 7014c21dbb..521400a048 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java @@ -32,6 +32,7 @@ public class ActionConfiguration { // API fields String path; List headers; + Boolean encodeParamsToggle; List queryParameters; String body; // For form-data input instead of json use the following diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index 7d0fb3aa50..ab48d3d915 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -93,11 +93,23 @@ public class RestApiPlugin extends BasePlugin { String url = datasourceConfiguration.getUrl() + path; String reqContentType = ""; + /* + * - If encodeParamsToggle is null, then assume it to be true because params are supposed to be + * encoded by default, unless explicitly prohibited by the user. + */ + Boolean encodeParamsToggle = true; + if(actionConfiguration.getEncodeParamsToggle() != null + && actionConfiguration.getEncodeParamsToggle() == false) { + encodeParamsToggle = false; + } + HttpMethod httpMethod = actionConfiguration.getHttpMethod(); URI uri; try { String httpUrl = addHttpToUrlWhenPrefixNotPresent(url); - uri = createFinalUriWithQueryParams(httpUrl, actionConfiguration.getQueryParameters()); + uri = createFinalUriWithQueryParams(httpUrl, + actionConfiguration.getQueryParameters(), + encodeParamsToggle); } catch (URISyntaxException e) { ActionExecutionRequest actionExecutionRequest = populateRequestFields(actionConfiguration, null); actionExecutionRequest.setUrl(url); @@ -141,7 +153,9 @@ public class RestApiPlugin extends BasePlugin { if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(reqContentType) || MediaType.MULTIPART_FORM_DATA_VALUE.equals(reqContentType)) { - requestBodyAsString = convertPropertyListToReqBody(actionConfiguration.getBodyFormData(), reqContentType); + requestBodyAsString = convertPropertyListToReqBody(actionConfiguration.getBodyFormData(), + reqContentType, + encodeParamsToggle); } // If users have chosen to share the Appsmith signature in the header, calculate and add that @@ -287,7 +301,9 @@ public class RestApiPlugin extends BasePlugin { return null; } - public String convertPropertyListToReqBody(List bodyFormData, String reqContentType) { + public String convertPropertyListToReqBody(List bodyFormData, + String reqContentType, + Boolean encodeParamsToggle) { if (bodyFormData == null || bodyFormData.isEmpty()) { return ""; } @@ -297,7 +313,8 @@ public class RestApiPlugin extends BasePlugin { String key = property.getKey(); String value = property.getValue(); - if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(reqContentType)) { + if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(reqContentType) + && encodeParamsToggle == true) { try { value = URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); } catch (UnsupportedEncodingException e) { @@ -501,7 +518,9 @@ public class RestApiPlugin extends BasePlugin { return "http://" + url; } - private URI createFinalUriWithQueryParams(String url, List queryParams) throws URISyntaxException { + private URI createFinalUriWithQueryParams(String url, + List queryParams, + Boolean encodeParamsToggle) throws URISyntaxException { UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance(); uriBuilder.uri(new URI(url)); @@ -509,8 +528,18 @@ public class RestApiPlugin extends BasePlugin { for (Property queryParam : queryParams) { String key = queryParam.getKey(); if (StringUtils.isNotEmpty(key)) { - uriBuilder.queryParam(URLEncoder.encode(key, StandardCharsets.UTF_8), - URLEncoder.encode(queryParam.getValue(), StandardCharsets.UTF_8)); + if(encodeParamsToggle == true) { + uriBuilder.queryParam( + URLEncoder.encode(key, StandardCharsets.UTF_8), + URLEncoder.encode(queryParam.getValue(), StandardCharsets.UTF_8) + ); + } + else { + uriBuilder.queryParam( + key, + queryParam.getValue() + ); + } } } } diff --git a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java index 3497974bcd..5216a7beea 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java @@ -13,7 +13,6 @@ import io.jsonwebtoken.security.SignatureException; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpMethod; -import reactor.core.Exceptions; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -21,9 +20,11 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import javax.crypto.SecretKey; +import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -47,7 +48,6 @@ public class RestApiPluginTest { actionConfig.setBody(requestBody); Mono resultMono = pluginExecutor.execute(null, dsConfig, actionConfig); - StepVerifier.create(resultMono) .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); @@ -59,9 +59,10 @@ public class RestApiPluginTest { } @Test - public void testEncodingFunction() throws UnsupportedEncodingException { + public void testEncodingFunctionWithEncodeParamsToggleTrue() throws UnsupportedEncodingException { String encoded_value = pluginExecutor.convertPropertyListToReqBody(List.of(new Property("key", "valüe")), - "application/x-www-form-urlencoded"); + "application/x-www-form-urlencoded", + true); String expected_value = null; try { expected_value = "key=" + URLEncoder.encode("valüe", StandardCharsets.UTF_8.toString()); @@ -71,6 +72,20 @@ public class RestApiPluginTest { assertEquals(expected_value, encoded_value); } + @Test + public void testEncodingFunctionWithEncodeParamsToggleFalse() throws UnsupportedEncodingException { + String encoded_value = pluginExecutor.convertPropertyListToReqBody(List.of(new Property("key", "valüe")), + "application/x-www-form-urlencoded", + false); + String expected_value = null; + try { + expected_value = "key=" + URLEncoder.encode("valüe", StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + throw e; + } + assertNotEquals(expected_value, encoded_value); + } + @Test public void testValidFormApiExecution() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); @@ -174,4 +189,91 @@ public class RestApiPluginTest { }) .verifyComplete(); } + + @Test + public void testEncodeParamsToggleOn() { + DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + dsConfig.setUrl("https://postman-echo.com/post"); + + ActionConfiguration actionConfig = new ActionConfiguration(); + actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); + actionConfig.setHttpMethod(HttpMethod.POST); + String requestBody = "body"; + actionConfig.setBody(requestBody); + + List queryParams = new ArrayList<>(); + queryParams.add(new Property("query_key", "query val")); /* encoding changes 'query val' to 'query+val' */ + actionConfig.setQueryParameters(queryParams); + actionConfig.setEncodeParamsToggle(true); + + Mono resultMono = pluginExecutor.execute(null, dsConfig, actionConfig); + + StepVerifier.create(resultMono) + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + + String expected_url = "\"https://postman-echo.com/post?query_key=query+val\""; + JsonNode url = ((ObjectNode) result.getBody()).get("url"); + assertEquals(expected_url, url.toString()); + }) + .verifyComplete(); + } + + @Test + public void testEncodeParamsToggleNull() { + DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + dsConfig.setUrl("https://postman-echo.com/post"); + + ActionConfiguration actionConfig = new ActionConfiguration(); + actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); + actionConfig.setHttpMethod(HttpMethod.POST); + String requestBody = "body"; + actionConfig.setBody(requestBody); + + List queryParams = new ArrayList<>(); + queryParams.add(new Property("query_key", "query val")); /* encoding changes 'query val' to 'query+val' */ + actionConfig.setQueryParameters(queryParams); + actionConfig.setEncodeParamsToggle(null); + + Mono resultMono = pluginExecutor.execute(null, dsConfig, actionConfig); + + StepVerifier.create(resultMono) + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + + String expected_url = "\"https://postman-echo.com/post?query_key=query+val\""; + JsonNode url = ((ObjectNode) result.getBody()).get("url"); + assertEquals(expected_url, url.toString()); + }) + .verifyComplete(); + } + + @Test + public void testEncodeParamsToggleOff() { + DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + dsConfig.setUrl("https://postman-echo.com/post"); + + ActionConfiguration actionConfig = new ActionConfiguration(); + actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); + actionConfig.setHttpMethod(HttpMethod.POST); + String requestBody = "body"; + actionConfig.setBody(requestBody); + + List queryParams = new ArrayList<>(); + queryParams.add(new Property("query_key", "query val")); + actionConfig.setQueryParameters(queryParams); + actionConfig.setEncodeParamsToggle(false); + + Mono pluginExecutorMono = Mono.just(pluginExecutor); + Mono resultMono = pluginExecutorMono.flatMap(executor -> executor.execute(null, + dsConfig, + actionConfig)); + StepVerifier.create(resultMono) + .verifyErrorSatisfies(e -> { + assertTrue(e instanceof IllegalArgumentException); + assertTrue(e.getMessage().contains("Invalid character ' ' for QUERY_PARAM in \"query val\"")); + }); + } }