From 89ae5ddf7f2286c47ed50c69d6152a3db00b1573 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 20 Jan 2023 15:36:49 +0600 Subject: [PATCH] fix: Forking fails when template has REST datasource with Bearer Token Authentication (#19930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description When an application is exported with credentials i.e. template application, it does not contain the bearer token authentication credentials. As a result, when user is trying to import this application, the datasource configuration popup appears. This PR fixes this issue. TL;DR enable export-import application with bearer token authentication Fixes #19415 Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - Manual - Jest ### Test Plan > Add Testsmith test cases links that relate to this PR ### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) ## Checklist: ### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag ### QA activity: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test --- .../models/DecryptedSensitiveFields.java | 2 + .../ImportExportApplicationServiceCEImpl.java | 7 ++ ...mportExportApplicationServiceCEImplV2.java | 8 +- .../ImportExportApplicationServiceTests.java | 79 ++++++++++++++++++ ...ImportExportApplicationServiceV2Tests.java | 81 ++++++++++++++++++- 5 files changed, 174 insertions(+), 3 deletions(-) diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DecryptedSensitiveFields.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DecryptedSensitiveFields.java index 6b6a64bb3f..4d41a1fd32 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DecryptedSensitiveFields.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DecryptedSensitiveFields.java @@ -30,6 +30,8 @@ public class DecryptedSensitiveFields { BasicAuth basicAuth; OAuth2 openAuth2; + + BearerTokenAuth bearerTokenAuth; public DecryptedSensitiveFields(AuthenticationResponse authResponse) { this.token = authResponse.getToken(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java index 58d91e609d..5f2be404b0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java @@ -7,6 +7,7 @@ import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.BaseDomain; import com.appsmith.external.models.BasicAuth; +import com.appsmith.external.models.BearerTokenAuth; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; @@ -1983,6 +1984,10 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica authResponse.setExpiresAt(Instant.now()); auth2.setAuthenticationResponse(authResponse); datasource.getDatasourceConfiguration().setAuthentication(auth2); + } else if (StringUtils.equals(authType, BearerTokenAuth.class.getName())) { + BearerTokenAuth auth = new BearerTokenAuth(); + auth.setBearerToken(decryptedFields.getBearerTokenAuth().getBearerToken()); + datasource.getDatasourceConfiguration().setAuthentication(auth); } return datasource; } @@ -2020,6 +2025,8 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica } else if (authentication instanceof BasicAuth auth) { dsDecryptedFields.setPassword(auth.getPassword()); dsDecryptedFields.setBasicAuth(auth); + } else if (authentication instanceof BearerTokenAuth auth) { + dsDecryptedFields.setBearerTokenAuth(auth); } dsDecryptedFields.setAuthType(authentication.getClass().getName()); return dsDecryptedFields; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java index 0ac9d9213d..68193a6691 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java @@ -7,6 +7,7 @@ import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.BaseDomain; import com.appsmith.external.models.BasicAuth; +import com.appsmith.external.models.BearerTokenAuth; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; @@ -32,7 +33,6 @@ import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ActionCollectionDTO; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; -import com.appsmith.server.dtos.CustomJSLibApplicationDTO; import com.appsmith.server.dtos.ExportFileDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; @@ -2019,6 +2019,10 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli authResponse.setExpiresAt(Instant.now()); auth2.setAuthenticationResponse(authResponse); datasource.getDatasourceConfiguration().setAuthentication(auth2); + } else if (org.apache.commons.lang.StringUtils.equals(authType, BearerTokenAuth.class.getName())) { + BearerTokenAuth auth = new BearerTokenAuth(); + auth.setBearerToken(decryptedFields.getBearerTokenAuth().getBearerToken()); + datasource.getDatasourceConfiguration().setAuthentication(auth); } return datasource; } @@ -2059,6 +2063,8 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli BasicAuth auth = (BasicAuth) authentication; dsDecryptedFields.setPassword(auth.getPassword()); dsDecryptedFields.setBasicAuth(auth); + } else if (authentication instanceof BearerTokenAuth auth) { + dsDecryptedFields.setBearerTokenAuth(auth); } dsDecryptedFields.setAuthType(authentication.getClass().getName()); return dsDecryptedFields; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java index bf55b9887e..53f215fffe 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java @@ -3,13 +3,17 @@ package com.appsmith.server.solutions; import com.appsmith.external.helpers.AppsmithBeanUtils; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionDTO; +import com.appsmith.external.models.BearerTokenAuth; +import com.appsmith.external.models.Connection; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DecryptedSensitiveFields; import com.appsmith.external.models.InvisibleActionFields; import com.appsmith.external.models.PluginType; import com.appsmith.external.models.Policy; import com.appsmith.external.models.Property; +import com.appsmith.external.models.SSLDetails; import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.SerialiseApplicationObjective; import com.appsmith.server.domains.ActionCollection; @@ -3509,4 +3513,79 @@ public class ImportExportApplicationServiceTests { .verifyComplete(); } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_WithBearerTokenAndExportWithConfig_exportedWithDecryptedFields() { + String randomUUID = UUID.randomUUID().toString(); + + Workspace testWorkspace = new Workspace(); + testWorkspace.setName("workspace-" + randomUUID); + // User apiUser = userService.findByEmail("api_user").block(); + Mono workspaceMono = workspaceService.create(testWorkspace).cache(); + + Mono applicationMono = workspaceMono.flatMap(workspace -> { + Application testApplication = new Application(); + testApplication.setName("application-" + randomUUID); + testApplication.setExportWithConfiguration(true); + testApplication.setWorkspaceId(workspace.getId()); + return applicationPageService.createApplication(testApplication); + }).flatMap(application -> { + ApplicationAccessDTO accessDTO = new ApplicationAccessDTO(); + accessDTO.setPublicAccess(true); + return applicationService.changeViewAccess(application.getId(), accessDTO).thenReturn(application); + }); + + Mono datasourceMono = workspaceMono.zipWith(pluginRepository.findByPackageName("restapi-plugin")) + .flatMap(objects -> { + Workspace workspace = objects.getT1(); + Plugin plugin = objects.getT2(); + + Datasource datasource = new Datasource(); + datasource.setPluginId(plugin.getId()); + datasource.setName("RestAPIWithBearerToken"); + datasource.setWorkspaceId(workspace.getId()); + datasource.setIsConfigured(true); + + BearerTokenAuth bearerTokenAuth = new BearerTokenAuth(); + bearerTokenAuth.setBearerToken("token_" + randomUUID); + + SSLDetails sslDetails = new SSLDetails(); + sslDetails.setAuthType(SSLDetails.AuthType.DEFAULT); + Connection connection = new Connection(); + connection.setSsl(sslDetails); + + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(bearerTokenAuth); + datasourceConfiguration.setConnection(connection); + datasourceConfiguration.setConnection(new Connection()); + datasourceConfiguration.setUrl("https://mock-api.appsmith.com"); + + datasource.setDatasourceConfiguration(datasourceConfiguration); + return datasourceService.create(datasource); + }); + + Mono exportAppMono = Mono.zip(applicationMono, datasourceMono).flatMap(objects -> { + ApplicationPage applicationPage = objects.getT1().getPages().get(0); + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(applicationPage.getId()); + action.setPluginId(objects.getT2().getPluginId()); + action.setDatasource(objects.getT2()); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + actionConfiguration.setPath("/test/path"); + action.setActionConfiguration(actionConfiguration); + + return layoutActionService.createSingleAction(action, Boolean.FALSE) + .then(importExportApplicationService.exportApplicationById(objects.getT1().getId(), "")); + }); + + StepVerifier.create(exportAppMono).assertNext(applicationJson -> { + assertThat(applicationJson.getDecryptedFields()).isNotEmpty(); + DecryptedSensitiveFields fields = applicationJson.getDecryptedFields().get("RestAPIWithBearerToken"); + assertThat(fields.getBearerTokenAuth().getBearerToken()).isEqualTo("token_" + randomUUID); + }).verifyComplete(); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java index 3c65b77735..585c6cf8e0 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java @@ -3,13 +3,17 @@ package com.appsmith.server.solutions; import com.appsmith.external.helpers.AppsmithBeanUtils; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionDTO; +import com.appsmith.external.models.BearerTokenAuth; +import com.appsmith.external.models.Connection; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DecryptedSensitiveFields; import com.appsmith.external.models.InvisibleActionFields; import com.appsmith.external.models.PluginType; import com.appsmith.external.models.Policy; import com.appsmith.external.models.Property; +import com.appsmith.external.models.SSLDetails; import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.SerialiseApplicationObjective; import com.appsmith.server.domains.ActionCollection; @@ -30,7 +34,6 @@ import com.appsmith.server.dtos.ApplicationAccessDTO; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.ApplicationPagesDTO; -import com.appsmith.server.dtos.CustomJSLibApplicationDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.PageNameIdDTO; import com.appsmith.server.exceptions.AppsmithError; @@ -95,7 +98,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -3601,4 +3603,79 @@ public class ImportExportApplicationServiceV2Tests { .verifyComplete(); } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_WithBearerTokenAndExportWithConfig_exportedWithDecryptedFields() { + String randomUUID = UUID.randomUUID().toString(); + + Workspace testWorkspace = new Workspace(); + testWorkspace.setName("workspace-" + randomUUID); + // User apiUser = userService.findByEmail("api_user").block(); + Mono workspaceMono = workspaceService.create(testWorkspace).cache(); + + Mono applicationMono = workspaceMono.flatMap(workspace -> { + Application testApplication = new Application(); + testApplication.setName("application-" + randomUUID); + testApplication.setExportWithConfiguration(true); + testApplication.setWorkspaceId(workspace.getId()); + return applicationPageService.createApplication(testApplication); + }).flatMap(application -> { + ApplicationAccessDTO accessDTO = new ApplicationAccessDTO(); + accessDTO.setPublicAccess(true); + return applicationService.changeViewAccess(application.getId(), accessDTO).thenReturn(application); + }); + + Mono datasourceMono = workspaceMono.zipWith(pluginRepository.findByPackageName("restapi-plugin")) + .flatMap(objects -> { + Workspace workspace = objects.getT1(); + Plugin plugin = objects.getT2(); + + Datasource datasource = new Datasource(); + datasource.setPluginId(plugin.getId()); + datasource.setName("RestAPIWithBearerToken"); + datasource.setWorkspaceId(workspace.getId()); + datasource.setIsConfigured(true); + + BearerTokenAuth bearerTokenAuth = new BearerTokenAuth(); + bearerTokenAuth.setBearerToken("token_" + randomUUID); + + SSLDetails sslDetails = new SSLDetails(); + sslDetails.setAuthType(SSLDetails.AuthType.DEFAULT); + Connection connection = new Connection(); + connection.setSsl(sslDetails); + + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(bearerTokenAuth); + datasourceConfiguration.setConnection(connection); + datasourceConfiguration.setConnection(new Connection()); + datasourceConfiguration.setUrl("https://mock-api.appsmith.com"); + + datasource.setDatasourceConfiguration(datasourceConfiguration); + return datasourceService.create(datasource); + }); + + Mono exportAppMono = Mono.zip(applicationMono, datasourceMono).flatMap(objects -> { + ApplicationPage applicationPage = objects.getT1().getPages().get(0); + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(applicationPage.getId()); + action.setPluginId(objects.getT2().getPluginId()); + action.setDatasource(objects.getT2()); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + actionConfiguration.setPath("/test/path"); + action.setActionConfiguration(actionConfiguration); + + return layoutActionService.createSingleAction(action, Boolean.FALSE) + .then(importExportApplicationService.exportApplicationById(objects.getT1().getId(), "")); + }); + + StepVerifier.create(exportAppMono).assertNext(applicationJson -> { + assertThat(applicationJson.getDecryptedFields()).isNotEmpty(); + DecryptedSensitiveFields fields = applicationJson.getDecryptedFields().get("RestAPIWithBearerToken"); + assertThat(fields.getBearerTokenAuth().getBearerToken()).isEqualTo("token_" + randomUUID); + }).verifyComplete(); + } }