Skip to content

Commit be51dad

Browse files
Merge pull request #2007 from microsoftgraph/fix/redirect-handler-strip-cookie-header
Fix: strip Cookie header on cross-origin redirects in RedirectHandler
2 parents cb4cd88 + 5860392 commit be51dad

3 files changed

Lines changed: 108 additions & 4 deletions

File tree

‎src/middleware/MiddlewareUtil.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,40 @@ export const appendRequestHeader = (request: RequestInfo, options: FetchOptions
130130
}
131131
};
132132

133+
/**
134+
* @constant
135+
* To remove a header from the given request
136+
* @param {RequestInfo} request - The request object or the url string
137+
* @param {FetchOptions|undefined} options - The request options object
138+
* @param {string} key - The header key string
139+
* @returns Nothing
140+
*/
141+
export const removeRequestHeader = (request: RequestInfo, options: FetchOptions | undefined, key: string): void => {
142+
if (typeof Request !== "undefined" && request instanceof Request) {
143+
(request as Request).headers.delete(key);
144+
}
145+
if (typeof options !== "undefined" && options.headers !== undefined) {
146+
if (typeof Headers !== "undefined" && options.headers instanceof Headers) {
147+
(options.headers as Headers).delete(key);
148+
} else if (options.headers instanceof Array) {
149+
const headers = options.headers as string[][];
150+
const lowerKey = key.toLowerCase();
151+
for (let i = headers.length - 1; i >= 0; i--) {
152+
if (headers[i][0].toLowerCase() === lowerKey) {
153+
headers.splice(i, 1);
154+
}
155+
}
156+
} else {
157+
const headers = options.headers as Record<string, string>;
158+
for (const headerKey of Object.keys(headers)) {
159+
if (headerKey.toLowerCase() === key.toLowerCase()) {
160+
delete headers[headerKey];
161+
}
162+
}
163+
}
164+
}
165+
};
166+
133167
/**
134168
* @constant
135169
* To clone the request with the new url

‎src/middleware/RedirectHandler.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Context } from "../IContext";
1313
import { RequestMethod } from "../RequestMethod";
1414
import { Middleware } from "./IMiddleware";
1515
import { MiddlewareControl } from "./MiddlewareControl";
16-
import { cloneRequestWithNewUrl } from "./MiddlewareUtil";
16+
import { cloneRequestWithNewUrl, removeRequestHeader } from "./MiddlewareUtil";
1717
import { RedirectHandlerOptions } from "./options/RedirectHandlerOptions";
1818
import { FeatureUsageFlag, TelemetryHandlerOptions } from "./options/TelemetryHandlerOptions";
1919

@@ -58,6 +58,13 @@ export class RedirectHandler implements Middleware {
5858
*/
5959
private static AUTHORIZATION_HEADER = "Authorization";
6060

61+
/**
62+
* @private
63+
* @static
64+
* A member representing the cookie header name
65+
*/
66+
private static COOKIE_HEADER = "Cookie";
67+
6168
/**
6269
* @private
6370
* @static
@@ -131,10 +138,10 @@ export class RedirectHandler implements Middleware {
131138

132139
/**
133140
* @private
134-
* To check whether the authorization header in the request should be dropped for consequent redirected requests
141+
* To check whether the sensitive headers (Authorization, Cookie) in the request should be dropped for consequent redirected requests
135142
* @param {string} requestUrl - The request url value
136143
* @param {string} redirectUrl - The redirect url value
137-
* @returns A boolean representing whether the authorization header in the request should be dropped for consequent redirected requests
144+
* @returns A boolean representing whether the sensitive headers in the request should be dropped for consequent redirected requests
138145
*/
139146
private shouldDropAuthorizationHeader(requestUrl: string, redirectUrl: string): boolean {
140147
const schemeHostRegex = /^[A-Za-z].+?:\/\/.+?(?=\/|$)/;
@@ -200,7 +207,8 @@ export class RedirectHandler implements Middleware {
200207
} else {
201208
const redirectUrl: string = this.getLocationHeader(response);
202209
if (!this.isRelativeURL(redirectUrl) && this.shouldDropAuthorizationHeader(response.url, redirectUrl)) {
203-
delete context.options.headers[RedirectHandler.AUTHORIZATION_HEADER];
210+
removeRequestHeader(context.request, context.options, RedirectHandler.AUTHORIZATION_HEADER);
211+
removeRequestHeader(context.request, context.options, RedirectHandler.COOKIE_HEADER);
204212
}
205213
await this.updateRequestUrl(redirectUrl, context);
206214
}

‎test/common/middleware/RedirectHandler.ts‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import { assert } from "chai";
9+
import * as sinon from "sinon";
910

1011
import { Context } from "../../../src/IContext";
1112
import { MiddlewareControl } from "../../../src/middleware/MiddlewareControl";
@@ -293,6 +294,67 @@ describe("RedirectHandler.ts", () => {
293294
assert.equal(cxt.response.status, 200);
294295
});
295296

297+
it("Should not drop Cookie header for same authority redirect url", async () => {
298+
const options = new RedirectHandlerOptions();
299+
const cxt: Context = {
300+
request: "https://graph.microsoft.com/v1.0/me",
301+
options: {
302+
method: "POST",
303+
body: "dummy body",
304+
headers: {
305+
[RedirectHandler["COOKIE_HEADER"]]: "session=abc",
306+
},
307+
},
308+
};
309+
dummyHTTPHandler.setResponses([
310+
new Response("", {
311+
status: 301,
312+
headers: {
313+
[RedirectHandler["LOCATION_HEADER"]]: "https://graph.microsoft.com/v2.0/me",
314+
},
315+
}),
316+
new Response("ok", { status: 200 }),
317+
]);
318+
await handler["executeWithRedirect"](cxt, 0, options);
319+
assert.isDefined(cxt.options.headers[RedirectHandler["COOKIE_HEADER"]]);
320+
assert.equal(cxt.response.status, 200);
321+
});
322+
323+
it("Should drop both Authorization and Cookie headers for cross origin redirects", async () => {
324+
const options = new RedirectHandlerOptions();
325+
const cxt: Context = {
326+
request: "https://graph.microsoft.com/v1.0/me",
327+
options: {
328+
method: "POST",
329+
body: "dummy body",
330+
headers: {
331+
[RedirectHandler["AUTHORIZATION_HEADER"]]: "******",
332+
[RedirectHandler["COOKIE_HEADER"]]: "session=abc",
333+
"X-Custom-Header": "custom-value",
334+
},
335+
},
336+
};
337+
dummyHTTPHandler.setResponses([
338+
new Response("", {
339+
status: 301,
340+
headers: {
341+
[RedirectHandler["LOCATION_HEADER"]]: "https://graphredirection.microsoft.com/v1.0/me",
342+
},
343+
}),
344+
new Response("ok", { status: 200 }),
345+
]);
346+
const stub = sinon.stub(handler as any, "shouldDropAuthorizationHeader").returns(true);
347+
try {
348+
await handler["executeWithRedirect"](cxt, 0, options);
349+
} finally {
350+
stub.restore();
351+
}
352+
assert.isUndefined(cxt.options.headers[RedirectHandler["AUTHORIZATION_HEADER"]]);
353+
assert.isUndefined(cxt.options.headers[RedirectHandler["COOKIE_HEADER"]]);
354+
assert.equal(cxt.options.headers["X-Custom-Header"], "custom-value");
355+
assert.equal(cxt.response.status, 200);
356+
});
357+
296358
it("Should return success response after successful redirect", async () => {
297359
const options = new RedirectHandlerOptions();
298360
const cxt: Context = {

0 commit comments

Comments
 (0)