Skip to content

Commit f9ff755

Browse files
authored
Disable authentications on redirections (#207)
* Disable authentications on redirections * Add allowCrossOriginAuthentication boolean
1 parent e4b9bf2 commit f9ff755

File tree

4 files changed

+170
-6
lines changed

4 files changed

+170
-6
lines changed

lib/handlers/basiccreds.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,25 @@ import ifm = require('../Interfaces');
66
export class BasicCredentialHandler implements ifm.IRequestHandler {
77
username: string;
88
password: string;
9+
allowCrossOriginAuthentication: boolean;
10+
origin: string;
911

10-
constructor(username: string, password: string) {
12+
constructor(username: string, password: string, allowCrossOriginAuthentication?: boolean) {
1113
this.username = username;
1214
this.password = password;
15+
this.allowCrossOriginAuthentication = allowCrossOriginAuthentication;
1316
}
1417

1518
// currently implements pre-authorization
1619
// TODO: support preAuth = false where it hooks on 401
1720
prepareRequest(options:any): void {
18-
options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`;
21+
if (!this.origin) {
22+
this.origin = options.host;
23+
}
24+
// If this is a redirection, don't set the Authorization header
25+
if (this.origin === options.host || this.allowCrossOriginAuthentication) {
26+
options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`;
27+
}
1928
options.headers['X-TFS-FedAuthRedirect'] = 'Suppress';
2029
}
2130

lib/handlers/bearertoken.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@ import ifm = require('../Interfaces');
55

66
export class BearerCredentialHandler implements ifm.IRequestHandler {
77
token: string;
8+
allowCrossOriginAuthentication: boolean;
9+
origin: string;
810

9-
constructor(token: string) {
11+
constructor(token: string, allowCrossOriginAuthentication?: boolean) {
1012
this.token = token;
13+
this.allowCrossOriginAuthentication = allowCrossOriginAuthentication;
1114
}
1215

1316
// currently implements pre-authorization
1417
// TODO: support preAuth = false where it hooks on 401
1518
prepareRequest(options:any): void {
16-
options.headers['Authorization'] = `Bearer ${this.token}`;
19+
if (!this.origin) {
20+
this.origin = options.host;
21+
}
22+
// If this is a redirection, don't set the Authorization header
23+
if (this.origin === options.host || this.allowCrossOriginAuthentication) {
24+
options.headers['Authorization'] = `Bearer ${this.token}`;
25+
}
1726
options.headers['X-TFS-FedAuthRedirect'] = 'Suppress';
1827
}
1928

lib/handlers/personalaccesstoken.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@ import ifm = require('../Interfaces');
55

66
export class PersonalAccessTokenCredentialHandler implements ifm.IRequestHandler {
77
token: string;
8+
allowCrossOriginAuthentication: boolean;
9+
origin: string;
810

9-
constructor(token: string) {
11+
constructor(token: string, allowCrossOriginAuthentication?: boolean) {
1012
this.token = token;
13+
this.allowCrossOriginAuthentication = allowCrossOriginAuthentication;
1114
}
1215

1316
// currently implements pre-authorization
1417
// TODO: support preAuth = false where it hooks on 401
1518
prepareRequest(options:any): void {
16-
options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`;
19+
if (!this.origin) {
20+
this.origin = options.host;
21+
}
22+
// If this is a redirection, don't set the Authorization header
23+
if (this.origin === options.host || this.allowCrossOriginAuthentication) {
24+
options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`;
25+
}
1726
options.headers['X-TFS-FedAuthRedirect'] = 'Suppress';
1827
}
1928

test/units/handlers.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,50 @@ describe('Authentication Handlers Tests', function () {
8080
assert(! asJson.success, "success = false; Authentication should fail");
8181
});
8282

83+
it('[Basic Auth] - does redirection request with basic auth', async() => {
84+
const url: string = 'http://microsoft.com';
85+
const redirectionUrl: string = 'http://jfrog.com';
86+
const user: string = _authHandlersOptions.basicAuth.username;
87+
const pass: string = _authHandlersOptions.basicAuth.password;
88+
89+
//Set nock for redirection with credentials
90+
const redirectAuthScope = nock(url)
91+
.get('/')
92+
.basicAuth({ user, pass })
93+
.reply(httpm.HttpCodes.MovedPermanently, undefined, {
94+
location: redirectionUrl
95+
});
96+
97+
//Set nock for request without expecting/matching Authorization header(s)
98+
nock(redirectionUrl)
99+
.matchHeader('authorization', (val: string | undefined) => !val )
100+
.get('/')
101+
.reply(httpm.HttpCodes.OK, {
102+
success: true,
103+
source: "nock"
104+
});
105+
106+
//Set nock for request with expecting/matching Authorization header(s)
107+
nock(redirectionUrl)
108+
.matchHeader('authorization', (val: string | undefined) => val )
109+
.get('/')
110+
.reply(httpm.HttpCodes.BadRequest, {
111+
success: false,
112+
source: "nock"
113+
});
114+
115+
const basicAuthHandler: hm.BasicCredentialHandler = new hm.BasicCredentialHandler(user, pass);
116+
let httpClient: httpm.HttpClient = new httpm.HttpClient('typed-rest-client-tests', [basicAuthHandler]);
117+
let httpResponse: httpm.HttpClientResponse = await httpClient.get(url);
118+
let body: string = await httpResponse.readBody();
119+
let asJson: any = JSON.parse(body);
120+
121+
assert(redirectAuthScope.isDone());
122+
assert(httpResponse.message.statusCode == httpm.HttpCodes.OK, "status code should be 200 - OK");
123+
assert(asJson.source === "nock", "http get request should be intercepted by nock");
124+
assert(asJson.success, "Authentication should not occur in redirection to other hosts");
125+
});
126+
83127
it('[Basic Auth - Presigned] doesnt use auth when presigned', async() => {
84128
const url: string = 'http://microsoft.com';
85129
const user: string = _authHandlersOptions.basicAuth.username;
@@ -165,6 +209,53 @@ describe('Authentication Handlers Tests', function () {
165209
assert(! asJson.success, "success = false; Authentication should fail");
166210
});
167211

212+
it('[Personal Access Token] - does redirection request with PAT token auth', async() => {
213+
const url: string = 'http://microsoft.com';
214+
const redirectionUrl: string = 'http://jfrog.com';
215+
const secret: string = _authHandlersOptions.personalAccessToken.secret;
216+
const personalAccessToken: string = Buffer.from(`PAT:${secret}`).toString('base64');
217+
const expectedAuthHeader: string = `Basic ${personalAccessToken}`;
218+
const patAuthHandler: hm.PersonalAccessTokenCredentialHandler =
219+
new hm.PersonalAccessTokenCredentialHandler(secret);
220+
221+
//Nock request for redirection with expecting/matching Authorization header(s)
222+
const redirectAuthScope = nock(url)
223+
.matchHeader('Authorization', expectedAuthHeader)
224+
.matchHeader('X-TFS-FedAuthRedirect', 'Suppress')
225+
.get('/')
226+
.reply(httpm.HttpCodes.MovedPermanently, undefined, {
227+
location: redirectionUrl
228+
});
229+
230+
//Set nock for request without expecting/matching Authorization header(s)
231+
nock(redirectionUrl)
232+
.matchHeader('authorization', (val: string | undefined) => !val )
233+
.get('/')
234+
.reply(httpm.HttpCodes.OK, {
235+
success: true,
236+
source: "nock"
237+
});
238+
239+
//Set nock for request with expecting/matching Authorization header(s)
240+
nock(redirectionUrl)
241+
.matchHeader('authorization', (val: string | undefined) => val )
242+
.get('/')
243+
.reply(httpm.HttpCodes.BadRequest, {
244+
success: false,
245+
source: "nock"
246+
});
247+
248+
let httpClient: httpm.HttpClient = new httpm.HttpClient('typed-rest-client-tests', [patAuthHandler]);
249+
let httpResponse: httpm.HttpClientResponse = await httpClient.get(url);
250+
let body: string = await httpResponse.readBody();
251+
let asJson: any = JSON.parse(body);
252+
253+
assert(redirectAuthScope.isDone());
254+
assert(httpResponse.message.statusCode == httpm.HttpCodes.OK, "status code should be 200 - OK");
255+
assert(asJson.source === "nock", "http get request should be intercepted by nock");
256+
assert(asJson.success, "Authentication should not occur in redirection to other hosts");
257+
});
258+
168259
it('[Bearer Token] - does basic http get request with bearer token authentication', async() => {
169260
const url: string = 'http://microsoft.com';
170261
const bearerToken: string = _authHandlersOptions.bearer.token;
@@ -216,6 +307,52 @@ describe('Authentication Handlers Tests', function () {
216307
assert(httpResponse.message.statusCode === httpm.HttpCodes.Unauthorized, "statusCode returned should be 401 - Unauthorized"); //statusCode is 401 - Unauthorized
217308
});
218309

310+
it('[Bearer Token] - does redirection request with bearer token authentication', async() => {
311+
const url: string = 'http://microsoft.com';
312+
const redirectionUrl: string = 'http://jfrog.com';
313+
const bearerToken: string = _authHandlersOptions.bearer.token;
314+
315+
const expectedAuthHeader: string = `Bearer ${bearerToken}`;
316+
const bearerTokenAuthHandler: hm.BearerCredentialHandler = new hm.BearerCredentialHandler(bearerToken);
317+
318+
//Nock request for redirection with expecting/matching Authorization header(s)
319+
const redirectAuthScope = nock(url)
320+
.matchHeader('Authorization', expectedAuthHeader)
321+
.matchHeader('X-TFS-FedAuthRedirect', 'Suppress')
322+
.get('/')
323+
.reply(httpm.HttpCodes.MovedPermanently, undefined, {
324+
location: redirectionUrl
325+
});
326+
327+
//Set nock for request without expecting/matching Authorization header(s)
328+
nock(redirectionUrl)
329+
.matchHeader('authorization', (val: string | undefined) => !val )
330+
.get('/')
331+
.reply(httpm.HttpCodes.OK, {
332+
success: true,
333+
source: "nock"
334+
});
335+
336+
//Set nock for request with expecting/matching Authorization header(s)
337+
nock(redirectionUrl)
338+
.matchHeader('authorization', (val: string | undefined) => val )
339+
.get('/')
340+
.reply(httpm.HttpCodes.BadRequest, {
341+
success: false,
342+
source: "nock"
343+
});
344+
345+
let httpClient: httpm.HttpClient = new httpm.HttpClient('typed-rest-client-tests', [bearerTokenAuthHandler]);
346+
let httpResponse: httpm.HttpClientResponse = await httpClient.get(url);
347+
let body: string = await httpResponse.readBody();
348+
let asJson: any = JSON.parse(body);
349+
350+
assert(redirectAuthScope.isDone());
351+
assert(httpResponse.message.statusCode == httpm.HttpCodes.OK, "status code should be 200 - OK");
352+
assert(asJson.source === "nock", "http get request should be intercepted by nock");
353+
assert(asJson.success, "Authentication should not occur in redirection to other hosts");
354+
});
355+
219356
it('[NTLM] - does basic http get request with NTLM Authentication', async() => {
220357
/**
221358
* Following NTLM Authentication Example on:

0 commit comments

Comments
 (0)