diff --git a/spec/core/urlParsing.ts b/spec/core/urlParsing.ts index c1241eac4..ee47ab2bf 100644 --- a/spec/core/urlParsing.ts +++ b/spec/core/urlParsing.ts @@ -43,6 +43,10 @@ const testCases = { "/me/$filter=any(Actors, it/ID eq Director/ID)": "https://graph.microsoft.com/v1.0/me/$filter=any(Actors, it/ID eq Director/ID)", "/me?$whatif": "https://graph.microsoft.com/v1.0/me?$whatif", "/me/?$filter=any(Actors a, any(a/Movies m, a/ID eq m/Director/ID))": "https://graph.microsoft.com/v1.0/me/?$filter=any(Actors a, any(a/Movies m, a/ID eq m/Director/ID))", + + // National cloud deployment urls + "https://graph.microsoft.us/v1.0/me": "https://graph.microsoft.us/v1.0/me", + "https://dod-graph.microsoft.us/beta/me?$filter=a": "https://dod-graph.microsoft.us/beta/me?$filter=a", }; describe("urlParsing.ts", () => { diff --git a/spec/middleware/TelemetryHandler.ts b/spec/middleware/TelemetryHandler.ts index 81f5c7809..101a9ef5d 100644 --- a/spec/middleware/TelemetryHandler.ts +++ b/spec/middleware/TelemetryHandler.ts @@ -7,6 +7,7 @@ import { assert } from "chai"; +import { GRAPH_BASE_URL } from "../../src/Constants"; import { Context } from "../../src/IContext"; import { MiddlewareControl } from "../../src/middleware/MiddlewareControl"; import { FeatureUsageFlag, TelemetryHandlerOptions } from "../../src/middleware/options/TelemetryHandlerOptions"; @@ -26,79 +27,80 @@ describe("TelemetryHandler.ts", () => { statusText: "OK", }); it("Should not disturb client-request-id in the header", async () => { - try { - const uuid = "dummy_uuid"; - const context: Context = { - request: "url", - options: { - headers: { - "client-request-id": uuid, - }, + const uuid = "dummy_uuid"; + const context: Context = { + request: GRAPH_BASE_URL, + options: { + headers: { + "client-request-id": uuid, }, - }; - dummyHTTPHandler.setResponses([okayResponse]); - await telemetryHandler.execute(context); - assert.equal(context.options.headers["client-request-id"], uuid); - } catch (error) { - throw error; - } + }, + }; + dummyHTTPHandler.setResponses([okayResponse]); + await telemetryHandler.execute(context); + assert.equal(context.options.headers["client-request-id"], uuid); }); it("Should create client-request-id if one is not present in the request header", async () => { - try { - const context: Context = { - request: "url", - options: { - headers: { - method: "GET", - }, + const context: Context = { + request: "https://GRAPH.microsoft.com:443/", + options: { + headers: { + method: "GET", }, - }; - dummyHTTPHandler.setResponses([okayResponse]); - await telemetryHandler.execute(context); - assert.isDefined(context.options.headers["client-request-id"]); - } catch (error) { - throw error; - } + }, + }; + dummyHTTPHandler.setResponses([okayResponse]); + await telemetryHandler.execute(context); + assert.isDefined(context.options.headers["client-request-id"]); }); it("Should set sdk version header without feature flag usage if telemetry options is not present", async () => { - try { - const context: Context = { - request: "url", - options: { - headers: { - method: "GET", - }, + const context: Context = { + request: GRAPH_BASE_URL, + options: { + headers: { + method: "GET", }, - }; - dummyHTTPHandler.setResponses([okayResponse]); - await telemetryHandler.execute(context); - assert.equal(context.options.headers["SdkVersion"], `graph-js/${PACKAGE_VERSION}`); - } catch (error) { - throw error; - } + }, + }; + dummyHTTPHandler.setResponses([okayResponse]); + await telemetryHandler.execute(context); + assert.equal(context.options.headers["SdkVersion"], `graph-js/${PACKAGE_VERSION}`); }); it("Should set sdk version header with feature flag", async () => { - try { - const telemetryOptions = new TelemetryHandlerOptions(); - telemetryOptions["setFeatureUsage"](FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED); - const context: Context = { - request: "url", - options: { - headers: { - method: "GET", - }, + const telemetryOptions = new TelemetryHandlerOptions(); + telemetryOptions["setFeatureUsage"](FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED); + const context: Context = { + request: GRAPH_BASE_URL, + options: { + headers: { + method: "GET", }, - middlewareControl: new MiddlewareControl([telemetryOptions]), - }; - dummyHTTPHandler.setResponses([okayResponse]); - await telemetryHandler.execute(context); - assert.equal(context.options.headers["SdkVersion"], `graph-js/${PACKAGE_VERSION} (featureUsage=${FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED.toString(16)})`); - } catch (error) { - throw error; - } + }, + middlewareControl: new MiddlewareControl([telemetryOptions]), + }; + dummyHTTPHandler.setResponses([okayResponse]); + await telemetryHandler.execute(context); + assert.equal(context.options.headers["SdkVersion"], `graph-js/${PACKAGE_VERSION} (featureUsage=${FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED.toString(16)})`); + }); + + it("Should not set telemetry for non-graph url", async () => { + const context: Context = { + request: "test url", + options: { + headers: { + method: "GET", + }, + }, + middlewareControl: new MiddlewareControl(), + }; + dummyHTTPHandler.setResponses([okayResponse]); + await telemetryHandler.execute(context); + assert.equal(context.options.headers["client-request-id"], undefined); + assert.equal(context.options.headers["SdkVersion"], undefined); + assert.equal(context.options.headers["setFeatureUsage"], undefined); }); }); /* tslint:enable: no-string-literal */ diff --git a/src/Constants.ts b/src/Constants.ts index d22efcf74..06028da66 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -20,3 +20,9 @@ export const GRAPH_API_VERSION = "v1.0"; * A Default base url for a request */ export const GRAPH_BASE_URL = "https://graph.microsoft.com/"; + +/** + * To hold list of the service root endpoints for Microsoft Graph and Graph Explorer for each national cloud. + * Set(iterable:Object) is not supported in Internet Explorer. The consumer is recommended to use a suitable polyfill. + */ +export const GRAPH_URLS = new Set(["graph.microsoft.com", "graph.microsoft.us", "dod-graph.microsoft.us", "graph.microsoft.de", "microsoftgraph.chinacloudapi.cn"]); diff --git a/src/GraphRequestUtil.ts b/src/GraphRequestUtil.ts index a9ab02bb1..78e51f021 100644 --- a/src/GraphRequestUtil.ts +++ b/src/GraphRequestUtil.ts @@ -8,7 +8,7 @@ /** * @module GraphRequestUtil */ - +import { GRAPH_URLS } from "./Constants"; /** * To hold list of OData query params */ @@ -58,3 +58,34 @@ export const serializeContent = (content: any): any => { } return content; }; + +/** + * Checks if the url is one of the service root endpoints for Microsoft Graph and Graph Explorer. + * @param {string} url - The url to be verified + * @returns {boolean} - Returns true if the url is a Graph URL + */ +export const isGraphURL = (url: string): boolean => { + // Valid Graph URL pattern - https://graph.microsoft.com/{version}/{resource}?{query-parameters} + // Valid Graph URL example - https://graph.microsoft.com/v1.0/ + url = url.toLowerCase(); + + if (url.indexOf("https://") !== -1) { + url = url.replace("https://", ""); + + // Find where the host ends + const startofPortNoPos = url.indexOf(":"); + const endOfHostStrPos = url.indexOf("/"); + let hostName = ""; + if (endOfHostStrPos !== -1) { + if (startofPortNoPos !== -1 && startofPortNoPos < endOfHostStrPos) { + hostName = url.substring(0, startofPortNoPos); + return GRAPH_URLS.has(hostName); + } + // Parse out the host + hostName = url.substring(0, endOfHostStrPos); + return GRAPH_URLS.has(hostName); + } + } + + return false; +}; diff --git a/src/middleware/RedirectHandler.ts b/src/middleware/RedirectHandler.ts index 4bdcc9e39..1d451fc7f 100644 --- a/src/middleware/RedirectHandler.ts +++ b/src/middleware/RedirectHandler.ts @@ -202,7 +202,7 @@ export class RedirectHandler implements Middleware { } else { const redirectUrl: string = this.getLocationHeader(response); if (!this.isRelativeURL(redirectUrl) && this.shouldDropAuthorizationHeader(response.url, redirectUrl)) { - setRequestHeader(context.request, context.options, RedirectHandler.AUTHORIZATION_HEADER, undefined); + delete context.options.headers[RedirectHandler.AUTHORIZATION_HEADER]; } await this.updateRequestUrl(redirectUrl, context); } diff --git a/src/middleware/TelemetryHandler.ts b/src/middleware/TelemetryHandler.ts index e72d2c187..75b8b77ca 100644 --- a/src/middleware/TelemetryHandler.ts +++ b/src/middleware/TelemetryHandler.ts @@ -8,7 +8,7 @@ /** * @module TelemetryHandler */ - +import { isGraphURL } from "../GraphRequestUtil"; import { Context } from "../IContext"; import { PACKAGE_VERSION } from "../Version"; @@ -66,21 +66,31 @@ export class TelemetryHandler implements Middleware { */ public async execute(context: Context): Promise { try { - let clientRequestId: string = getRequestHeader(context.request, context.options, TelemetryHandler.CLIENT_REQUEST_ID_HEADER); - if (clientRequestId === null) { - clientRequestId = generateUUID(); - setRequestHeader(context.request, context.options, TelemetryHandler.CLIENT_REQUEST_ID_HEADER, clientRequestId); - } - let sdkVersionValue: string = `${TelemetryHandler.PRODUCT_NAME}/${PACKAGE_VERSION}`; - let options: TelemetryHandlerOptions; - if (context.middlewareControl instanceof MiddlewareControl) { - options = context.middlewareControl.getMiddlewareOptions(TelemetryHandlerOptions) as TelemetryHandlerOptions; - } - if (typeof options !== "undefined") { - const featureUsage: string = options.getFeatureUsage(); - sdkVersionValue += ` (${TelemetryHandler.FEATURE_USAGE_STRING}=${featureUsage})`; + if (typeof context.request === "string") { + if (isGraphURL(context.request)) { + // Add telemetry only if the request url is a Graph URL. + // Errors are reported as in issue #265 if headers are present when redirecting to a non Graph URL + let clientRequestId: string = getRequestHeader(context.request, context.options, TelemetryHandler.CLIENT_REQUEST_ID_HEADER); + if (clientRequestId === null) { + clientRequestId = generateUUID(); + setRequestHeader(context.request, context.options, TelemetryHandler.CLIENT_REQUEST_ID_HEADER, clientRequestId); + } + let sdkVersionValue: string = `${TelemetryHandler.PRODUCT_NAME}/${PACKAGE_VERSION}`; + let options: TelemetryHandlerOptions; + if (context.middlewareControl instanceof MiddlewareControl) { + options = context.middlewareControl.getMiddlewareOptions(TelemetryHandlerOptions) as TelemetryHandlerOptions; + } + if (options) { + const featureUsage: string = options.getFeatureUsage(); + sdkVersionValue += ` (${TelemetryHandler.FEATURE_USAGE_STRING}=${featureUsage})`; + } + appendRequestHeader(context.request, context.options, TelemetryHandler.SDK_VERSION_HEADER, sdkVersionValue); + } else { + // Remove telemetry headers if present during redirection. + delete context.options.headers[TelemetryHandler.CLIENT_REQUEST_ID_HEADER]; + delete context.options.headers[TelemetryHandler.SDK_VERSION_HEADER]; + } } - appendRequestHeader(context.request, context.options, TelemetryHandler.SDK_VERSION_HEADER, sdkVersionValue); return await this.nextMiddleware.execute(context); } catch (error) { throw error;