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,