Skip to content

Commit e39dc4e

Browse files
damccormstephenmichaelf
authored andcommitted
Upgrade to latest typed-rest-client (#206)
* Upgrade to latest typed-rest-client * Missed 2 files * Was misusing some variables, had to do away with final callback option * Updated VSO Client to serialize nested objects correctly * Updated handlers to match typed-rest-client * Now uses the handlers from typed-rest-client * Revert "Now uses the handlers from typed-rest-client" This reverts commit 7ae7a55. * Updated handlers to use typed-rest-client logic without hurting backwards compat * Making getValueString private instead of protected
1 parent 48bd17e commit e39dc4e

File tree

9 files changed

+74
-207
lines changed

9 files changed

+74
-207
lines changed

api/VsoClient.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,25 +183,40 @@ export class VsoClient {
183183
return url.resolve(this.baseUrl, path.join(this.basePath, relativeUrl));
184184
}
185185

186-
private getSerializedObject(object: any): string {
186+
private getSerializedObject(queryValue: any, object: any): string {
187187
let value:string = "";
188188
let first:boolean = true;
189189

190190
for (let property in object) {
191191
if (object.hasOwnProperty(property)) {
192192
let prop = object[property];
193+
let valueString = this.getValueString(property, prop);
193194
if (first && prop !== undefined) {
194-
value += property + "=" + encodeURIComponent(prop);
195+
value += property + "=" + valueString;
195196
first = false;
196197
} else if (prop !== undefined) {
197-
value += "&" + property +"=" + encodeURIComponent(prop);
198+
value += "&" + property +"=" + valueString;
198199
}
199200
}
200201
}
201202

203+
if (value == ""){
204+
value += queryValue + "=" + object.toString();
205+
}
206+
202207
return value;
203208
}
204209

210+
private getValueString(queryValue, value) {
211+
let valueString = null;
212+
if (typeof(value) === 'object') {
213+
valueString = this.getSerializedObject(queryValue, value);
214+
} else {
215+
valueString = queryValue + "=" + encodeURIComponent(value);
216+
}
217+
return valueString;
218+
}
219+
205220
protected getRequestUrl(routeTemplate: string, area: string, resource: string, routeValues: any, queryParams?: any): string {
206221

207222
// Add area/resource route values (based on the location)
@@ -221,12 +236,7 @@ export class VsoClient {
221236
for (let queryValue in queryParams) {
222237
if (queryParams[queryValue] != null) {
223238
let value = queryParams[queryValue];
224-
let valueString = null;
225-
if (typeof(value) === 'object') {
226-
valueString = this.getSerializedObject(value);
227-
} else {
228-
valueString = queryValue + "=" + encodeURIComponent(queryParams[queryValue]);
229-
}
239+
let valueString = this.getValueString(queryValue, value);
230240
if (first) {
231241
relativeUrl += "?" + valueString;
232242
first = false;
@@ -314,4 +324,4 @@ export class VsoClient {
314324

315325
return result;
316326
}
317-
}
327+
}

api/handlers/basiccreds.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,11 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces');
5-
6-
export class BasicCredentialHandler implements VsoBaseInterfaces.IRequestHandler {
7-
username: string;
8-
password: string;
4+
import ifm = require('../interfaces/common/VsoBaseInterfaces');
5+
import * as resthandlers from 'typed-rest-client/Handlers';
96

7+
export class BasicCredentialHandler extends resthandlers.BasicCredentialHandler implements ifm.IRequestHandler {
108
constructor(username: string, password: string) {
11-
this.username = username;
12-
this.password = password;
13-
}
14-
15-
// currently implements pre-authorization
16-
// TODO: support preAuth = false where it hooks on 401
17-
prepareRequest(options:any): void {
18-
options.headers['Authorization'] = 'Basic ' + new Buffer(this.username + ':' + this.password).toString('base64');
19-
options.headers['X-TFS-FedAuthRedirect'] = 'Suppress';
20-
}
21-
22-
// This handler cannot handle 401
23-
canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean {
24-
return false;
25-
}
26-
27-
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void {
9+
super(username, password);
2810
}
29-
}
11+
}

api/handlers/bearertoken.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces');
5-
6-
export class BearerCredentialHandler implements VsoBaseInterfaces.IRequestHandler {
7-
token: string;
4+
import ifm = require('../interfaces/common/VsoBaseInterfaces');
5+
import * as resthandlers from 'typed-rest-client/Handlers';
86

7+
export class BearerCredentialHandler extends resthandlers.BearerCredentialHandler implements ifm.IRequestHandler {
98
constructor(token: string) {
10-
this.token = token;
11-
}
12-
13-
// currently implements pre-authorization
14-
// TODO: support preAuth = false where it hooks on 401
15-
prepareRequest(options:any): void {
16-
options.headers['Authorization'] = 'Bearer ' + this.token;
17-
options.headers['X-TFS-FedAuthRedirect'] = 'Suppress';
18-
}
19-
20-
// This handler cannot handle 401
21-
canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean {
22-
return false;
23-
}
24-
25-
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void {
9+
super(token);
2610
}
27-
}
11+
}

api/handlers/ntlm.ts

Lines changed: 6 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,11 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces');
4+
import ifm = require('../interfaces/common/VsoBaseInterfaces');
5+
import * as resthandlers from 'typed-rest-client/Handlers';
56

6-
import http = require("http");
7-
import https = require("https");
8-
var _ = require("underscore");
9-
var ntlm = require("../opensource/node-http-ntlm/ntlm");
10-
11-
export class NtlmCredentialHandler implements VsoBaseInterfaces.IRequestHandler {
12-
username: string;
13-
password: string;
14-
workstation: string;
15-
domain: string;
16-
17-
constructor(username: string, password: string, workstation?: string, domain?: string) {
18-
this.username = username;
19-
this.password = password;
20-
if (workstation !== undefined) {
21-
this.workstation = workstation;
22-
}
23-
if (domain !== undefined) {
24-
this.domain = domain;
25-
}
26-
}
27-
28-
prepareRequest(options:any): void {
29-
// No headers or options need to be set. We keep the credentials on the handler itself.
30-
// If a (proxy) agent is set, remove it as we don't support proxy for NTLM at this time
31-
if (options.agent) {
32-
delete options.agent;
33-
}
34-
}
35-
36-
canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean {
37-
if (res && res.statusCode === 401) {
38-
// Ensure that we're talking NTLM here
39-
// Once we have the www-authenticate header, split it so we can ensure we can talk NTLM
40-
var wwwAuthenticate = res.headers['www-authenticate'];
41-
if (wwwAuthenticate !== undefined) {
42-
var mechanisms = wwwAuthenticate.split(', ');
43-
var idx = mechanisms.indexOf("NTLM");
44-
if (idx >= 0) {
45-
// Check specifically for 'NTLM' since www-authenticate header can also contain
46-
// the Authorization value to use in the form of 'NTLM TlRMTVNT....AAAADw=='
47-
if (mechanisms[idx].length == 4) {
48-
return true;
49-
}
50-
}
51-
}
52-
}
53-
return false;
54-
}
55-
56-
// The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js
57-
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void {
58-
// Set up the headers for NTLM authentication
59-
var ntlmOptions = _.extend(options, {
60-
username: this.username,
61-
password: this.password,
62-
domain: this.domain || '',
63-
workstation: this.workstation || ''
64-
});
65-
var keepaliveAgent;
66-
if (httpClient.isSsl === true) {
67-
keepaliveAgent = new https.Agent({});
68-
} else {
69-
keepaliveAgent = new http.Agent({ keepAlive: true });
70-
}
71-
let self = this;
72-
// The following pattern of sending the type1 message following immediately (in a setImmediate) is
73-
// critical for the NTLM exchange to happen. If we removed setImmediate (or call in a different manner)
74-
// the NTLM exchange will always fail with a 401.
75-
this.sendType1Message(httpClient, protocol, ntlmOptions, objs, keepaliveAgent, function (err, res) {
76-
if (err) {
77-
return finalCallback(err, null, null);
78-
}
79-
setImmediate(function () {
80-
self.sendType3Message(httpClient, protocol, ntlmOptions, objs, keepaliveAgent, res, finalCallback);
81-
});
82-
});
83-
}
84-
85-
// The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js
86-
private sendType1Message(httpClient, protocol, options, objs, keepaliveAgent, callback): void {
87-
var type1msg = ntlm.createType1Message(options);
88-
var type1options = {
89-
headers: {
90-
'Connection': 'keep-alive',
91-
'Authorization': type1msg
92-
},
93-
timeout: options.timeout || 0,
94-
agent: keepaliveAgent,
95-
// don't redirect because http could change to https which means we need to change the keepaliveAgent
96-
allowRedirects: false
97-
};
98-
type1options = _.extend(type1options, _.omit(options, 'headers'));
99-
httpClient.requestInternal(protocol, type1options, objs, callback);
100-
}
101-
102-
// The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js
103-
private sendType3Message(httpClient, protocol, options, objs, keepaliveAgent, res, callback): void {
104-
if (!res.headers['www-authenticate']) {
105-
return callback(new Error('www-authenticate not found on response of second request'));
106-
}
107-
// parse type2 message from server:
108-
var type2msg = ntlm.parseType2Message(res.headers['www-authenticate']);
109-
// create type3 message:
110-
var type3msg = ntlm.createType3Message(type2msg, options);
111-
// build type3 request:
112-
var type3options = {
113-
headers: {
114-
'Authorization': type3msg
115-
},
116-
allowRedirects: false,
117-
agent: keepaliveAgent
118-
};
119-
// pass along other options:
120-
type3options.headers = _.extend(type3options.headers, options.headers);
121-
type3options = _.extend(type3options, _.omit(options, 'headers'));
122-
// send type3 message to server:
123-
httpClient.requestInternal(protocol, type3options, objs, callback);
7+
export class NtlmCredentialHandler extends resthandlers.NtlmCredentialHandler implements ifm.IRequestHandler {
8+
constructor(username: string, password: string, workstation?: string, domain?: string) {
9+
super(username, password, workstation, domain);
12410
}
125-
}
11+
}
Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
import VsoBaseInterfaces = require('../interfaces/common/VsoBaseInterfaces');
5-
6-
export class PersonalAccessTokenCredentialHandler implements VsoBaseInterfaces.IRequestHandler {
7-
token: string;
4+
import ifm = require('../interfaces/common/VsoBaseInterfaces');
5+
import * as resthandlers from 'typed-rest-client/Handlers';
86

7+
export class PersonalAccessTokenCredentialHandler extends resthandlers.PersonalAccessTokenCredentialHandler implements ifm.IRequestHandler {
98
constructor(token: string) {
10-
this.token = token;
11-
}
12-
13-
// currently implements pre-authorization
14-
// TODO: support preAuth = false where it hooks on 401
15-
prepareRequest(options:any): void {
16-
options.headers['Authorization'] = 'Basic ' + new Buffer('PAT:' + this.token).toString('base64');
17-
options.headers['X-TFS-FedAuthRedirect'] = 'Suppress';
18-
}
19-
20-
// This handler cannot handle 401
21-
canHandleAuthentication(res: VsoBaseInterfaces.IHttpResponse): boolean {
22-
return false;
23-
}
24-
25-
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void {
9+
super(token);
2610
}
27-
}
11+
}

api/interfaces/common/VsoBaseInterfaces.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
//----------------------------------------------------------------------------
77

88
import Serialization = require('../../Serialization');
9+
import http = require("http");
10+
import url = require("url");
911

1012
/**
1113
* Information about the location of a REST API resource
@@ -52,15 +54,34 @@ export interface IBasicCredentials {
5254
password: string;
5355
}
5456

57+
export interface IHttpClient {
58+
options(requestUrl: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
59+
get(requestUrl: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
60+
del(requestUrl: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
61+
post(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
62+
patch(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
63+
put(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
64+
sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
65+
request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: IHeaders): Promise<IHttpClientResponse>;
66+
requestRaw(info: IRequestInfo, data: string | NodeJS.ReadableStream): Promise<IHttpClientResponse>;
67+
requestRawWithCallback(info: IRequestInfo, data: string | NodeJS.ReadableStream, onResult: (err: any, res: IHttpClientResponse) => void): void;
68+
}
69+
70+
export interface IRequestInfo {
71+
options: http.RequestOptions;
72+
parsedUrl: url.Url;
73+
httpModule: any;
74+
}
75+
5576
export interface IRequestHandler {
56-
prepareRequest(options: any): void;
57-
canHandleAuthentication(res: IHttpResponse): boolean;
58-
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void;
77+
prepareRequest(options: http.RequestOptions): void;
78+
canHandleAuthentication(response: IHttpClientResponse): boolean;
79+
handleAuthentication(httpClient: IHttpClient, requestInfo: IRequestInfo, objs): Promise<IHttpClientResponse>;
5980
}
6081

61-
export interface IHttpResponse {
62-
statusCode?: number;
63-
headers: any;
82+
export interface IHttpClientResponse {
83+
message: http.IncomingMessage;
84+
readBody(): Promise<string>;
6485
}
6586

6687
export interface IRequestOptions {

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"license": "MIT",
2323
"dependencies": {
2424
"tunnel": "0.0.4",
25-
"typed-rest-client": "^0.12.0",
25+
"typed-rest-client": "1.0.9",
2626
"underscore": "1.8.3"
2727
},
2828
"devDependencies": {

samples/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)