| "use strict"; |
| // Copyright (c) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| const http = require("http"); |
| const https = require("https"); |
| const _ = require("underscore"); |
| const ntlm = require("../opensource/node-http-ntlm/ntlm"); |
| class NtlmCredentialHandler { |
| constructor(username, password, workstation, domain) { |
| this._ntlmOptions = {}; |
| this._ntlmOptions.username = username; |
| this._ntlmOptions.password = password; |
| if (domain !== undefined) { |
| this._ntlmOptions.domain = domain; |
| } |
| else { |
| this._ntlmOptions.domain = ''; |
| } |
| if (workstation !== undefined) { |
| this._ntlmOptions.workstation = workstation; |
| } |
| else { |
| this._ntlmOptions.workstation = ''; |
| } |
| } |
| prepareRequest(options) { |
| // 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(response) { |
| 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 |
| 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; |
| } |
| handleAuthentication(httpClient, requestInfo, objs) { |
| return new Promise((resolve, reject) => { |
| const callbackForResult = function (err, res) { |
| 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); |
| }); |
| } |
| handleAuthenticationPrivate(httpClient, requestInfo, objs, finalCallback) { |
| // 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) { |
| requestInfo.options.agent = new https.Agent({ keepAlive: true }); |
| } |
| else { |
| 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, requestInfo, objs, function (err, res) { |
| if (err) { |
| return finalCallback(err, null, null); |
| } |
| /// 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 |
| sendType1Message(httpClient, requestInfo, objs, finalCallback) { |
| const type1msg = ntlm.createType1Message(this._ntlmOptions); |
| const type1options = { |
| headers: { |
| 'Connection': 'keep-alive', |
| 'Authorization': type1msg |
| }, |
| timeout: requestInfo.options.timeout || 0, |
| agent: requestInfo.httpModule, |
| }; |
| const type1info = {}; |
| 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 |
| sendType3Message(httpClient, requestInfo, objs, res, callback) { |
| if (!res.message.headers && !res.message.headers['www-authenticate']) { |
| throw new Error('www-authenticate not found on response of second request'); |
| } |
| const type2msg = ntlm.parseType2Message(res.message.headers['www-authenticate']); |
| const type3msg = ntlm.createType3Message(type2msg, this._ntlmOptions); |
| const type3options = { |
| headers: { |
| 'Authorization': type3msg, |
| 'Connection': 'Close' |
| }, |
| agent: requestInfo.httpModule, |
| }; |
| const type3info = {}; |
| 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); |
| } |
| } |
| exports.NtlmCredentialHandler = NtlmCredentialHandler; |