Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions spec/core/urlParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const testCases = {
// Passing invalid parameters
"/me?&test&123": "https://graph.microsoft.com/v1.0/me?&test&123",
"/me?$select($select=name)": "https://graph.microsoft.com/v1.0/me?$select($select=name)",

// 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", () => {
Expand Down
30 changes: 26 additions & 4 deletions spec/middleware/TelemetryHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -29,7 +30,7 @@ describe("TelemetryHandler.ts", () => {
try {
const uuid = "dummy_uuid";
const context: Context = {
request: "url",
request: GRAPH_BASE_URL,
options: {
headers: {
"client-request-id": uuid,
Expand All @@ -47,7 +48,7 @@ describe("TelemetryHandler.ts", () => {
it("Should create client-request-id if one is not present in the request header", async () => {
try {
const context: Context = {
request: "url",
request: GRAPH_BASE_URL,
options: {
headers: {
method: "GET",
Expand All @@ -65,7 +66,7 @@ describe("TelemetryHandler.ts", () => {
it("Should set sdk version header without feature flag usage if telemetry options is not present", async () => {
try {
const context: Context = {
request: "url",
request: GRAPH_BASE_URL,
options: {
headers: {
method: "GET",
Expand All @@ -85,7 +86,7 @@ describe("TelemetryHandler.ts", () => {
const telemetryOptions = new TelemetryHandlerOptions();
telemetryOptions["setFeatureUsage"](FeatureUsageFlag.AUTHENTICATION_HANDLER_ENABLED);
const context: Context = {
request: "url",
request: GRAPH_BASE_URL,
options: {
headers: {
method: "GET",
Expand All @@ -100,6 +101,27 @@ describe("TelemetryHandler.ts", () => {
throw error;
}
});

it("Should not set telemetry for non-graph url", async () => {
try {
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);
} catch (error) {
throw error;
}
});
});
/* tslint:enable: no-string-literal */
});
13 changes: 13 additions & 0 deletions src/GraphRequestUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
*/
export const oDataQueryNames = ["$select", "$expand", "$orderby", "$filter", "$top", "$skip", "$skipToken", "$count"];

/**
* To hold list of the service root endpoints for Microsoft Graph and Graph Explorer for each national cloud.
*/
export const graphURLs = new Set<string>();

// using an IIFE to populate the set object with the graph host names as Set(iterable:Object) is not supported in Internet Explorer
(() => {
const urls = ["graph.microsoft.com", "graph.microsoft.us", "dod-graph.microsoft.us", "graph.microsoft.de", "microsoftgraph.chinacloudapi.cn"];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could initialize the set directly from the array, see the edit in the response.
https://stackoverflow.com/a/30902786/3808675

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used an IIFE with set.add() instead as IE does not support Set(array). IE creates an empty set when I passed in new Set(object/array).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, and I'm guessing this is an edge case the transpilation is not catching?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additionally this is something that people (or we) can polyfill using core-js, see the first example. https://www.npmjs.com/package/core-js

urls.forEach((url) => {
graphURLs.add(url);
});
})();

/**
* To construct the URL by appending the segments with "/"
* @param {string[]} urlSegments - The array of strings
Expand Down
60 changes: 46 additions & 14 deletions src/middleware/TelemetryHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* @module TelemetryHandler
*/

import { graphURLs } from "../GraphRequestUtil";
import { Context } from "../IContext";
import { PACKAGE_VERSION } from "../Version";

Expand Down Expand Up @@ -64,23 +65,54 @@ export class TelemetryHandler implements Middleware {
* @param {Context} context - The context object of the request
* @returns A Promise that resolves to nothing
*/

/**
* @private
* Checks if the request url points to the Graph service endpoints
* @param {string} url - The request url string
* @returns true if request url is a Graph url
*/
private isGraphURL(url: string): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. construct a Set() object for graphURLs where the values are only the host names.
  2. construct a typed URL object for url.
  3. Have this function return the result of graphURLs.has(urlObject.hostname)

Benefits:

  1. Efficiency: Lookup is amortized O(1).
  2. Correctness: We probably want to capture telemetry for urls like: https://graph.microsoft.com:443 and current implementation will not accept URLs with port numbers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also consider inlining the whole function as it will be a simple call to graphURLs.has(new URL(context.request).hostname)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for suggesting an optimal approach!

I have used the approach you suggested with some modifications. Implemented a set object with the host names. I haven't used the URL object as some browsers do not support of the URL object and I am extracting the host name as it is done in GraphRequest.ts during the url parsing process.

// Valid Graph URL pattern - https://graph.microsoft.com/{version}/{resource}?{query-parameters}
// Valid Graph URL example - https://graph.microsoft.com/v1.0/me

if (url.indexOf("https://") !== -1) {
url = url.replace("https://", "");

// Find where the host ends
const endOfHostStrPos = url.indexOf("/");
if (endOfHostStrPos !== -1) {
// Parse out the host
const hostName = url.substring(0, endOfHostStrPos);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't cover the scenario of https://graph.microsoft.com:443/v1.0/me

return graphURLs.has(hostName);
}
return false;
}

return false;
}

public async execute(context: Context): Promise<void> {
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" && this.isGraphURL(context.request)) {
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})`;
}

appendRequestHeader(context.request, context.options, TelemetryHandler.SDK_VERSION_HEADER, sdkVersionValue);
}
appendRequestHeader(context.request, context.options, TelemetryHandler.SDK_VERSION_HEADER, sdkVersionValue);

return await this.nextMiddleware.execute(context);
} catch (error) {
throw error;
Expand Down