From ddf5e6f97fdb8a55b433c822e70b00dee067084e Mon Sep 17 00:00:00 2001 From: Millicent Achieng Date: Fri, 8 Apr 2022 04:21:48 +0300 Subject: [PATCH] Handle CORS error thrown when GE attempts to download file from a redirect --- .../actions/query-action-creator-util.ts | 24 ++++ .../services/actions/query-action-creators.ts | 110 +++++++----------- src/app/services/graph-constants.ts | 22 +--- .../views/app-sections/ResponseMessages.tsx | 11 +- 4 files changed, 71 insertions(+), 96 deletions(-) diff --git a/src/app/services/actions/query-action-creator-util.ts b/src/app/services/actions/query-action-creator-util.ts index 90d129a39..2497b1cce 100644 --- a/src/app/services/actions/query-action-creator-util.ts +++ b/src/app/services/actions/query-action-creator-util.ts @@ -245,3 +245,27 @@ export function parseResponse( } return response; } + +/** + * Check if query attempts to download from OneDrive's /content API or reporting API + * Examples: + * /drive/items/{item-id}/content + * /shares/{shareIdOrEncodedSharingUrl}/driveItem/content + * /me/drive/items/{item-id}/thumbnails/{thumb-id}/{size}/content + * /sites/{site-id}/drive/items/{item-id}/versions/{version-id}/content + * /reports/getOffice365ActivationCounts?$format=text/csv + * /reports/getEmailActivityUserCounts(period='D7')?$format=text/csv + * @param query + * @returns true if query calls the OneDrive or reporting API, otherwise false + */ +export function queryResultsInCorsError(sampleUrl: string): boolean { + sampleUrl = sampleUrl.toLowerCase(); + if ( + (['/drive/', '/drives/', '/driveItem/'].some((x) => + sampleUrl.includes(x)) && sampleUrl.endsWith('/content')) || + (sampleUrl.includes('/reports/') && sampleUrl.includes('$format=text/csv')) + ) { + return true; + } + return false; +} diff --git a/src/app/services/actions/query-action-creators.ts b/src/app/services/actions/query-action-creators.ts index e5324cd96..43f74ffab 100644 --- a/src/app/services/actions/query-action-creators.ts +++ b/src/app/services/actions/query-action-creators.ts @@ -5,11 +5,8 @@ import { IHistoryItem } from '../../../types/history'; import { IQuery } from '../../../types/query-runner'; import { IStatus } from '../../../types/status'; import { ClientError } from '../../utils/ClientError'; -import { sanitizeQueryUrl } from '../../utils/query-url-sanitization'; -import { parseSampleUrl } from '../../utils/sample-url-generation'; import { setStatusMessage } from '../../utils/status-message'; import { writeHistoryData } from '../../views/sidebar/history/history-utils'; -import { CORS_ERROR_QUERIES } from '../graph-constants'; import { anonymousRequest, authenticatedRequest, @@ -17,7 +14,8 @@ import { isFileResponse, isImageResponse, parseResponse, - queryResponse + queryResponse, + queryResultsInCorsError } from './query-action-creator-util'; import { setQueryResponseStatus } from './query-status-action-creator'; import { addHistoryItem } from './request-history-action-creators'; @@ -31,26 +29,18 @@ export function runQuery(query: IQuery): Function { if (tokenPresent) { return authenticatedRequest(dispatch, query) .then(async (response: Response) => { - await processResponse( - response, - respHeaders, - dispatch, - createdAt, - ); + await processResponse(response, respHeaders, dispatch, createdAt); + }) + .catch(async (error: any) => { + return handleError(dispatch, error); }); } return anonymousRequest(dispatch, query, getState) - .then( - async (response: Response) => { - await processResponse( - response, - respHeaders, - dispatch, - createdAt, - ); - } - ).catch(async (error: any) => { + .then(async (response: Response) => { + await processResponse(response, respHeaders, dispatch, createdAt); + }) + .catch(async (error: any) => { return handleError(dispatch, error); }); }; @@ -59,7 +49,7 @@ export function runQuery(query: IQuery): Function { response: Response, respHeaders: any, dispatch: Function, - createdAt: any, + createdAt: any ) { let result = await parseResponse(response, respHeaders); const duration = new Date().getTime() - new Date(createdAt).getTime(); @@ -93,8 +83,6 @@ export function runQuery(query: IQuery): Function { status.ok = true; status.messageType = MessageBarType.success; - dispatch(setQueryResponseStatus(status)); - if (isFileResponse(respHeaders)) { const contentDownloadUrl = await generateResponseDownloadUrl( response, @@ -106,59 +94,49 @@ export function runQuery(query: IQuery): Function { }; } } - - return dispatch( - queryResponse({ - body: result, - headers: respHeaders - }) - ); - } else { - const { requestUrl } = parseSampleUrl(sanitizeQueryUrl(query.sampleUrl)); - // check if this is one of the queries that result in a CORS error - if (response.status === 0 && CORS_ERROR_QUERIES.has(requestUrl)) { - result = { - throwsCorsError: true, - workload: CORS_ERROR_QUERIES.get(requestUrl) - }; - } - - dispatch( - queryResponse({ - body: result, - headers: respHeaders - }) - ); - return dispatch(setQueryResponseStatus(status)); } - } -} -function handleError(dispatch: Function, error: any) { - dispatch( - queryResponse({ - body: error, - headers: null - }) - ); - if (error instanceof ClientError) { + dispatch(setQueryResponseStatus(status)); + return dispatch( - setQueryResponseStatus({ - messageType: MessageBarType.error, - ok: false, - status: 0, - statusText: `${error.name}: ${error.message}` + queryResponse({ + body: result, + headers: respHeaders }) ); } - return dispatch( - setQueryResponseStatus({ + + function handleError(dispatch: Function, error: any) { + let body = error; + const status: IStatus = { messageType: MessageBarType.error, ok: false, status: 400, statusText: 'Bad Request' - }) - ); + }; + + if (error instanceof ClientError) { + status.status = 0; + status.statusText = `${error.name}: ${error.message}`; + } + + if (queryResultsInCorsError(query.sampleUrl)) { + status.status = 0; + status.statusText = 'CORS error'; + body = { + throwsCorsError: true + }; + } + + dispatch( + queryResponse({ + body, + headers: null + }) + ); + + return dispatch(setQueryResponseStatus(status)); + } } async function createHistory( diff --git a/src/app/services/graph-constants.ts b/src/app/services/graph-constants.ts index 0d261eb3d..0079db865 100644 --- a/src/app/services/graph-constants.ts +++ b/src/app/services/graph-constants.ts @@ -1,5 +1,5 @@ export const GRAPH_URL = 'https://graph.microsoft.com'; -export const GRAPH_API_VERSIONS = ['v1.0','beta']; +export const GRAPH_API_VERSIONS = ['v1.0', 'beta']; export const USER_INFO_URL = `${GRAPH_URL}/v1.0/me`; export const BETA_USER_INFO_URL = `${GRAPH_URL}/beta/me/profile`; export const USER_PICTURE_URL = `${GRAPH_URL}/beta/me/photo/$value`; @@ -21,28 +21,8 @@ export enum PERMS_SCOPE { APPLICATION = 'Application', PERSONAL = 'DelegatedPersonal' } -export enum WORKLOAD { - ONEDRIVE = 'OneDrive', - O365REPORTING = 'O365Reporting' -} export const ADAPTIVE_CARD_URL = 'https://templates.adaptivecards.io/graph.microsoft.com'; export const GRAPH_TOOOLKIT_EXAMPLE_URL = 'https://mgt.dev/?path=/story'; export const MOZILLA_CORS_DOCUMENTATION_LINK = 'https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS'; -export const ONE_DRIVE_CONTENT_DOWNLOAD_DOCUMENTATION_LINK = - 'https://docs.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0&tabs=http'; -export const CORS_ERROR_QUERIES = new Map([ - ['groups/{groups-id}/drive/items/{items-id}/content', WORKLOAD.ONEDRIVE], - ['sites/{sites-id}/drive/items/{items-id}/content', WORKLOAD.ONEDRIVE], - ['users/{users-id}/drive/items/{items-id}/content', WORKLOAD.ONEDRIVE], - ['drives/{drives-id}/items/{items-id}/content', WORKLOAD.ONEDRIVE], - ['shares/{shares-id}/driveItem/content', WORKLOAD.ONEDRIVE], - ['me/drive/items/{items-id}/content', WORKLOAD.ONEDRIVE], - ['me/drive/root:/content', WORKLOAD.ONEDRIVE], - ['reports/getYammerGroupsActivityDetail(period=)', WORKLOAD.O365REPORTING], - ['reports/getTeamsDeviceUsageUserCounts(period=)', WORKLOAD.O365REPORTING], - ['reports/getSharePointSiteUsageDetail(period=)',WORKLOAD.O365REPORTING], - ['reports/getOneDriveUsageFileCounts(period=)', WORKLOAD.O365REPORTING], - ['reports/getEmailActivityUserCounts(period=)', WORKLOAD.O365REPORTING] -]); diff --git a/src/app/views/app-sections/ResponseMessages.tsx b/src/app/views/app-sections/ResponseMessages.tsx index 1c13c83a0..d1910b745 100644 --- a/src/app/views/app-sections/ResponseMessages.tsx +++ b/src/app/views/app-sections/ResponseMessages.tsx @@ -6,11 +6,7 @@ import { IGraphResponse } from '../../../types/query-response'; import { IQuery } from '../../../types/query-runner'; import { runQuery } from '../../services/actions/query-action-creators'; import { setSampleQuery } from '../../services/actions/query-input-action-creators'; -import { - MOZILLA_CORS_DOCUMENTATION_LINK, - ONE_DRIVE_CONTENT_DOWNLOAD_DOCUMENTATION_LINK, - WORKLOAD -} from '../../services/graph-constants'; +import { MOZILLA_CORS_DOCUMENTATION_LINK } from '../../services/graph-constants'; interface ODataLink { link: string; @@ -73,14 +69,11 @@ export function responseMessages(graphResponse: IGraphResponse, sampleQuery: IQu // Show CORS compliance message if (body?.throwsCorsError) { - const documentationLink = body?.workload === WORKLOAD.ONEDRIVE - ? ONE_DRIVE_CONTENT_DOWNLOAD_DOCUMENTATION_LINK - : MOZILLA_CORS_DOCUMENTATION_LINK; return (
- + .