diff --git a/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/QueryPane/S3_1_spec.js b/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/QueryPane/S3_1_spec.js index 412d7c5c1e..6c0010e949 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/QueryPane/S3_1_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/QueryPane/S3_1_spec.js @@ -120,9 +120,9 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications", cy.onlyQueryRun(); cy.wait("@postExecute").then(({ response }) => { expect(response.body.data.isExecutionSuccess).to.eq(false); - expect(response.body.data.pluginErrorDetails.appsmithErrorMessage).to.contains( - "File content is not base64 encoded.", - ); + expect( + response.body.data.pluginErrorDetails.appsmithErrorMessage, + ).to.contains("File content is not base64 encoded."); }); cy.ValidateAndSelectDropdownOption( formControls.s3CreateFileDataType, @@ -253,7 +253,9 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications", cy.onlyQueryRun(); cy.wait("@postExecute").then(({ response }) => { expect(response.body.data.isExecutionSuccess).to.eq(false); - expect(response.body.data.pluginErrorDetails.appsmithErrorMessage).to.contain( + expect( + response.body.data.pluginErrorDetails.appsmithErrorMessage, + ).to.contain( "Your S3 query failed to execute. To know more please check the error details.", ); }); @@ -263,7 +265,9 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications", cy.onlyQueryRun(); cy.wait("@postExecute").then(({ response }) => { expect(response.body.data.isExecutionSuccess).to.eq(false); - expect(response.body.data.pluginErrorDetails.appsmithErrorMessage).to.contain( + expect( + response.body.data.pluginErrorDetails.appsmithErrorMessage, + ).to.contain( "Your S3 query failed to execute. To know more please check the error details.", ); }); diff --git a/app/client/public/index.html b/app/client/public/index.html index 0ef03d00ec..3f8319806a 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -46,6 +46,14 @@ } + + @@ -166,6 +174,10 @@ hideWatermark: parseConfig("__APPSMITH_HIDE_WATERMARK__"), disableIframeWidgetSandbox: parseConfig("__APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX__"), }; + + gapiLoaded = () => { + window.googleAPIsLoaded = true; + } diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index b388092abc..d9e061d5ea 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -366,6 +366,19 @@ export const initializeDatasourceFormDefaults = (pluginType: string) => { }; }; +// In case of access to specific sheets in google sheet datasource, this action +// is used for handling file picker callback, when user selects files/cancels the selection +// this callback action will be triggered +export const filePickerCallbackAction = (data: { + action: string; + datasourceId: string; +}) => { + return { + type: ReduxActionTypes.FILE_PICKER_CALLBACK_ACTION, + payload: data, + }; +}; + export default { fetchDatasources, initDatasourcePane, diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 16188e662f..bb4625edcb 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -760,6 +760,8 @@ export const ReduxActionTypes = { AUTOLAYOUT_REORDER_WIDGETS: "AUTOLAYOUT_REORDER_WIDGETS", AUTOLAYOUT_ADD_NEW_WIDGETS: "AUTOLAYOUT_ADD_NEW_WIDGETS", RECALCULATE_COLUMNS: "RECALCULATE_COLUMNS", + SET_GSHEET_TOKEN: "SET_GSHEET_TOKEN", + FILE_PICKER_CALLBACK_ACTION: "FILE_PICKER_CALLBACK_ACTION", }; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; diff --git a/app/client/src/entities/Datasource/index.ts b/app/client/src/entities/Datasource/index.ts index b8bce27d78..a2b0b79fb2 100644 --- a/app/client/src/entities/Datasource/index.ts +++ b/app/client/src/entities/Datasource/index.ts @@ -11,6 +11,7 @@ export enum AuthenticationStatus { NONE = "NONE", IN_PROGRESS = "IN_PROGRESS", SUCCESS = "SUCCESS", + FAILURE = "FAILURE", } export interface DatasourceAuthentication { authType?: string; @@ -104,6 +105,11 @@ export interface Datasource extends BaseDatasource { success?: boolean; } +export interface TokenResponse { + datasource: Datasource; + token: string; +} + export interface MockDatasource { name: string; description: string; diff --git a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx index 5eb3e9fd26..37474d384d 100644 --- a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx +++ b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx @@ -34,6 +34,7 @@ import Connected from "../DataSourceEditor/Connected"; import { getCurrentApplicationId, + getGsheetToken, getPagePermissions, } from "selectors/editorSelectors"; import DatasourceAuth from "pages/common/datasourceAuth"; @@ -88,6 +89,7 @@ interface StateProps extends JSONtoFormProps { isDatasourceBeingSaved: boolean; isDatasourceBeingSavedFromPopup: boolean; isFormDirty: boolean; + gsheetToken?: string; } interface DatasourceFormFunctions { discardTempDatasource: () => void; @@ -260,6 +262,7 @@ class DatasourceSaaSEditor extends JSONtoForm { datasourceButtonConfiguration, datasourceId, formData, + gsheetToken, hiddenHeader, pageId, plugin, @@ -376,6 +379,7 @@ class DatasourceSaaSEditor extends JSONtoForm { datasourceDeleteTrigger={this.datasourceDeleteTrigger} formData={formData} getSanitizedFormData={_.memoize(this.getSanitizedData)} + gsheetToken={gsheetToken} isInvalid={this.validate()} pageId={pageId} shouldDisplayAuthMessage={!isGoogleSheetPlugin} @@ -436,6 +440,8 @@ const mapStateToProps = (state: AppState, props: any) => { ...pagePermissions, ]); + const gsheetToken = getGsheetToken(state); + return { datasource, datasourceButtonConfiguration, @@ -464,6 +470,7 @@ const mapStateToProps = (state: AppState, props: any) => { isFormDirty, canCreateDatasourceActions, featureFlags: selectFeatureFlags(state), + gsheetToken, }; }; diff --git a/app/client/src/pages/common/datasourceAuth/index.tsx b/app/client/src/pages/common/datasourceAuth/index.tsx index e8d3afa1a0..eb43c447a3 100644 --- a/app/client/src/pages/common/datasourceAuth/index.tsx +++ b/app/client/src/pages/common/datasourceAuth/index.tsx @@ -16,6 +16,7 @@ import { setDatasourceViewMode, createDatasourceFromForm, toggleSaveActionFlag, + filePickerCallbackAction, } from "actions/datasourceActions"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { getCurrentApplicationId } from "selectors/editorSelectors"; @@ -46,6 +47,7 @@ import { hasDeleteDatasourcePermission, hasManageDatasourcePermission, } from "@appsmith/utils/permissionHelpers"; +import { getAppsmithConfigs } from "ce/configs"; interface Props { datasource: Datasource; @@ -59,6 +61,7 @@ interface Props { triggerSave?: boolean; isFormDirty?: boolean; datasourceDeleteTrigger: () => void; + gsheetToken?: string; } export type DatasourceFormButtonTypes = Record; @@ -119,6 +122,7 @@ function DatasourceAuth({ shouldDisplayAuthMessage = true, triggerSave, isFormDirty, + gsheetToken, }: Props) { const authType = formData && "authType" in formData @@ -148,9 +152,18 @@ function DatasourceAuth({ const pageId = (pageIdQuery || pageIdProp) as string; const [confirmDelete, setConfirmDelete] = useState(false); + + const [scriptLoadedFlag] = useState( + (window as any).googleAPIsLoaded, + ); + const [pickerInitiated, setPickerInitiated] = useState(false); const dsName = datasource?.name; const orgId = datasource?.workspaceId; + // objects gapi and google are set, when google apis script is loaded + const gapi: any = (window as any).gapi; + const google: any = (window as any).google; + useEffect(() => { if (confirmDelete) { delayConfirmDeleteToFalse(); @@ -285,6 +298,50 @@ function DatasourceAuth({ } }; + useEffect(() => { + // This loads the picker object in gapi script + if (!!gsheetToken && !!gapi) { + gapi.load("client:picker", async () => { + await gapi.client.load( + "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + ); + setPickerInitiated(true); + }); + } + }, [scriptLoadedFlag, gsheetToken]); + + useEffect(() => { + if (!!gsheetToken && scriptLoadedFlag && pickerInitiated && !!google) { + createPicker(gsheetToken); + } + }, [gsheetToken, scriptLoadedFlag, pickerInitiated]); + + const createPicker = async (accessToken: string) => { + const { enableGoogleOAuth } = getAppsmithConfigs(); + const googleOAuthClientId: string = enableGoogleOAuth + ""; + const APP_ID = googleOAuthClientId.split("-")[0]; + const view = new google.picker.View(google.picker.ViewId.SPREADSHEETS); + view.setMimeTypes("application/vnd.google-apps.spreadsheet"); + const picker = new google.picker.PickerBuilder() + .enableFeature(google.picker.Feature.NAV_HIDDEN) + .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) + .setAppId(APP_ID) + .setOAuthToken(accessToken) + .addView(view) + .setCallback(pickerCallback) + .build(); + picker.setVisible(true); + }; + + const pickerCallback = async (data: any) => { + dispatch( + filePickerCallbackAction({ + action: data.action, + datasourceId: datasourceId, + }), + ); + }; + const createMode = datasourceId === TEMP_DATASOURCE_ID; const datasourceButtonsComponentMap = (buttonType: string): JSX.Element => { diff --git a/app/client/src/reducers/entityReducers/datasourceReducer.ts b/app/client/src/reducers/entityReducers/datasourceReducer.ts index f7cdb0597f..a1e2076b71 100644 --- a/app/client/src/reducers/entityReducers/datasourceReducer.ts +++ b/app/client/src/reducers/entityReducers/datasourceReducer.ts @@ -26,6 +26,7 @@ export interface DatasourceDataState { unconfiguredList: Datasource[]; isDatasourceBeingSaved: boolean; isDatasourceBeingSavedFromPopup: boolean; + gsheetToken: string; } const initialState: DatasourceDataState = { @@ -43,6 +44,7 @@ const initialState: DatasourceDataState = { unconfiguredList: [], isDatasourceBeingSaved: false, isDatasourceBeingSavedFromPopup: false, + gsheetToken: "", }; const datasourceReducer = createReducer(initialState, { @@ -455,6 +457,15 @@ const datasourceReducer = createReducer(initialState, { isDatasourceBeingSavedFromPopup: action.payload.isDSSavedFromPopup, }; }, + [ReduxActionTypes.SET_GSHEET_TOKEN]: ( + state: DatasourceDataState, + action: ReduxAction<{ gsheetToken: string }>, + ) => { + return { + ...state, + gsheetToken: action.payload.gsheetToken, + }; + }, }); export default datasourceReducer; diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index 4437a9f5bb..7f6afa1e5f 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -13,7 +13,7 @@ import { getFormValues, initialize, } from "redux-form"; -import _, { merge, isEmpty, get, set } from "lodash"; +import { merge, isEmpty, get, set, partition, omit } from "lodash"; import equal from "fast-deep-equal/es6"; import { ReduxAction, @@ -47,10 +47,15 @@ import { removeTempDatasource, createDatasourceSuccess, resetDefaultKeyValPairFlag, + updateDatasource, } from "actions/datasourceActions"; import { ApiResponse } from "api/ApiResponses"; import DatasourcesApi, { CreateDatasourceConfig } from "api/DatasourcesApi"; -import { Datasource } from "entities/Datasource"; +import { + AuthenticationStatus, + Datasource, + TokenResponse, +} from "entities/Datasource"; import { INTEGRATION_EDITOR_MODES, INTEGRATION_TABS } from "constants/routes"; import history from "utils/history"; @@ -332,7 +337,7 @@ function* updateDatasourceSaga( ) { try { const queryParams = getQueryParams(); - const datasourcePayload = _.omit(actionPayload.payload, "name"); + const datasourcePayload = omit(actionPayload.payload, "name"); datasourcePayload.isConfigured = true; // when clicking save button, it should be changed as configured const response: ApiResponse = yield DatasourcesApi.updateDatasource( @@ -458,7 +463,7 @@ function* getOAuthAccessTokenSaga( } try { // Get access token for datasource - const response: ApiResponse = yield OAuthApi.getAccessToken( + const response: ApiResponse = yield OAuthApi.getAccessToken( datasourceId, appsmithToken, ); @@ -466,12 +471,22 @@ function* getOAuthAccessTokenSaga( // Update the datasource object yield put({ type: ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS, - payload: response.data, - }); - Toaster.show({ - text: OAUTH_AUTHORIZATION_SUCCESSFUL, - variant: Variant.success, + payload: response.data.datasource, }); + + if (!!response.data.token) { + yield put({ + type: ReduxActionTypes.SET_GSHEET_TOKEN, + payload: { + gsheetToken: response.data.token, + }, + }); + } else { + Toaster.show({ + text: OAUTH_AUTHORIZATION_SUCCESSFUL, + variant: Variant.success, + }); + } // Remove the token because it is supposed to be short lived localStorage.removeItem(APPSMITH_TOKEN_STORAGE_KEY); } @@ -702,7 +717,7 @@ function* createDatasourceFromFormSaga( formConfig, ); - const payload = _.omit(merge(initialValues, actionPayload.payload), [ + const payload = omit(merge(initialValues, actionPayload.payload), [ "id", "new", "type", @@ -778,12 +793,12 @@ function* changeDatasourceSaga( const draft: Record = yield select(getDatasourceDraft, id); const pageId: string = yield select(getCurrentPageId); let data; - if (_.isEmpty(draft)) { + if (isEmpty(draft)) { data = datasource; } else { data = draft; } - yield put(initialize(DATASOURCE_DB_FORM, _.omit(data, ["name"]))); + yield put(initialize(DATASOURCE_DB_FORM, omit(data, ["name"]))); // on reconnect modal, it shouldn't be redirected to datasource edit page if (shouldNotRedirect) return; // this redirects to the same route, so checking first. @@ -804,7 +819,7 @@ function* changeDatasourceSaga( ); yield put( // @ts-expect-error: data is of type unknown - updateReplayEntity(data.id, _.omit(data, ["name"]), ENTITY_TYPE.DATASOURCE), + updateReplayEntity(data.id, omit(data, ["name"]), ENTITY_TYPE.DATASOURCE), ); } @@ -859,10 +874,10 @@ function* storeAsDatasourceSaga() { const { values } = yield select(getFormData, API_EDITOR_FORM_NAME); const applicationId: string = yield select(getCurrentApplicationId); const pageId: string | undefined = yield select(getCurrentPageId); - let datasource = _.get(values, "datasource"); - datasource = _.omit(datasource, ["name"]); - const originalHeaders = _.get(values, "actionConfiguration.headers", []); - const [datasourceHeaders, actionHeaders] = _.partition( + let datasource = get(values, "datasource"); + datasource = omit(datasource, ["name"]); + const originalHeaders = get(values, "actionConfiguration.headers", []); + const [datasourceHeaders, actionHeaders] = partition( originalHeaders, ({ key, value }: { key: string; value: string }) => { return !(isDynamicValue(key) || isDynamicValue(value)); @@ -881,11 +896,7 @@ function* storeAsDatasourceSaga() { (d) => !(d.key === "" && d.key === ""), ); - _.set( - datasource, - "datasourceConfiguration.headers", - filteredDatasourceHeaders, - ); + set(datasource, "datasourceConfiguration.headers", filteredDatasourceHeaders); yield put(createTempDatasourceFromForm(datasource)); const createDatasourceSuccessAction: unknown = yield take( @@ -912,7 +923,7 @@ function* storeAsDatasourceSaga() { function* updateDatasourceSuccessSaga(action: UpdateDatasourceSuccessAction) { const state: AppState = yield select(); - const actionRouteInfo = _.get(state, "ui.datasourcePane.actionRouteInfo"); + const actionRouteInfo = get(state, "ui.datasourcePane.actionRouteInfo"); const generateCRUDSupportedPlugin: GenerateCRUDEnabledPluginMap = yield select( getGenerateCRUDEnabledPluginMap, ); @@ -1153,6 +1164,30 @@ function* initializeFormWithDefaults( } } +function* filePickerActionCallbackSaga( + actionPayload: ReduxAction<{ action: string; datasourceId: string }>, +) { + try { + const { action, datasourceId } = actionPayload.payload; + yield put({ + type: ReduxActionTypes.SET_GSHEET_TOKEN, + payload: { + gsheetToken: "", + }, + }); + + if (action === "cancel") { + const datasource: Datasource = yield select(getDatasource, datasourceId); + set( + datasource, + "datasourceConfiguration.authentication.authenticationStatus", + AuthenticationStatus.FAILURE, + ); + yield put(updateDatasource(datasource)); + } + } catch (error) {} +} + export function* watchDatasourcesSagas() { yield all([ takeEvery(ReduxActionTypes.FETCH_DATASOURCES_INIT, fetchDatasourcesSaga), @@ -1215,5 +1250,9 @@ export function* watchDatasourcesSagas() { takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga), takeEvery(ReduxFormActionTypes.ARRAY_PUSH, formValueChangeSaga), takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga), + takeEvery( + ReduxActionTypes.FILE_PICKER_CALLBACK_ACTION, + filePickerActionCallbackSaga, + ), ]); } diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index b46ca791ce..e94ef76d8b 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -856,3 +856,6 @@ export const showCanvasTopSectionSelector = createSelector( return true; }, ); + +export const getGsheetToken = (state: AppState) => + state.entities.datasources.gsheetToken; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/OAuthResponseDTO.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/OAuthResponseDTO.java new file mode 100644 index 0000000000..9c5a9c7093 --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/OAuthResponseDTO.java @@ -0,0 +1,15 @@ +package com.appsmith.external.models; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class OAuthResponseDTO { + Datasource datasource; + String token; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/SaasControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/SaasControllerCE.java index 7c3083ec16..81f51deaf6 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/SaasControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/SaasControllerCE.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import com.appsmith.external.models.OAuthResponseDTO; @Slf4j @RequestMapping(Url.SAAS_URL) @@ -40,7 +41,7 @@ public class SaasControllerCE { } @PostMapping("/{datasourceId}/token") - public Mono> getAccessToken(@PathVariable String datasourceId, @RequestParam String appsmithToken, ServerWebExchange serverWebExchange) { + public Mono> getAccessToken(@PathVariable String datasourceId, @RequestParam String appsmithToken, ServerWebExchange serverWebExchange) { log.debug("Received callback for an OAuth2 authorization request"); return authenticationService.getAccessTokenFromCloud(datasourceId, appsmithToken) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCE.java index 742909568a..a3ab2053d9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCE.java @@ -1,6 +1,7 @@ package com.appsmith.server.solutions.ce; import com.appsmith.external.models.Datasource; +import com.appsmith.external.models.OAuthResponseDTO; import com.appsmith.server.dtos.AuthorizationCodeCallbackDTO; import org.springframework.http.server.reactive.ServerHttpRequest; import reactor.core.publisher.Mono; @@ -30,7 +31,7 @@ public interface AuthenticationServiceCE { Mono getAppsmithToken(String datasourceId, String pageId, String branchName, ServerHttpRequest request, String importForGit); - Mono getAccessTokenFromCloud(String datasourceId, String appsmithToken); + Mono getAccessTokenFromCloud(String datasourceId, String appsmithToken); Mono refreshAuthentication(Datasource datasource); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCEImpl.java index 99d082bd6d..550a7bd301 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/AuthenticationServiceCEImpl.java @@ -8,6 +8,7 @@ import com.appsmith.external.helpers.SSLHelper; import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.Datasource; +import com.appsmith.external.models.OAuthResponseDTO; import com.appsmith.external.models.DefaultResources; import com.appsmith.external.models.OAuth2; import com.appsmith.server.configurations.CloudServicesConfig; @@ -85,6 +86,8 @@ public class AuthenticationServiceCEImpl implements AuthenticationServiceCE { private final ConfigService configService; private final DatasourcePermission datasourcePermission; private final PagePermission pagePermission; + private static final String FILE_SPECIFIC_DRIVE_SCOPE = "https://www.googleapis.com/auth/drive.file"; + private static final String ACCESS_TOKEN_KEY = "access_token"; /** * This method is used by the generic OAuth2 implementation that is used by REST APIs. Here, we only populate all the required fields @@ -385,7 +388,7 @@ public class AuthenticationServiceCEImpl implements AuthenticationServiceCE { })); } - public Mono getAccessTokenFromCloud(String datasourceId, String appsmithToken) { + public Mono getAccessTokenFromCloud(String datasourceId, String appsmithToken) { // Check if user has access to manage datasource // If yes, check if datasource is in intermediate state // If yes, request for token and store in datasource @@ -437,10 +440,21 @@ public class AuthenticationServiceCEImpl implements AuthenticationServiceCE { } } datasource.getDatasourceConfiguration().setAuthentication(oAuth2); - return Mono.just(datasource); + String accessToken = ""; + if (oAuth2.getScope() != null && oAuth2.getScope().contains(FILE_SPECIFIC_DRIVE_SCOPE)) { + accessToken = (String) tokenResponse.get(ACCESS_TOKEN_KEY); + } + return Mono.zip(Mono.just(datasource), Mono.just(accessToken)); }); }) - .flatMap(datasource -> datasourceService.update(datasource.getId(), datasource)) + .flatMap(tuple -> { + Datasource datasource = tuple.getT1(); + String accessToken = tuple.getT2(); + OAuthResponseDTO response = new OAuthResponseDTO(); + response.setDatasource(datasource); + response.setToken(accessToken); + return datasourceService.update(datasource.getId(), datasource).thenReturn(response); + }) .onErrorMap(ConnectException.class, error -> new AppsmithException( AppsmithError.AUTHENTICATION_FAILURE,