Skip to content

Commit

Permalink
Disable authentications on redirections (#207)
Browse files Browse the repository at this point in the history
* Disable authentications on redirections

* Add allowCrossOriginAuthentication boolean
  • Loading branch information
yahavi committed Apr 1, 2020
1 parent e4b9bf2 commit f9ff755
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 6 deletions.
13 changes: 11 additions & 2 deletions lib/handlers/basiccreds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ import ifm = require('../Interfaces');
export class BasicCredentialHandler implements ifm.IRequestHandler {
username: string;
password: string;
allowCrossOriginAuthentication: boolean;
origin: string;

constructor(username: string, password: string) {
constructor(username: string, password: string, allowCrossOriginAuthentication?: boolean) {
this.username = username;
this.password = password;
this.allowCrossOriginAuthentication = allowCrossOriginAuthentication;
}

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

Expand Down
13 changes: 11 additions & 2 deletions lib/handlers/bearertoken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@ import ifm = require('../Interfaces');

export class BearerCredentialHandler implements ifm.IRequestHandler {
token: string;
allowCrossOriginAuthentication: boolean;
origin: string;

constructor(token: string) {
constructor(token: string, allowCrossOriginAuthentication?: boolean) {
this.token = token;
this.allowCrossOriginAuthentication = allowCrossOriginAuthentication;
}

// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options:any): void {
options.headers['Authorization'] = `Bearer ${this.token}`;
if (!this.origin) {
this.origin = options.host;
}
// If this is a redirection, don't set the Authorization header
if (this.origin === options.host || this.allowCrossOriginAuthentication) {
options.headers['Authorization'] = `Bearer ${this.token}`;
}
options.headers['X-TFS-FedAuthRedirect'] = 'Suppress';
}

Expand Down
13 changes: 11 additions & 2 deletions lib/handlers/personalaccesstoken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@ import ifm = require('../Interfaces');

export class PersonalAccessTokenCredentialHandler implements ifm.IRequestHandler {
token: string;
allowCrossOriginAuthentication: boolean;
origin: string;

constructor(token: string) {
constructor(token: string, allowCrossOriginAuthentication?: boolean) {
this.token = token;
this.allowCrossOriginAuthentication = allowCrossOriginAuthentication;
}

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

Expand Down
137 changes: 137 additions & 0 deletions test/units/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,50 @@ describe('Authentication Handlers Tests', function () {
assert(! asJson.success, "success = false; Authentication should fail");
});

it('[Basic Auth] - does redirection request with basic auth', async() => {
const url: string = 'http://microsoft.com';
const redirectionUrl: string = 'http://jfrog.com';
const user: string = _authHandlersOptions.basicAuth.username;
const pass: string = _authHandlersOptions.basicAuth.password;

//Set nock for redirection with credentials
const redirectAuthScope = nock(url)
.get('/')
.basicAuth({ user, pass })
.reply(httpm.HttpCodes.MovedPermanently, undefined, {
location: redirectionUrl
});

//Set nock for request without expecting/matching Authorization header(s)
nock(redirectionUrl)
.matchHeader('authorization', (val: string | undefined) => !val )
.get('/')
.reply(httpm.HttpCodes.OK, {
success: true,
source: "nock"
});

//Set nock for request with expecting/matching Authorization header(s)
nock(redirectionUrl)
.matchHeader('authorization', (val: string | undefined) => val )
.get('/')
.reply(httpm.HttpCodes.BadRequest, {
success: false,
source: "nock"
});

const basicAuthHandler: hm.BasicCredentialHandler = new hm.BasicCredentialHandler(user, pass);
let httpClient: httpm.HttpClient = new httpm.HttpClient('typed-rest-client-tests', [basicAuthHandler]);
let httpResponse: httpm.HttpClientResponse = await httpClient.get(url);
let body: string = await httpResponse.readBody();
let asJson: any = JSON.parse(body);

assert(redirectAuthScope.isDone());
assert(httpResponse.message.statusCode == httpm.HttpCodes.OK, "status code should be 200 - OK");
assert(asJson.source === "nock", "http get request should be intercepted by nock");
assert(asJson.success, "Authentication should not occur in redirection to other hosts");
});

it('[Basic Auth - Presigned] doesnt use auth when presigned', async() => {
const url: string = 'http://microsoft.com';
const user: string = _authHandlersOptions.basicAuth.username;
Expand Down Expand Up @@ -165,6 +209,53 @@ describe('Authentication Handlers Tests', function () {
assert(! asJson.success, "success = false; Authentication should fail");
});

it('[Personal Access Token] - does redirection request with PAT token auth', async() => {
const url: string = 'http://microsoft.com';
const redirectionUrl: string = 'http://jfrog.com';
const secret: string = _authHandlersOptions.personalAccessToken.secret;
const personalAccessToken: string = Buffer.from(`PAT:${secret}`).toString('base64');
const expectedAuthHeader: string = `Basic ${personalAccessToken}`;
const patAuthHandler: hm.PersonalAccessTokenCredentialHandler =
new hm.PersonalAccessTokenCredentialHandler(secret);

//Nock request for redirection with expecting/matching Authorization header(s)
const redirectAuthScope = nock(url)
.matchHeader('Authorization', expectedAuthHeader)
.matchHeader('X-TFS-FedAuthRedirect', 'Suppress')
.get('/')
.reply(httpm.HttpCodes.MovedPermanently, undefined, {
location: redirectionUrl
});

//Set nock for request without expecting/matching Authorization header(s)
nock(redirectionUrl)
.matchHeader('authorization', (val: string | undefined) => !val )
.get('/')
.reply(httpm.HttpCodes.OK, {
success: true,
source: "nock"
});

//Set nock for request with expecting/matching Authorization header(s)
nock(redirectionUrl)
.matchHeader('authorization', (val: string | undefined) => val )
.get('/')
.reply(httpm.HttpCodes.BadRequest, {
success: false,
source: "nock"
});

let httpClient: httpm.HttpClient = new httpm.HttpClient('typed-rest-client-tests', [patAuthHandler]);
let httpResponse: httpm.HttpClientResponse = await httpClient.get(url);
let body: string = await httpResponse.readBody();
let asJson: any = JSON.parse(body);

assert(redirectAuthScope.isDone());
assert(httpResponse.message.statusCode == httpm.HttpCodes.OK, "status code should be 200 - OK");
assert(asJson.source === "nock", "http get request should be intercepted by nock");
assert(asJson.success, "Authentication should not occur in redirection to other hosts");
});

it('[Bearer Token] - does basic http get request with bearer token authentication', async() => {
const url: string = 'http://microsoft.com';
const bearerToken: string = _authHandlersOptions.bearer.token;
Expand Down Expand Up @@ -216,6 +307,52 @@ describe('Authentication Handlers Tests', function () {
assert(httpResponse.message.statusCode === httpm.HttpCodes.Unauthorized, "statusCode returned should be 401 - Unauthorized"); //statusCode is 401 - Unauthorized
});

it('[Bearer Token] - does redirection request with bearer token authentication', async() => {
const url: string = 'http://microsoft.com';
const redirectionUrl: string = 'http://jfrog.com';
const bearerToken: string = _authHandlersOptions.bearer.token;

const expectedAuthHeader: string = `Bearer ${bearerToken}`;
const bearerTokenAuthHandler: hm.BearerCredentialHandler = new hm.BearerCredentialHandler(bearerToken);

//Nock request for redirection with expecting/matching Authorization header(s)
const redirectAuthScope = nock(url)
.matchHeader('Authorization', expectedAuthHeader)
.matchHeader('X-TFS-FedAuthRedirect', 'Suppress')
.get('/')
.reply(httpm.HttpCodes.MovedPermanently, undefined, {
location: redirectionUrl
});

//Set nock for request without expecting/matching Authorization header(s)
nock(redirectionUrl)
.matchHeader('authorization', (val: string | undefined) => !val )
.get('/')
.reply(httpm.HttpCodes.OK, {
success: true,
source: "nock"
});

//Set nock for request with expecting/matching Authorization header(s)
nock(redirectionUrl)
.matchHeader('authorization', (val: string | undefined) => val )
.get('/')
.reply(httpm.HttpCodes.BadRequest, {
success: false,
source: "nock"
});

let httpClient: httpm.HttpClient = new httpm.HttpClient('typed-rest-client-tests', [bearerTokenAuthHandler]);
let httpResponse: httpm.HttpClientResponse = await httpClient.get(url);
let body: string = await httpResponse.readBody();
let asJson: any = JSON.parse(body);

assert(redirectAuthScope.isDone());
assert(httpResponse.message.statusCode == httpm.HttpCodes.OK, "status code should be 200 - OK");
assert(asJson.source === "nock", "http get request should be intercepted by nock");
assert(asJson.success, "Authentication should not occur in redirection to other hosts");
});

it('[NTLM] - does basic http get request with NTLM Authentication', async() => {
/**
* Following NTLM Authentication Example on:
Expand Down

0 comments on commit f9ff755

Please sign in to comment.