Add NTLM authorization handler. (#56)

* Add NTLM authorization handler.

* Code review feedback.
This commit is contained in:
Stephen Franceschelli 2018-01-17 17:30:25 -05:00 коммит произвёл Bryan MacFarlane
Родитель 9e6a31e88e
Коммит 75a37890dd
15 изменённых файлов: 336 добавлений и 168 удалений

Просмотреть файл

@ -36,9 +36,9 @@ export enum HttpCodes {
GatewayTimeout = 504,
}
const HttpRedirectCodes: number[] = [ HttpCodes.MovedPermanently, HttpCodes.ResourceMoved, HttpCodes.TemporaryRedirect, HttpCodes.PermanentRedirect ];
const HttpRedirectCodes: number[] = [HttpCodes.MovedPermanently, HttpCodes.ResourceMoved, HttpCodes.TemporaryRedirect, HttpCodes.PermanentRedirect];
export class HttpClientResponse {
export class HttpClientResponse implements ifm.IHttpClientResponse {
constructor(message: http.IncomingMessage) {
this.message = message;
}
@ -71,11 +71,11 @@ export function isHttps(requestUrl: string) {
}
enum EnvironmentVariables {
HTTP_PROXY = "HTTP_PROXY",
HTTP_PROXY = "HTTP_PROXY",
HTTPS_PROXY = "HTTPS_PROXY",
}
export class HttpClient {
export class HttpClient implements ifm.IHttpClient {
userAgent: string;
handlers: ifm.IRequestHandler[];
requestOptions: ifm.IRequestOptions;
@ -97,7 +97,7 @@ export class HttpClient {
constructor(userAgent: string, handlers?: ifm.IRequestHandler[], requestOptions?: ifm.IRequestOptions) {
this.userAgent = userAgent;
this.handlers = handlers;
this.handlers = handlers || [];
this.requestOptions = requestOptions;
if (requestOptions) {
if (requestOptions.ignoreSslError != null) {
@ -142,35 +142,35 @@ export class HttpClient {
}
}
public options(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public options(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
}
public get(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public get(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request('GET', requestUrl, null, additionalHeaders || {});
}
public del(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public del(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request('DELETE', requestUrl, null, additionalHeaders || {});
}
public post(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public post(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request('POST', requestUrl, data, additionalHeaders || {});
}
public patch(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public patch(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request('PATCH', requestUrl, data, additionalHeaders || {});
}
public put(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public put(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request('PUT', requestUrl, data, additionalHeaders || {});
}
public head(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public head(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request('HEAD', requestUrl, null, additionalHeaders || {});
}
public sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: ifm.IHeaders): Promise<HttpClientResponse> {
public sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
return this.request(verb, requestUrl, stream, additionalHeaders);
}
@ -179,13 +179,34 @@ export class HttpClient {
* All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch
*/
public async request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: ifm.IHeaders): Promise<HttpClientResponse> {
public async request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: ifm.IHeaders): Promise<ifm.IHttpClientResponse> {
if (this._disposed) {
throw new Error("Client has already been disposed");
throw new Error("Client has already been disposed.");
}
let info: RequestInfo = this._prepareRequest(verb, requestUrl, headers);
let response: HttpClientResponse = await this._requestRaw(info, data);
let response: HttpClientResponse = await this.requestRaw(info, data);
// Check if it's an authentication challenge
if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) {
let authenticationHandler: ifm.IRequestHandler;
for (let i = 0; i < this.handlers.length; i++) {
if (this.handlers[i].canHandleAuthentication(response)) {
authenticationHandler = this.handlers[i];
break;
}
}
if (authenticationHandler) {
return authenticationHandler.handleAuthentication(this, info, data);
}
else {
// We have received an unauthorized response but have no handlers to handle it.
// Let the response return to the caller.
return response;
}
}
let redirectsRemaining: number = this._maxRedirects;
while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1
@ -204,7 +225,7 @@ export class HttpClient {
// let's make the request with the new redirectUrl
info = this._prepareRequest(verb, redirectUrl, headers);
response = await this._requestRaw(info, data);
response = await this.requestRaw(info, data);
redirectsRemaining--;
}
@ -226,63 +247,93 @@ export class HttpClient {
this._disposed = true;
}
private _requestRaw(info: RequestInfo, data: string | NodeJS.ReadableStream): Promise<HttpClientResponse> {
return new Promise<HttpClientResponse>((resolve, reject) => {
let socket;
let isDataString = typeof (data) === 'string';
if (typeof (data) === 'string') {
info.options.headers["Content-Length"] = Buffer.byteLength(data, 'utf8');
}
let req: http.ClientRequest = info.httpModule.request(info.options, (msg: http.IncomingMessage) => {
let res: HttpClientResponse = new HttpClientResponse(msg);
resolve(res);
});
req.on('socket', (sock) => {
socket = sock;
});
// If we ever get disconnected, we want the socket to timeout eventually
req.setTimeout(this._socketTimeout || 3 * 60000, () => {
if (socket) {
socket.end();
/**
* Raw request.
* @param info
* @param data
*/
public requestRaw(info: ifm.IRequestInfo, data: string | NodeJS.ReadableStream): Promise<ifm.IHttpClientResponse> {
return new Promise<ifm.IHttpClientResponse>((resolve, reject) => {
let callbackForResult = function (err: any, res: ifm.IHttpClientResponse) {
if (err) {
reject(err);
}
reject(new Error('Request timeout: ' + info.options.path));
});
req.on('error', function (err) {
// err has statusCode property
// res should have headers
reject(err);
});
resolve(res);
};
if (data && typeof (data) === 'string') {
req.write(data, 'utf8');
}
if (data && typeof (data) !== 'string') {
data.on('close', function () {
req.end();
});
data.pipe(req);
}
else {
req.end();
}
this.requestRawWithCallback(info, data, callbackForResult);
});
}
private _prepareRequest(method: string, requestUrl: string, headers: any): RequestInfo {
let info: RequestInfo = <RequestInfo>{};
/**
* Raw request with callback.
* @param info
* @param data
* @param onResult
*/
public requestRawWithCallback(info: ifm.IRequestInfo, data: string | NodeJS.ReadableStream, onResult: (err: any, res: ifm.IHttpClientResponse) => void): void {
let socket;
let isDataString = typeof (data) === 'string';
if (typeof (data) === 'string') {
info.options.headers["Content-Length"] = Buffer.byteLength(data, 'utf8');
}
let callbackCalled: boolean = false;
let handleResult = (err: any, res: HttpClientResponse) => {
if (!callbackCalled) {
callbackCalled = true;
onResult(err, res);
}
};
let req: http.ClientRequest = info.httpModule.request(info.options, (msg: http.IncomingMessage) => {
let res: HttpClientResponse = new HttpClientResponse(msg);
handleResult(null, res);
});
req.on('socket', (sock) => {
socket = sock;
});
// If we ever get disconnected, we want the socket to timeout eventually
req.setTimeout(this._socketTimeout || 3 * 60000, () => {
if (socket) {
socket.end();
}
handleResult(new Error('Request timeout: ' + info.options.path), null);
});
req.on('error', function (err) {
// err has statusCode property
// res should have headers
handleResult(err, null);
});
if (data && typeof (data) === 'string') {
req.write(data, 'utf8');
}
if (data && typeof (data) !== 'string') {
data.on('close', function () {
req.end();
});
data.pipe(req);
}
else {
req.end();
}
}
private _prepareRequest(method: string, requestUrl: string, headers: any): ifm.IRequestInfo {
const info: ifm.IRequestInfo = <ifm.IRequestInfo>{};
info.parsedUrl = url.parse(requestUrl);
let usingSsl = info.parsedUrl.protocol === 'https:';
const usingSsl: boolean = info.parsedUrl.protocol === 'https:';
info.httpModule = usingSsl ? https : http;
var defaultPort: number = usingSsl ? 443 : 80;
const defaultPort: number = usingSsl ? 443 : 80;
info.options = <http.RequestOptions>{};
info.options.host = info.parsedUrl.hostname;
info.options.port = info.parsedUrl.port ? parseInt(info.parsedUrl.port) : defaultPort;
@ -320,15 +371,15 @@ export class HttpClient {
return agent;
}
var parsedUrl = url.parse(requestUrl);
let usingSsl = parsedUrl.protocol === 'https:';
let parsedUrl = url.parse(requestUrl);
const usingSsl = parsedUrl.protocol === 'https:';
let maxSockets = 100;
if (!!this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
}
if (useProxy) {
var agentOptions: tunnel.TunnelOptions = {
const agentOptions: tunnel.TunnelOptions = {
maxSockets: maxSockets,
keepAlive: this._keepAlive,
proxy: {
@ -338,8 +389,8 @@ export class HttpClient {
},
};
var tunnelAgent: Function;
var overHttps = proxy.proxyUrl.protocol === 'https:';
let tunnelAgent: Function;
const overHttps = proxy.proxyUrl.protocol === 'https:';
if (usingSsl) {
tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp;
} else {
@ -352,13 +403,13 @@ export class HttpClient {
// if reusing agent across request and tunneling agent isn't assigned create a new agent
if (this._keepAlive && !agent) {
var options = { keepAlive: this._keepAlive, maxSockets: maxSockets };
const options = { keepAlive: this._keepAlive, maxSockets: maxSockets };
agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
this._agent = agent;
}
// if not using private agent and tunnel agent isn't setup then use global agent
if(!agent) {
if (!agent) {
agent = usingSsl ? https.globalAgent : http.globalAgent;
}
@ -377,7 +428,7 @@ export class HttpClient {
}
private _getProxy(requestUrl) {
var parsedUrl = url.parse(requestUrl);
const parsedUrl = url.parse(requestUrl);
let usingSsl = parsedUrl.protocol === 'https:';
let proxyConfig: ifm.IProxyConfiguration = this._httpProxy;

Просмотреть файл

@ -1,3 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import http = require("http");
import url = require("url");
export interface IHeaders { [key: string]: any };
export interface IBasicCredentials {
@ -5,18 +11,38 @@ export interface IBasicCredentials {
password: string;
}
export interface IRequestHandler {
prepareRequest(options: any): void;
canHandleAuthentication(res: IHttpResponse): boolean;
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void;
export interface IHttpClient {
options(requestUrl: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
get(requestUrl: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
del(requestUrl: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
post(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
patch(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
put(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: IHeaders): Promise<IHttpClientResponse>;
request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: IHeaders): Promise<IHttpClientResponse>;
requestRaw(info: IRequestInfo, data: string | NodeJS.ReadableStream): Promise<IHttpClientResponse>;
requestRawWithCallback(info: IRequestInfo, data: string | NodeJS.ReadableStream, onResult: (err: any, res: IHttpClientResponse) => void): void;
}
export interface IHttpResponse {
statusCode?: number;
headers: any;
export interface IRequestHandler {
prepareRequest(options: http.RequestOptions): void;
canHandleAuthentication(response: IHttpClientResponse): boolean;
handleAuthentication(httpClient: IHttpClient, requestInfo: IRequestInfo, objs): Promise<IHttpClientResponse>;
}
export interface IHttpClientResponse {
message: http.IncomingMessage;
readBody(): Promise<string>;
}
export interface IRequestInfo {
options: http.RequestOptions;
parsedUrl: url.Url;
httpModule: any;
}
export interface IRequestOptions {
headers?: IHeaders;
socketTimeout?: number,
ignoreSslError?: boolean,
proxy?: IProxyConfiguration,
@ -39,4 +65,4 @@ export interface ICertConfiguration {
certFile?: string;
keyFile?: string;
passphrase?: string;
}
}

Просмотреть файл

@ -1,3 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import * as url from 'url';
import * as path from 'path';

Просмотреть файл

@ -20,10 +20,11 @@ export class BasicCredentialHandler implements ifm.IRequestHandler {
}
// This handler cannot handle 401
canHandleAuthentication(res: ifm.IHttpResponse): boolean {
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
return false;
}
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void {
handleAuthentication(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs): Promise<ifm.IHttpClientResponse> {
return null;
}
}

Просмотреть файл

@ -18,10 +18,11 @@ export class BearerCredentialHandler implements ifm.IRequestHandler {
}
// This handler cannot handle 401
canHandleAuthentication(res: ifm.IHttpResponse): boolean {
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
return false;
}
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void {
handleAuthentication(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs): Promise<ifm.IHttpClientResponse> {
return null;
}
}

Просмотреть файл

@ -2,30 +2,37 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import ifm = require('../Interfaces');
import http = require("http");
import https = require("https");
var _ = require("underscore");
var ntlm = require("../opensource/node-http-ntlm/ntlm");
export class NtlmCredentialHandler implements ifm.IRequestHandler {
username: string;
password: string;
workstation: string;
domain: string;
interface INtlmOptions {
username?: string,
password?: string,
domain: string,
workstation: string
}
export class NtlmCredentialHandler implements ifm.IRequestHandler {
private _ntlmOptions: INtlmOptions;
constructor(username: string, password: string, workstation?: string, domain?: string) {
this._ntlmOptions = <INtlmOptions>{};
this._ntlmOptions.username = username;
this._ntlmOptions.password = password;
constructor(username: string, password: string, domain?: string, workstation?: string) {
this.username = username;
this.password = password;
if (domain !== undefined) {
this.domain = domain;
this._ntlmOptions.domain = domain;
}
if (workstation !== undefined) {
this.workstation = workstation;
this._ntlmOptions.workstation = workstation;
}
}
prepareRequest(options:any): void {
public prepareRequest(options: http.RequestOptions): 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) {
@ -33,93 +40,123 @@ export class NtlmCredentialHandler implements ifm.IRequestHandler {
}
}
canHandleAuthentication(res: ifm.IHttpResponse): boolean {
if (res && res.statusCode === 401) {
public canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
if (response && response.message && response.message.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;
}
const wwwAuthenticate = response.message.headers['www-authenticate'];
if (wwwAuthenticate) {
const mechanisms = wwwAuthenticate.split(', ');
const index = mechanisms.indexOf("NTLM");
if (index >= 0) {
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 || ''
public handleAuthentication(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs): Promise<ifm.IHttpClientResponse> {
return new Promise<ifm.IHttpClientResponse>((resolve, reject) => {
var callbackForResult = function (err: any, res: ifm.IHttpClientResponse) {
if(err) {
reject(err);
}
// We have to readbody on the response before continuing otherwise there is a hang.
res.readBody().then(() => {
resolve(res);
});
};
this.handleAuthenticationPrivate(httpClient, requestInfo, objs, callbackForResult);
});
var keepaliveAgent;
}
private handleAuthenticationPrivate(httpClient: any, requestInfo: ifm.IRequestInfo, objs, finalCallback): void {
// Set up the headers for NTLM authentication
requestInfo.options = _.extend(requestInfo.options, {
username: this._ntlmOptions.username,
password: this._ntlmOptions.password,
domain: this._ntlmOptions.domain,
workstation: this._ntlmOptions.workstation
});
if (httpClient.isSsl === true) {
keepaliveAgent = new https.Agent({});
requestInfo.options.agent = new https.Agent({ keepAlive: true });
} else {
keepaliveAgent = new http.Agent({ keepAlive: true });
requestInfo.options.agent = 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) {
this.sendType1Message(httpClient, requestInfo, objs, function (err, res) {
if (err) {
return finalCallback(err, null, null);
}
setImmediate(function () {
self.sendType3Message(httpClient, protocol, ntlmOptions, objs, keepaliveAgent, res, finalCallback);
/// We have to readbody on the response before continuing otherwise there is a hang.
res.readBody().then(() => {
// It is critical that we have setImmediate here due to how connection requests are queued.
// If setImmediate is removed then the NTLM handshake will not work.
// setImmediate allows us to queue a second request on the same connection. If this second
// request is not queued on the connection when the first request finishes then node closes
// the connection. NTLM requires both requests to be on the same connection so we need this.
setImmediate(function () {
self.sendType3Message(httpClient, requestInfo, objs, 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 = {
private sendType1Message(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs: any, finalCallback): void {
const type1msg = ntlm.createType1Message(this._ntlmOptions);
const type1options: http.RequestOptions = {
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
timeout: requestInfo.options.timeout || 0,
agent: requestInfo.httpModule,
};
type1options = _.extend(type1options, _.omit(options, 'headers'));
httpClient.requestInternal(protocol, type1options, objs, callback);
const type1info = <ifm.IRequestInfo>{};
type1info.httpModule = requestInfo.httpModule;
type1info.parsedUrl = requestInfo.parsedUrl;
type1info.options = _.extend(type1options, _.omit(requestInfo.options, 'headers'));
return httpClient.requestRawWithCallback(type1info, objs, finalCallback);
}
// 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'));
private sendType3Message(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs: any, res, callback): void {
if (!res.message.headers && !res.message.headers['www-authenticate']) {
throw 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 = {
const type2msg = ntlm.parseType2Message(res.message.headers['www-authenticate']);
const type3msg = ntlm.createType3Message(type2msg, this._ntlmOptions);
const type3options: http.RequestOptions = {
headers: {
'Authorization': type3msg
'Authorization': type3msg,
'Connection': 'Close'
},
allowRedirects: false,
agent: keepaliveAgent
agent: requestInfo.httpModule,
};
// 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);
const type3info = <ifm.IRequestInfo>{};
type3info.httpModule = requestInfo.httpModule;
type3info.parsedUrl = requestInfo.parsedUrl;
type3options.headers = _.extend(type3options.headers, requestInfo.options.headers);
type3info.options = _.extend(type3options, _.omit(requestInfo.options, 'headers'));
return httpClient.requestRawWithCallback(type3info, objs, callback);
}
}

Просмотреть файл

@ -18,10 +18,11 @@ export class PersonalAccessTokenCredentialHandler implements ifm.IRequestHandler
}
// This handler cannot handle 401
canHandleAuthentication(res: ifm.IHttpResponse): boolean {
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
return false;
}
handleAuthentication(httpClient, protocol, options, objs, finalCallback): void {
handleAuthentication(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs): Promise<ifm.IHttpClientResponse> {
return null;
}
}

Просмотреть файл

@ -387,4 +387,3 @@ exports.createType3Message = createType3Message;

6
package-lock.json сгенерированный
Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "typed-rest-client",
"version": "1.0.1",
"version": "1.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -23,13 +23,13 @@
"@types/mocha": {
"version": "2.2.44",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.44.tgz",
"integrity": "sha512-k2tWTQU8G4+iSMvqKi0Q9IIsWAp/n8xzdZS4Q4YVIltApoMA00wFBFdlJnmoaK1/z7B0Cy0yPe6GgXteSmdUNw==",
"integrity": "sha1-HUp5jlPzUhL9WtTQQFBiAXHNW14=",
"dev": true
},
"@types/node": {
"version": "6.0.92",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.92.tgz",
"integrity": "sha512-awEYSSTn7dauwVCYSx2CJaPTu0Z1Ht2oR1b2AD3CYao6ZRb+opb6EL43fzmD7eMFgMHzTBWSUzlWSD+S8xN0Nw==",
"integrity": "sha1-5/chrigncuEroleZaMANnM5CLF0=",
"dev": true
},
"@types/shelljs": {

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "typed-rest-client",
"version": "1.0.2",
"version": "1.0.3",
"description": "Node Rest and Http Clients for use with TypeScript",
"main": "./RestClient.js",
"scripts": {

Просмотреть файл

@ -1,15 +1,23 @@
import * as hm from 'typed-rest-client/Handlers'
import * as cm from './common';
import * as httpm from 'typed-rest-client/HttpClient';
export async function run() {
cm.banner('Handler Samples');
var username = "";
var password = "";
var workstation = "";
var domain = "";
var url = "";
let bh: hm.BasicCredentialHandler = new hm.BasicCredentialHandler('johndoe', 'password');
let ph: hm.PersonalAccessTokenCredentialHandler =
new hm.PersonalAccessTokenCredentialHandler('scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs');
let nh: hm.NtlmCredentialHandler = new hm.NtlmCredentialHandler('johndoe', 'password');
const basicHandler: hm.BasicCredentialHandler = new hm.BasicCredentialHandler(username, password);
const patHandler: hm.PersonalAccessTokenCredentialHandler = new hm.PersonalAccessTokenCredentialHandler('scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs');
const ntlmHandler: hm.NtlmCredentialHandler = new hm.NtlmCredentialHandler(username, password, workstation, domain);
// These handlers would then be passed to the constructors of the http or rest modules
// const httpClient: httpm.HttpClient = new httpm.HttpClient('vsts-node-api', [ntlmHandler]);
// const response: httpm.HttpClientResponse = await httpClient.get(url);
// console.log("response code: " + response.message.statusCode);
}

18
samples/package-lock.json сгенерированный
Просмотреть файл

@ -11,6 +11,24 @@
"underscore": "1.8.3"
},
"dependencies": {
"httpntlm": {
"version": "1.7.5",
"bundled": true,
"requires": {
"httpreq": "0.4.24",
"underscore": "1.7.0"
},
"dependencies": {
"underscore": {
"version": "1.7.0",
"bundled": true
}
}
},
"httpreq": {
"version": "0.4.24",
"bundled": true
},
"tunnel": {
"version": "0.0.4",
"bundled": true

Просмотреть файл

@ -80,7 +80,7 @@ describe('Http Tests', function () {
let creds: string = Buffer.from(auth.substring('Basic '.length), 'base64').toString();
assert(creds === 'PAT:' + token, "creds should be the token");
assert(obj.url === "http://httpbin.org/get");
});
});
it('pipes a get request', () => {
return new Promise<string>(async (resolve, reject) => {
@ -107,12 +107,12 @@ describe('Http Tests', function () {
let res: httpm.HttpClientResponse = await http.get("https://httpbin.org/redirect-to?url=" + encodeURIComponent("https://httpbin.org/get"))
assert(res.message.statusCode == 302, "status code should be 302");
let body: string = await res.readBody();
});
});
it('does basic head request', async() => {
let res: httpm.HttpClientResponse = await _http.head('http://httpbin.org/get');
assert(res.message.statusCode == 200, "status code should be 200");
});
});
it('does basic http delete request', async() => {
let res: httpm.HttpClientResponse = await _http.del('http://httpbin.org/delete');
@ -139,7 +139,7 @@ describe('Http Tests', function () {
let obj:any = JSON.parse(body);
assert(obj.data === b);
assert(obj.url === "http://httpbin.org/patch");
});
});
it('does basic http options request', async() => {
let res: httpm.HttpClientResponse = await _http.options('http://httpbin.org');

18
test/package-lock.json сгенерированный
Просмотреть файл

@ -11,6 +11,24 @@
"underscore": "1.8.3"
},
"dependencies": {
"httpntlm": {
"version": "1.7.5",
"bundled": true,
"requires": {
"httpreq": "0.4.24",
"underscore": "1.7.0"
},
"dependencies": {
"underscore": {
"version": "1.7.0",
"bundled": true
}
}
},
"httpreq": {
"version": "0.4.24",
"bundled": true
},
"tunnel": {
"version": "0.0.4",
"bundled": true

Просмотреть файл

@ -64,6 +64,8 @@ describe('Rest Tests', function () {
});
it('replaces a resource', async() => {
this.timeout(3000);
let res: any = { name: 'foo' };
let restRes: restm.IRestResponse<HttpBinData> = await _rest.replace<HttpBinData>('https://httpbin.org/put', res);
assert(restRes.statusCode == 200, "statusCode should be 200");
@ -126,6 +128,8 @@ describe('Rest Tests', function () {
// should return a null resource, 404 status, and should not throw
//
it('gets a non-existant resource (404)', async() => {
this.timeout(3000);
try {
let restRes: restm.IRestResponse<HttpBinData> = await _rest.get<HttpBinData>('https://httpbin.org/status/404');
@ -248,6 +252,7 @@ describe('Rest Tests', function () {
it('maintains the path from the base url where request has query parameters', async() => {
// Arrange
this.timeout(3000);
let rest = new restm.RestClient('typed-rest-client-tests', 'https://httpbin.org/anything/multiple');
// Act