diff --git a/api/VsoClient.ts b/api/VsoClient.ts index fc2e50d4..142d62f0 100644 --- a/api/VsoClient.ts +++ b/api/VsoClient.ts @@ -183,25 +183,40 @@ export class VsoClient { return url.resolve(this.baseUrl, path.join(this.basePath, relativeUrl)); } - private getSerializedObject(object: any): string { + private getSerializedObject(queryValue: any, object: any): string { let value:string = ""; let first:boolean = true; for (let property in object) { if (object.hasOwnProperty(property)) { let prop = object[property]; + let valueString = this.getValueString(property, prop); if (first && prop !== undefined) { - value += property + "=" + encodeURIComponent(prop); + value += property + "=" + valueString; first = false; } else if (prop !== undefined) { - value += "&" + property +"=" + encodeURIComponent(prop); + value += "&" + property +"=" + valueString; } } } + if (value == ""){ + value += queryValue + "=" + object.toString(); + } + return value; } + private getValueString(queryValue, value) { + let valueString = null; + if (typeof(value) === 'object') { + valueString = this.getSerializedObject(queryValue, value); + } else { + valueString = queryValue + "=" + encodeURIComponent(value); + } + return valueString; + } + protected getRequestUrl(routeTemplate: string, area: string, resource: string, routeValues: any, queryParams?: any): string { // Add area/resource route values (based on the location) @@ -221,12 +236,7 @@ export class VsoClient { for (let queryValue in queryParams) { if (queryParams[queryValue] != null) { let value = queryParams[queryValue]; - let valueString = null; - if (typeof(value) === 'object') { - valueString = this.getSerializedObject(value); - } else { - valueString = queryValue + "=" + encodeURIComponent(queryParams[queryValue]); - } + let valueString = this.getValueString(queryValue, value); if (first) { relativeUrl += "?" + valueString; first = false; @@ -314,4 +324,4 @@ export class VsoClient { return result; } -} \ No newline at end of file +} diff --git a/api/handlers/basiccreds.ts b/api/handlers/basiccreds.ts index d15bff59..71002fac 100644 --- a/api/handlers/basiccreds.ts +++ b/api/handlers/basiccreds.ts @@ -1,29 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces'); - -export class BasicCredentialHandler implements VsoBaseInterfaces.IRequestHandler { - username: string; - password: string; +import ifm = require('../interfaces/common/VsoBaseInterfaces'); +import * as resthandlers from 'typed-rest-client/Handlers'; +export class BasicCredentialHandler extends resthandlers.BasicCredentialHandler implements ifm.IRequestHandler { constructor(username: string, password: string) { - this.username = username; - this.password = password; - } - - // currently implements pre-authorization - // TODO: support preAuth = false where it hooks on 401 - prepareRequest(options:any): void { - options.headers['Authorization'] = 'Basic ' + new Buffer(this.username + ':' + this.password).toString('base64'); - options.headers['X-TFS-FedAuthRedirect'] = 'Suppress'; - } - - // This handler cannot handle 401 - canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean { - return false; - } - - handleAuthentication(httpClient, protocol, options, objs, finalCallback): void { + super(username, password); } -} +} \ No newline at end of file diff --git a/api/handlers/bearertoken.ts b/api/handlers/bearertoken.ts index 8e3e5c5b..e32941ce 100644 --- a/api/handlers/bearertoken.ts +++ b/api/handlers/bearertoken.ts @@ -1,27 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces'); - -export class BearerCredentialHandler implements VsoBaseInterfaces.IRequestHandler { - token: string; +import ifm = require('../interfaces/common/VsoBaseInterfaces'); +import * as resthandlers from 'typed-rest-client/Handlers'; +export class BearerCredentialHandler extends resthandlers.BearerCredentialHandler implements ifm.IRequestHandler { constructor(token: string) { - this.token = token; - } - - // currently implements pre-authorization - // TODO: support preAuth = false where it hooks on 401 - prepareRequest(options:any): void { - options.headers['Authorization'] = 'Bearer ' + this.token; - options.headers['X-TFS-FedAuthRedirect'] = 'Suppress'; - } - - // This handler cannot handle 401 - canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean { - return false; - } - - handleAuthentication(httpClient, protocol, options, objs, finalCallback): void { + super(token); } -} +} \ No newline at end of file diff --git a/api/handlers/ntlm.ts b/api/handlers/ntlm.ts index cf037d8b..c64fba2f 100644 --- a/api/handlers/ntlm.ts +++ b/api/handlers/ntlm.ts @@ -1,125 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces'); +import ifm = require('../interfaces/common/VsoBaseInterfaces'); +import * as resthandlers from 'typed-rest-client/Handlers'; -import http = require("http"); -import https = require("https"); -var _ = require("underscore"); -var ntlm = require("../opensource/node-http-ntlm/ntlm"); - -export class NtlmCredentialHandler implements VsoBaseInterfaces.IRequestHandler { - username: string; - password: string; - workstation: string; - domain: string; - - constructor(username: string, password: string, workstation?: string, domain?: string) { - this.username = username; - this.password = password; - if (workstation !== undefined) { - this.workstation = workstation; - } - if (domain !== undefined) { - this.domain = domain; - } - } - - prepareRequest(options:any): void { - // No headers or options need to be set. We keep the credentials on the handler itself. - // If a (proxy) agent is set, remove it as we don't support proxy for NTLM at this time - if (options.agent) { - delete options.agent; - } - } - - canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean { - if (res && res.statusCode === 401) { - // Ensure that we're talking NTLM here - // Once we have the www-authenticate header, split it so we can ensure we can talk NTLM - var wwwAuthenticate = res.headers['www-authenticate']; - if (wwwAuthenticate !== undefined) { - var mechanisms = wwwAuthenticate.split(', '); - var idx = mechanisms.indexOf("NTLM"); - if (idx >= 0) { - // Check specifically for 'NTLM' since www-authenticate header can also contain - // the Authorization value to use in the form of 'NTLM TlRMTVNT....AAAADw==' - if (mechanisms[idx].length == 4) { - return true; - } - } - } - } - return false; - } - - // The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js - handleAuthentication(httpClient, protocol, options, objs, finalCallback): void { - // Set up the headers for NTLM authentication - var ntlmOptions = _.extend(options, { - username: this.username, - password: this.password, - domain: this.domain || '', - workstation: this.workstation || '' - }); - var keepaliveAgent; - if (httpClient.isSsl === true) { - keepaliveAgent = new https.Agent({}); - } else { - keepaliveAgent = new http.Agent({ keepAlive: true }); - } - let self = this; - // The following pattern of sending the type1 message following immediately (in a setImmediate) is - // critical for the NTLM exchange to happen. If we removed setImmediate (or call in a different manner) - // the NTLM exchange will always fail with a 401. - this.sendType1Message(httpClient, protocol, ntlmOptions, objs, keepaliveAgent, function (err, res) { - if (err) { - return finalCallback(err, null, null); - } - setImmediate(function () { - self.sendType3Message(httpClient, protocol, ntlmOptions, objs, keepaliveAgent, res, finalCallback); - }); - }); - } - - // The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js - private sendType1Message(httpClient, protocol, options, objs, keepaliveAgent, callback): void { - var type1msg = ntlm.createType1Message(options); - var type1options = { - headers: { - 'Connection': 'keep-alive', - 'Authorization': type1msg - }, - timeout: options.timeout || 0, - agent: keepaliveAgent, - // don't redirect because http could change to https which means we need to change the keepaliveAgent - allowRedirects: false - }; - type1options = _.extend(type1options, _.omit(options, 'headers')); - httpClient.requestInternal(protocol, type1options, objs, callback); - } - - // The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js - private sendType3Message(httpClient, protocol, options, objs, keepaliveAgent, res, callback): void { - if (!res.headers['www-authenticate']) { - return callback(new Error('www-authenticate not found on response of second request')); - } - // parse type2 message from server: - var type2msg = ntlm.parseType2Message(res.headers['www-authenticate']); - // create type3 message: - var type3msg = ntlm.createType3Message(type2msg, options); - // build type3 request: - var type3options = { - headers: { - 'Authorization': type3msg - }, - allowRedirects: false, - agent: keepaliveAgent - }; - // pass along other options: - type3options.headers = _.extend(type3options.headers, options.headers); - type3options = _.extend(type3options, _.omit(options, 'headers')); - // send type3 message to server: - httpClient.requestInternal(protocol, type3options, objs, callback); +export class NtlmCredentialHandler extends resthandlers.NtlmCredentialHandler implements ifm.IRequestHandler { + constructor(username: string, password: string, workstation?: string, domain?: string) { + super(username, password, workstation, domain); } -} +} \ No newline at end of file diff --git a/api/handlers/personalaccesstoken.ts b/api/handlers/personalaccesstoken.ts index fd6ba616..ee16a599 100644 --- a/api/handlers/personalaccesstoken.ts +++ b/api/handlers/personalaccesstoken.ts @@ -1,27 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces'); - -export class PersonalAccessTokenCredentialHandler implements VsoBaseInterfaces.IRequestHandler { - token: string; +import ifm = require('../interfaces/common/VsoBaseInterfaces'); +import * as resthandlers from 'typed-rest-client/Handlers'; +export class PersonalAccessTokenCredentialHandler extends resthandlers.PersonalAccessTokenCredentialHandler implements ifm.IRequestHandler { constructor(token: string) { - this.token = token; - } - - // currently implements pre-authorization - // TODO: support preAuth = false where it hooks on 401 - prepareRequest(options:any): void { - options.headers['Authorization'] = 'Basic ' + new Buffer('PAT:' + this.token).toString('base64'); - options.headers['X-TFS-FedAuthRedirect'] = 'Suppress'; - } - - // This handler cannot handle 401 - canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean { - return false; - } - - handleAuthentication(httpClient, protocol, options, objs, finalCallback): void { + super(token); } -} +} \ No newline at end of file diff --git a/api/interfaces/common/VsoBaseInterfaces.ts b/api/interfaces/common/VsoBaseInterfaces.ts index 05a5fe91..6199efdb 100644 --- a/api/interfaces/common/VsoBaseInterfaces.ts +++ b/api/interfaces/common/VsoBaseInterfaces.ts @@ -6,6 +6,8 @@ //---------------------------------------------------------------------------- import Serialization = require('../../Serialization'); +import http = require("http"); +import url = require("url"); /** * Information about the location of a REST API resource @@ -52,15 +54,34 @@ export interface IBasicCredentials { password: string; } +export interface IHttpClient { + options(requestUrl: string, additionalHeaders?: IHeaders): Promise; + get(requestUrl: string, additionalHeaders?: IHeaders): Promise; + del(requestUrl: string, additionalHeaders?: IHeaders): Promise; + post(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise; + patch(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise; + put(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise; + sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: IHeaders): Promise; + request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: IHeaders): Promise; + requestRaw(info: IRequestInfo, data: string | NodeJS.ReadableStream): Promise; + requestRawWithCallback(info: IRequestInfo, data: string | NodeJS.ReadableStream, onResult: (err: any, res: IHttpClientResponse) => void): void; +} + +export interface IRequestInfo { + options: http.RequestOptions; + parsedUrl: url.Url; + httpModule: any; +} + export interface IRequestHandler { - prepareRequest(options: any): void; - canHandleAuthentication(res: IHttpResponse): boolean; - handleAuthentication(httpClient, protocol, options, objs, finalCallback): void; + prepareRequest(options: http.RequestOptions): void; + canHandleAuthentication(response: IHttpClientResponse): boolean; + handleAuthentication(httpClient: IHttpClient, requestInfo: IRequestInfo, objs): Promise; } -export interface IHttpResponse { - statusCode?: number; - headers: any; +export interface IHttpClientResponse { + message: http.IncomingMessage; + readBody(): Promise; } export interface IRequestOptions { diff --git a/package-lock.json b/package-lock.json index d1ca10b9..1513d719 100644 --- a/package-lock.json +++ b/package-lock.json @@ -172,9 +172,9 @@ "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=" }, "typed-rest-client": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-0.12.0.tgz", - "integrity": "sha1-Y3b1Un9CfaEh3K/f1+QeEyHgcgw=", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.0.9.tgz", + "integrity": "sha512-iOdwgmnP/tF6Qs+oY4iEtCf/3fnCDl7Gy9LGPJ4E3M4Wj3uaSko15FVwbsaBmnBqTJORnXBWVY5306D4HH8oiA==", "requires": { "tunnel": "0.0.4", "underscore": "1.8.3" diff --git a/package.json b/package.json index cffb41ff..e82d5f60 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "license": "MIT", "dependencies": { "tunnel": "0.0.4", - "typed-rest-client": "^0.12.0", + "typed-rest-client": "1.0.9", "underscore": "1.8.3" }, "devDependencies": { diff --git a/samples/package-lock.json b/samples/package-lock.json index 47fad46f..da1b04a5 100644 --- a/samples/package-lock.json +++ b/samples/package-lock.json @@ -8,7 +8,7 @@ "version": "file:../_build", "requires": { "tunnel": "0.0.4", - "typed-rest-client": "0.12.0", + "typed-rest-client": "1.0.9", "underscore": "1.8.3" }, "dependencies": { @@ -17,7 +17,7 @@ "bundled": true }, "typed-rest-client": { - "version": "0.12.0", + "version": "1.0.9", "bundled": true, "requires": { "tunnel": "0.0.4",