From 60efdb998b3162b085f84551013c42d96e38c93b Mon Sep 17 00:00:00 2001
From: Shrikant Sharat Kandula
Date: Wed, 29 Jul 2020 11:12:04 +0530
Subject: [PATCH 1/4] Fix: Datasource password gets double encrypted on cloning
(#191)
* Decrypt sensitive fields before cloning a datasource
* Fix NPE in cloning datasource when configuration is missing
---
.../services/DatasourceContextServiceImpl.java | 2 +-
.../solutions/ExamplesOrganizationCloner.java | 5 +++++
.../ExamplesOrganizationClonerTests.java | 16 ++++++++++++++++
3 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java
index f4a95ee3c3..82492504ec 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java
@@ -146,7 +146,7 @@ public class DatasourceContextServiceImpl implements DatasourceContextService {
@Override
public AuthenticationDTO decryptSensitiveFields(AuthenticationDTO authenticationDTO) {
- if (authenticationDTO.getPassword() != null) {
+ if (authenticationDTO != null && authenticationDTO.getPassword() != null) {
authenticationDTO.setPassword(encryptionService.decryptString(authenticationDTO.getPassword()));
}
return authenticationDTO;
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java
index ee62513f7b..52bccd314c 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java
@@ -18,6 +18,7 @@ import com.appsmith.server.repositories.OrganizationRepository;
import com.appsmith.server.repositories.PageRepository;
import com.appsmith.server.services.ActionService;
import com.appsmith.server.services.ApplicationPageService;
+import com.appsmith.server.services.DatasourceContextService;
import com.appsmith.server.services.DatasourceService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SessionUserService;
@@ -51,6 +52,7 @@ public class ExamplesOrganizationCloner {
private final SessionUserService sessionUserService;
private final UserService userService;
private final ApplicationPageService applicationPageService;
+ private final DatasourceContextService datasourceContextService;
public Mono cloneExamplesOrganization() {
return sessionUserService
@@ -230,6 +232,9 @@ public class ExamplesOrganizationCloner {
makePristine(datasource);
datasource.setOrganizationId(toOrganizationId);
datasource.setName(datasource.getName());
+ if (datasource.getDatasourceConfiguration() != null) {
+ datasourceContextService.decryptSensitiveFields(datasource.getDatasourceConfiguration().getAuthentication());
+ }
return Mono.zip(
Mono.just(templateDatasourceId),
datasourceService.create(datasource)
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java
index 89f9613ccb..808a8ffad4 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java
@@ -1,5 +1,6 @@
package com.appsmith.server.solutions;
+import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Property;
import com.appsmith.server.constants.FieldName;
@@ -17,6 +18,7 @@ import com.appsmith.server.services.ActionService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.DatasourceService;
+import com.appsmith.server.services.EncryptionService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.PageService;
import com.appsmith.server.services.SessionUserService;
@@ -88,6 +90,9 @@ public class ExamplesOrganizationClonerTests {
@Autowired
private PluginRepository pluginRepository;
+ @Autowired
+ private EncryptionService encryptionService;
+
@MockBean
private PluginExecutorHelper pluginExecutorHelper;
@@ -324,6 +329,9 @@ public class ExamplesOrganizationClonerTests {
final Datasource ds2 = new Datasource();
ds2.setName("datasource 2");
ds2.setOrganizationId(organization.getId());
+ ds2.setDatasourceConfiguration(new DatasourceConfiguration());
+ ds2.getDatasourceConfiguration().setAuthentication(new AuthenticationDTO());
+ ds2.getDatasourceConfiguration().getAuthentication().setPassword("answer-to-life");
return Mono.when(
datasourceService.create(ds1),
@@ -354,6 +362,14 @@ public class ExamplesOrganizationClonerTests {
new Property("X-Answer", "42")
);
+ final Datasource ds2 = data.datasources.stream()
+ .filter(datasource -> "datasource 2".equals(datasource.getName()))
+ .findFirst()
+ .orElseThrow();
+ assertThat(ds2.getDatasourceConfiguration().getAuthentication()).isNotNull();
+ assertThat(ds2.getDatasourceConfiguration().getAuthentication().getPassword())
+ .isEqualTo(encryptionService.encryptString("answer-to-life"));
+
assertThat(data.applications).isEmpty();
assertThat(data.actions).isEmpty();
})
From 3c5d93e6990eec378616c86c17d3d5257c72e7b7 Mon Sep 17 00:00:00 2001
From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com>
Date: Wed, 29 Jul 2020 11:30:03 +0530
Subject: [PATCH 2/4] Feature/validate request header (#189)
* validate request header
* Validate request header for API tests
Co-authored-by: Nandan Anantharamu
---
app/client/cypress/fixtures/testdata.json | 8 +++-
.../ApiPaneTests/API_All_Verb_spec.js | 37 ++++++++++++++++---
.../ApiPaneTests/API_Edit_spec.js | 4 +-
.../cypress/locators/apiWidgetslocator.json | 9 ++++-
app/client/cypress/support/commands.js | 14 +++++++
5 files changed, 61 insertions(+), 11 deletions(-)
diff --git a/app/client/cypress/fixtures/testdata.json b/app/client/cypress/fixtures/testdata.json
index 0d31926877..ea75e9ee3d 100644
--- a/app/client/cypress/fixtures/testdata.json
+++ b/app/client/cypress/fixtures/testdata.json
@@ -37,5 +37,9 @@
"prevUrl": ".data.previous}}",
"methodsWithParam": "users?page=2",
"invalidHeader": "invalid",
- "invalidValue": "invalid"
-}
+ "invalidValue": "invalid",
+ "Put": "PUT",
+ "Get": "GET",
+ "next": "?page=2&pageSize=10",
+ "prev": "?page=1&pageSize=10"
+}
\ No newline at end of file
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js
index a5a9c85f6f..166a3d6701 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js
@@ -30,9 +30,10 @@ describe("API Panel Test Functionality", function() {
.click({ force: true })
.focus()
.type(json, { force: true });
+ cy.WaitAutoSave();
+ cy.RunAPI();
+ cy.validateRequest(testdata.baseUrl2, testdata.methodput, testdata.Put);
});
- cy.WaitAutoSave();
- cy.RunAPI();
cy.ResponseStatusCheck("200 OK");
cy.log("Response code check successful");
cy.ResponseCheck("updatedAt");
@@ -55,9 +56,10 @@ describe("API Panel Test Functionality", function() {
.click({ force: true })
.focus()
.type(json, { force: true });
+ cy.WaitAutoSave();
+ cy.RunAPI();
+ cy.validateRequest(testdata.baseUrl2, testdata.methodpost, testdata.Post);
});
- cy.WaitAutoSave();
- cy.RunAPI();
cy.ResponseStatusCheck("201 CREATED");
cy.log("Response code check successful");
cy.ResponseCheck("createdAt");
@@ -80,9 +82,14 @@ describe("API Panel Test Functionality", function() {
.click({ force: true })
.focus()
.type(json, { force: true });
+ cy.WaitAutoSave();
+ cy.RunAPI();
+ cy.validateRequest(
+ testdata.baseUrl2,
+ testdata.methodpatch,
+ testdata.Patch,
+ );
});
- cy.WaitAutoSave();
- cy.RunAPI();
cy.ResponseStatusCheck("200 OK");
cy.log("Response code check successful");
cy.ResponseCheck("updatedAt");
@@ -101,6 +108,11 @@ describe("API Panel Test Functionality", function() {
);
cy.WaitAutoSave();
cy.RunAPI();
+ cy.validateRequest(
+ testdata.baseUrl2,
+ testdata.methodpatch,
+ testdata.Delete,
+ );
cy.ResponseStatusCheck("204 NO_CONTENT");
cy.log("Response code check successful");
});
@@ -112,6 +124,7 @@ describe("API Panel Test Functionality", function() {
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
cy.WaitAutoSave();
cy.RunAPI();
+ cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get);
cy.ResponseStatusCheck(testdata.successStatusCode);
cy.log("Response code check successful");
cy.ResponseCheck(testdata.responsetext);
@@ -120,12 +133,22 @@ describe("API Panel Test Functionality", function() {
cy.selectPaginationType(apiwidget.paginationWithUrl);
cy.enterUrl(apiname, apiwidget.panigationNextUrl, testdata.nextUrl);
cy.clickTest(apiwidget.TestNextUrl);
+ cy.validateRequest(
+ testdata.baseUrl,
+ testdata.methods.concat(testdata.next),
+ testdata.Get,
+ );
cy.ResponseStatusCheck(testdata.successStatusCode);
cy.log("Response code check successful");
cy.ResponseCheck("Josh M Krantz");
cy.log("Response data check successful");
cy.enterUrl(apiname, apiwidget.panigationPrevUrl, testdata.prevUrl);
cy.clickTest(apiwidget.TestPreUrl);
+ cy.validateRequest(
+ testdata.baseUrl,
+ testdata.methods.concat(testdata.prev),
+ testdata.Get,
+ );
cy.ResponseStatusCheck(testdata.successStatusCode);
cy.log("Response code check successful");
cy.ResponseCheck(testdata.responsetext);
@@ -138,6 +161,7 @@ describe("API Panel Test Functionality", function() {
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.queryAndValue);
cy.WaitAutoSave();
cy.RunAPI();
+ cy.validateRequest(testdata.baseUrl, testdata.queryAndValue, testdata.Get);
cy.ResponseStatusCheck("200 OK");
cy.log("Response code check successful");
cy.ResponseCheck(testdata.responsetext3);
@@ -157,6 +181,7 @@ describe("API Panel Test Functionality", function() {
);
cy.WaitAutoSave();
cy.RunAPI();
+ cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get);
cy.ResponseStatusCheck("5000");
cy.log("Response code check successful");
cy.ResponseCheck("Invalid value for Content-Type");
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js
index 7629cee44e..32ed8cc158 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js
@@ -9,8 +9,8 @@ describe("API Panel Test Functionality", function() {
cy.CreateAPI("FirstAPI");
cy.log("Creation of FirstAPI Action successful");
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
- cy.WaitAutoSave();
- cy.RunAPI();
+ cy.SaveAndRunAPI();
+ cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get);
cy.ResponseStatusCheck(testdata.successStatusCode);
cy.get(apiwidget.createApiOnSideBar)
.first()
diff --git a/app/client/cypress/locators/apiWidgetslocator.json b/app/client/cypress/locators/apiWidgetslocator.json
index 4d94ca1b2a..263257a37c 100644
--- a/app/client/cypress/locators/apiWidgetslocator.json
+++ b/app/client/cypress/locators/apiWidgetslocator.json
@@ -34,5 +34,12 @@
"TestNextUrl": ".t--apiFormPaginationNextTest",
"TestPreUrl": ".t--apiFormPaginationPrevTest",
"EditApiName": "img[alt='Edit pen']",
- "ApiName": ".t--action-name-edit-field span"
+ "ApiName": ".t--action-name-edit-field span",
+ "Request": "//li[text()='Request']",
+ "RequestURL": "(//span[@class='bp3-tree-node-label']/span)[1]",
+ "RequestMethod": "(//span[@class='bp3-tree-node-label']/span)[2]",
+ "content-Type": "(//span[@class='bp3-tree-node-label']/span)[3]",
+ "requestBody": "(//div[contains(@class,'bp3-collapse-body')]//textarea)[1]",
+ "showrequest": "span:contains('Show Request')",
+ "Responsetab": "//li[text()='Response Body']"
}
diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js
index 65cac3a16d..183ce923da 100644
--- a/app/client/cypress/support/commands.js
+++ b/app/client/cypress/support/commands.js
@@ -377,6 +377,17 @@ Cypress.Commands.add("SaveAndRunAPI", () => {
cy.RunAPI();
});
+Cypress.Commands.add("validateRequest", (baseurl, path, verb) => {
+ cy.xpath(apiwidget.Request)
+ .should("be.visible")
+ .click({ force: true });
+ cy.xpath(apiwidget.RequestURL).contains(baseurl.concat(path));
+ cy.xpath(apiwidget.RequestMethod).contains(verb);
+ cy.xpath(apiwidget.Responsetab)
+ .should("be.visible")
+ .click({ force: true });
+});
+
Cypress.Commands.add("SelectAction", action => {
cy.get(ApiEditor.ApiVerb)
.first()
@@ -471,9 +482,12 @@ Cypress.Commands.add("selectPaginationType", option => {
});
Cypress.Commands.add("clickTest", testbutton => {
+ cy.wait(2000);
+ cy.wait("@saveAction");
cy.get(testbutton)
.first()
.click({ force: true });
+ cy.wait("@postExecute");
});
Cypress.Commands.add("enterUrl", (apiname, url, value) => {
From 9575abc6025f1717f17c9206160d0159273b76bf Mon Sep 17 00:00:00 2001
From: Arpit Mohan
Date: Wed, 29 Jul 2020 11:33:40 +0530
Subject: [PATCH 3/4] Fixing minor typos in the README (#186)
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index bacaccf60b..861820a3ce 100644
--- a/README.md
+++ b/README.md
@@ -10,14 +10,14 @@
-[](https://github.com/appsmithorg/appsmith/releases/latest)
+[](https://github.com/appsmithorg/appsmith/releases/latest)
[](https://appsmith.com)
[](https://discord.gg/rBTTVJp)
[](https://docs.appsmith.com)
-
+
- Built with ❤︎ & empathy
+ Built with empathy, not just ❤︎
@@ -37,7 +37,7 @@ Appsmith provides a better way of building internal tools by visualising them as
## Features
-* **Build custom UI**: Drag & drop, resize and style widgets **without HTLM / CSS**. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui)
+* **Build custom UI**: Drag & drop, resize and style widgets **without HTML / CSS**. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui)
* **Query data**: Query & update your database directly from the UI. Supports **postgres, mongo, REST & GraphQL APIs**. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui/displaying-api-data)
* **JS Logic**: Write snippets of business logic using JS to transform data, manipuate UI or trigger workflows
* **Data Workflows**: Simple configuration to create flows when users interact with the UI. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui/calling-apis-from-widgets)
From 08d2a94768f3ca5a095c00026c0d67909f484a3a Mon Sep 17 00:00:00 2001
From: Abhinav Jha
Date: Wed, 29 Jul 2020 11:45:06 +0530
Subject: [PATCH 4/4] Change derivedpropertiesmap for selectedRow to use
filtered table data (#173)
* Add a new meta property to tableWidget called filteredTableData which will be used by the table to filter
* Use derived properties to update filteredTableData
* Check if search is done via actions, if so, filteredTableData is the same as tableData
---
app/client/src/widgets/TableWidget.tsx | 30 +++++++-------------------
1 file changed, 8 insertions(+), 22 deletions(-)
diff --git a/app/client/src/widgets/TableWidget.tsx b/app/client/src/widgets/TableWidget.tsx
index 4dad1fd1b2..a40569f824 100644
--- a/app/client/src/widgets/TableWidget.tsx
+++ b/app/client/src/widgets/TableWidget.tsx
@@ -34,14 +34,14 @@ class TableWidget extends BaseWidget {
label: VALIDATION_TYPES.TEXT,
selectedRowIndex: VALIDATION_TYPES.NUMBER,
searchText: VALIDATION_TYPES.TEXT,
- // columnActions: VALIDATION_TYPES.ARRAY_ACTION_SELECTOR,
- // onRowSelected: VALIDATION_TYPES.ACTION_SELECTOR,
- // onPageChange: VALIDATION_TYPES.ACTION_SELECTOR,
+ filteredTableData: VALIDATION_TYPES.TABLE_DATA,
};
}
static getDerivedPropertiesMap() {
return {
- selectedRow: "{{this.tableData[this.selectedRowIndex]}}",
+ filteredTableData:
+ "{{!this.onSearchTextChanged ? this.tableData.filter((item) => Object.values(item).join(', ').toUpperCase().includes(this.searchText.toUpperCase())) : this.tableData}}",
+ selectedRow: "{{this.filteredTableData[this.selectedRowIndex]}}",
};
}
@@ -51,6 +51,8 @@ class TableWidget extends BaseWidget {
pageSize: undefined,
selectedRowIndex: -1,
searchText: "",
+ // The following meta property is used for rendering the table.
+ filteredTableData: [],
};
}
@@ -198,26 +200,10 @@ class TableWidget extends BaseWidget {
return updatedTableData;
};
- searchTableData = (tableData: object[]) => {
- if (!tableData || !tableData.length) {
- return [];
- }
- const searchKey =
- this.props.searchText !== undefined
- ? this.props.searchText.toString().toUpperCase()
- : "";
- return tableData.filter((item: object) => {
- return Object.values(item)
- .join(", ")
- .toUpperCase()
- .includes(searchKey);
- });
- };
-
getPageView() {
- const { tableData, hiddenColumns } = this.props;
+ const { tableData, hiddenColumns, filteredTableData } = this.props;
const tableColumns = this.getTableColumns(tableData);
- const filteredTableData = this.searchTableData(tableData);
+ // Use the filtered data to render the table.
const transformedData = this.transformData(filteredTableData, tableColumns);
const serverSidePaginationEnabled = (this.props
.serverSidePaginationEnabled &&