postman-runtime 7.19.0 → 7.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc CHANGED
@@ -60,7 +60,7 @@
60
60
  "block-scoped-var": "error",
61
61
  "class-methods-use-this": "error",
62
62
  "complexity": "off",
63
- "consistent-return": "warn",
63
+ "consistent-return": "off",
64
64
  "curly": "error",
65
65
  "default-case": "error",
66
66
  "dot-location": [
package/CHANGELOG.yaml CHANGED
@@ -1,3 +1,48 @@
1
+ 7.21.0:
2
+ date: 2019-12-02
3
+ new features:
4
+ - >-
5
+ GH-937 Added ability to use OAuth2 tokens with unknown types as Bearer
6
+ token in OAuth2 authentication
7
+ - GH-934 Added ability to pull domain name from username for NTLM auth
8
+ fixed bugs:
9
+ - >-
10
+ GH-939 Fixed a bug where IPv6 localhost request through IPv4 localhost
11
+ proxy was failing.
12
+ - >-
13
+ GH-928 Fixed a bug where Basic Auth was failing if credentials had
14
+ non-ASCII characters
15
+ - >-
16
+ GH-929 Fixed a bug where error was thrown when `username` or `password`
17
+ fields were empty for NTLM auth
18
+ - >-
19
+ GH-933 Fixed a bug where NTLM could not complete authentication if
20
+ multiple `www-authenticate` headers were sent
21
+ chores:
22
+ - >-
23
+ GH-942 Convert response into a JSON serializable object before executing
24
+ the script
25
+ - Updated dependencies
26
+
27
+ 7.20.1:
28
+ date: 2019-11-13
29
+ chores:
30
+ - Updated dependencies
31
+
32
+ 7.20.0:
33
+ date: 2019-11-12
34
+ new features:
35
+ - GH-921 Added ability to preserve non-JSON data types in console logs
36
+ - >-
37
+ GH-921 Added an option `script.serializeLogs` to allow sending logs as a
38
+ serialized string which can be parsed using `teleport-javascript`
39
+ fixed bugs:
40
+ - >-
41
+ GH-918 Fixed a bug where requests having binary body with AWS, Hawk or
42
+ EdgeGrid authentication caused the Runtime to crash
43
+ chores:
44
+ - Updated dependencies
45
+
1
46
  7.19.0:
2
47
  date: 2019-10-16
3
48
  new features:
package/README.md CHANGED
@@ -135,6 +135,14 @@ runner.run(collection, {
135
135
  }
136
136
  },
137
137
 
138
+ // Options specific to the script execution
139
+ script: {
140
+
141
+ // Option to set whether to send console logs in serialized format which can be parsed
142
+ // using the `teleport-javascript` serialization library.
143
+ serializeLogs: false
144
+ },
145
+
138
146
  // A ProxyConfigList, from the SDK
139
147
  proxies: new sdk.ProxyConfigList(),
140
148
 
@@ -63,7 +63,7 @@ var _ = require('lodash'),
63
63
  return callback();
64
64
  }
65
65
 
66
- originalReadStream.cloneReadStream(function (err, clonedStream) {
66
+ return originalReadStream.cloneReadStream(function (err, clonedStream) {
67
67
  if (err) { return callback(); }
68
68
 
69
69
  clonedStream.on('data', function (chunk) {
@@ -1,5 +1,3 @@
1
- var btoa = require('btoa');
2
-
3
1
  /**
4
2
  * @implements {AuthHandlerInterface}
5
3
  */
@@ -70,7 +68,7 @@ module.exports = {
70
68
  request.removeHeader('Authorization', {ignoreCase: true});
71
69
  request.addHeader({
72
70
  key: 'Authorization',
73
- value: 'Basic ' + btoa(username + ':' + password),
71
+ value: 'Basic ' + Buffer.from(`${username}:${password}`, 'utf8').toString('base64'),
74
72
  system: true
75
73
  });
76
74
 
@@ -142,7 +142,7 @@ var _ = require('lodash'),
142
142
  return callback();
143
143
  }
144
144
 
145
- originalReadStream.cloneReadStream(function (err, clonedStream) {
145
+ return originalReadStream.cloneReadStream(function (err, clonedStream) {
146
146
  if (err) { return callback(); }
147
147
 
148
148
  clonedStream.on('data', function (chunk) {
@@ -90,7 +90,7 @@ function computeBodyHash (body, algorithm, digestEncoding, contentType, callback
90
90
  return callback();
91
91
  }
92
92
 
93
- originalReadStream.cloneReadStream(function (err, clonedStream) {
93
+ return originalReadStream.cloneReadStream(function (err, clonedStream) {
94
94
  if (err) { return callback(); }
95
95
 
96
96
  clonedStream.on('data', function (chunk) {
@@ -31,6 +31,58 @@ var ntlmUtil = require('httpntlm').ntlm,
31
31
  T3_MSG_CREATED: 'T3_MSG_CREATED'
32
32
  };
33
33
 
34
+ /**
35
+ * Parses the username to separate username and domain. It can handle two formats:
36
+ * - Down-Level Logon name format `DOMAIN\USERNAME`
37
+ * - User Principal Name format `USERNAME@DOMAIN`
38
+ *
39
+ * @param {String} username - Username string to parse from
40
+ * @return {Object} - An object with `username` and `domain` fields, which are `strings`.
41
+ */
42
+ function parseParametersFromUsername (username) {
43
+ var dllParams,
44
+ upnParams;
45
+
46
+ if (!(username && typeof username === 'string')) {
47
+ return {
48
+ username: EMPTY,
49
+ domain: EMPTY
50
+ };
51
+ }
52
+
53
+ dllParams = username.split('\\');
54
+ upnParams = username.split('@');
55
+
56
+ // username should be either of the two formats, not both
57
+ if (dllParams.length > 1 && upnParams.length > 1) {
58
+ return {
59
+ username,
60
+ domain: EMPTY
61
+ };
62
+ }
63
+
64
+ // try to parse from "down level logon" format
65
+ if (dllParams.length === 2 && dllParams[0] && dllParams[1]) {
66
+ return {
67
+ username: dllParams[1],
68
+ domain: dllParams[0]
69
+ };
70
+ }
71
+
72
+ // try to parse from "user principal name" format
73
+ if (upnParams.length === 2 && upnParams[0] && upnParams[1]) {
74
+ return {
75
+ username: upnParams[0],
76
+ domain: upnParams[1]
77
+ };
78
+ }
79
+
80
+ return {
81
+ username,
82
+ domain: EMPTY
83
+ };
84
+ }
85
+
34
86
  /**
35
87
  * NTLM auth while authenticating requires negotiateMessage (type 1) and authenticateMessage (type 3) to be stored.
36
88
  * Also it needs to know which stage is it in (INITIALIZED, T1_MSG_CREATED and T3_MSG_CREATED).
@@ -96,16 +148,26 @@ module.exports = {
96
148
  var state = auth.get(STATE),
97
149
  domain = auth.get(NTLM_PARAMETERS.DOMAIN) || EMPTY,
98
150
  workstation = auth.get(NTLM_PARAMETERS.WORKSTATION) || EMPTY,
99
- username = auth.get(NTLM_PARAMETERS.USERNAME),
100
- password = auth.get(NTLM_PARAMETERS.PASSWORD),
151
+ username = auth.get(NTLM_PARAMETERS.USERNAME) || EMPTY,
152
+ password = auth.get(NTLM_PARAMETERS.PASSWORD) || EMPTY,
101
153
  negotiateMessage, // type 1
102
154
  challengeMessage, // type 2
103
- authenticateMessage; // type 3
155
+ authenticateMessage, // type 3
156
+ ntlmType2Header,
157
+ parsedParameters;
104
158
 
105
159
  if (response.code !== 401 && response.code !== 403) {
106
160
  return done(null, true);
107
161
  }
108
162
 
163
+ // we try to extract domain from username if not specified.
164
+ if (!domain) {
165
+ parsedParameters = parseParametersFromUsername(username) || {};
166
+
167
+ username = parsedParameters.username;
168
+ domain = parsedParameters.domain;
169
+ }
170
+
109
171
  if (state === STATES.INITIALIZED) {
110
172
  // Nothing to do if the server does not ask us for auth in the first place.
111
173
  if (!(response.headers.has(WWW_AUTHENTICATE, NTLM) ||
@@ -130,7 +192,20 @@ module.exports = {
130
192
  }
131
193
  else if (state === STATES.T1_MSG_CREATED) {
132
194
  // At this point, we can assume that the type 1 message was sent to the server
133
- challengeMessage = ntlmUtil.parseType2Message(response.headers.get(WWW_AUTHENTICATE) || EMPTY, _.noop);
195
+
196
+ // there can be multiple headers present with key `www-authenticate`.
197
+ // iterate to get the one which has the NTLM hash. if multiple
198
+ // headers have the NTLM hash, use the first one.
199
+ ntlmType2Header = response.headers.find(function (header) {
200
+ return String(header.key).toLowerCase() === WWW_AUTHENTICATE &&
201
+ header.valueOf().startsWith('NTLM ');
202
+ });
203
+
204
+ if (!ntlmType2Header) {
205
+ return done(new Error('ntlm: server did not send NTLM type 2 message'));
206
+ }
207
+
208
+ challengeMessage = ntlmUtil.parseType2Message(ntlmType2Header.valueOf(), _.noop);
134
209
 
135
210
  if (!challengeMessage) {
136
211
  return done(new Error('ntlm: server did not correctly process authentication request'));
@@ -3,6 +3,7 @@ var _ = require('lodash'),
3
3
  HEADER = 'header',
4
4
  QUERY_PARAMS = 'queryParams',
5
5
  BEARER = 'bearer',
6
+ MAC = 'mac',
6
7
  AUTHORIZATION = 'Authorization',
7
8
  ACCESS_TOKEN = 'access_token',
8
9
  AUTHORIZATION_PREFIX = 'Bearer ',
@@ -94,27 +95,31 @@ module.exports = {
94
95
  tokenType = _.toLower(params.tokenType);
95
96
 
96
97
  // @TODO Add support for HMAC
97
- if (tokenType === BEARER) {
98
- // clean conflicting headers and query params
99
- // @todo: we should be able to get conflicting params from auth manifest
100
- // and clear them before the sign step for any auth
101
- request.removeHeader(AUTHORIZATION, {ignoreCase: true});
102
- request.removeQueryParams([ACCESS_TOKEN]);
98
+ if (tokenType === MAC) {
99
+ return done();
100
+ }
103
101
 
104
- if (params.addTokenTo === QUERY_PARAMS) {
105
- request.addQueryParams({
106
- key: ACCESS_TOKEN,
107
- value: params.accessToken,
108
- system: true
109
- });
110
- }
111
- else if (params.addTokenTo === HEADER) {
112
- request.addHeader({
113
- key: AUTHORIZATION,
114
- value: AUTHORIZATION_PREFIX + params.accessToken,
115
- system: true
116
- });
117
- }
102
+ // treat every token types (other than MAC) as bearer token
103
+
104
+ // clean conflicting headers and query params
105
+ // @todo: we should be able to get conflicting params from auth manifest
106
+ // and clear them before the sign step for any auth
107
+ request.removeHeader(AUTHORIZATION, {ignoreCase: true});
108
+ request.removeQueryParams([ACCESS_TOKEN]);
109
+
110
+ if (params.addTokenTo === QUERY_PARAMS) {
111
+ request.addQueryParams({
112
+ key: ACCESS_TOKEN,
113
+ value: params.accessToken,
114
+ system: true
115
+ });
116
+ }
117
+ else if (params.addTokenTo === HEADER) {
118
+ request.addHeader({
119
+ key: AUTHORIZATION,
120
+ value: AUTHORIZATION_PREFIX + params.accessToken,
121
+ system: true
122
+ });
118
123
  }
119
124
 
120
125
  return done();
@@ -251,12 +251,14 @@ module.exports = {
251
251
  self = this,
252
252
  bodyParams,
253
253
  url = request.url && urlEncoder.toNodeUrl(request.url.toString(true)),
254
- isSSL,
254
+ isSSL = _.startsWith(url.protocol, HTTPS),
255
+ isTunnelingProxy = request.proxy && (request.proxy.tunnel || isSSL),
255
256
  reqOption,
256
257
  portNumber,
257
258
  behaviorName,
258
259
  port = url && url.port,
259
- hostname = url && url.hostname && url.hostname.toLowerCase();
260
+ hostname = url && url.hostname && url.hostname.toLowerCase(),
261
+ proxyHostname = request.proxy && request.proxy.host;
260
262
 
261
263
  !defaultOpts && (defaultOpts = {});
262
264
  !protocolProfileBehavior && (protocolProfileBehavior = {});
@@ -268,6 +270,10 @@ module.exports = {
268
270
  hostname = LOCALHOST;
269
271
  }
270
272
 
273
+ if (getTLD(proxyHostname) === LOCALHOST) {
274
+ proxyHostname = LOCALHOST;
275
+ }
276
+
271
277
  options.url = url;
272
278
  options.method = request.method;
273
279
  options.jar = defaultOpts.cookieJar || true;
@@ -334,9 +340,17 @@ module.exports = {
334
340
  self.ensureHeaderExists(options.headers, 'Host', url.host);
335
341
 
336
342
  // override DNS lookup
337
- if (networkOptions.restrictedAddresses || hostname === LOCALHOST || networkOptions.hostLookup) {
338
- isSSL = _.startsWith(request.url.protocol, HTTPS);
339
- portNumber = Number(port) || (isSSL ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT);
343
+ if (networkOptions.restrictedAddresses || hostname === LOCALHOST ||
344
+ (!isTunnelingProxy && proxyHostname === LOCALHOST) || networkOptions.hostLookup) {
345
+ // Use proxy port for localhost resolution in case of non-tunneling proxy
346
+ // because the request will be sent to proxy server by postman-request
347
+ if (request.proxy && !isTunnelingProxy) {
348
+ portNumber = Number(request.proxy.port);
349
+ }
350
+ // Otherwise, use request's port
351
+ else {
352
+ portNumber = Number(port) || (isSSL ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT);
353
+ }
340
354
 
341
355
  _.isFinite(portNumber) && (options.lookup = lookup.bind(this, {
342
356
  port: portNumber,
@@ -428,6 +428,7 @@ module.exports = {
428
428
  timeout: payload.scriptTimeout, // @todo: Expose this as a property in Collection SDK's Script
429
429
  cursor: scriptCursor,
430
430
  context: _.pick(payload.context, SAFE_CONTEXT_VARIABLES),
431
+ serializeLogs: _.get(this, 'options.script.serializeLogs'),
431
432
 
432
433
  // legacy options
433
434
  legacy: {
@@ -489,7 +490,13 @@ module.exports = {
489
490
  result && result.collectionVariables &&
490
491
  (result.collectionVariables = new sdk.VariableScope(result.collectionVariables));
491
492
  result && result.request && (result.request = new sdk.Request(result.request));
492
- result && result.response && (result.response = new sdk.Response(result.response));
493
+
494
+ // @note Since postman-sandbox@3.5.2, response object is not included in the execution result.
495
+ // Refer: https://github.com/postmanlabs/postman-sandbox/pull/512
496
+ // Adding back here to avoid breaking change in `script` callback.
497
+ // @todo revisit script callback args in runtime v8.
498
+ payload.context && payload.context.response &&
499
+ (result.response = new sdk.Response(payload.context.response));
493
500
 
494
501
  // persist the pm.variables for the next script
495
502
  result && result._variables &&
@@ -1,9 +1,10 @@
1
1
  var _ = require('lodash'),
2
2
  uuid = require('uuid'),
3
+ Response = require('postman-collection').Response,
3
4
  visualizer = require('../../visualizer'),
4
5
 
5
6
  /**
6
- * List of request properties which can be mutated via prerequest
7
+ * List of request properties which can be mutated via pre-request
7
8
  *
8
9
  * @private
9
10
  * @const
@@ -11,7 +12,8 @@ var _ = require('lodash'),
11
12
  */
12
13
  ALLOWED_REQUEST_MUTATIONS = ['url', 'method', 'headers'],
13
14
 
14
- extractVisualizerData;
15
+ extractVisualizerData,
16
+ getResponseJSON;
15
17
 
16
18
  /**
17
19
  * Returns visualizer data from the latest execution result.
@@ -36,7 +38,7 @@ extractVisualizerData = function (prereqExecutions, testExecutions) {
36
38
  }
37
39
 
38
40
  if (_.isArray(prereqExecutions)) {
39
- // extraxt visualizer data from pre-request script results if it is not found earlier
41
+ // extract visualizer data from pre-request script results if it is not found earlier
40
42
  for (i = prereqExecutions.length - 1; i >= 0; i--) {
41
43
  visualizerData = _.get(prereqExecutions[i], 'result.return.visualizer');
42
44
 
@@ -47,6 +49,31 @@ extractVisualizerData = function (prereqExecutions, testExecutions) {
47
49
  }
48
50
  };
49
51
 
52
+ /**
53
+ * Convert response into a JSON serializable object.
54
+ * The stream property is converted to base64 string for performance reasons.
55
+ *
56
+ * @param {Object} response - SDK Response instance
57
+ * @returns {Object}
58
+ */
59
+ getResponseJSON = function (response) {
60
+ if (!Response.isResponse(response)) {
61
+ return;
62
+ }
63
+
64
+ return {
65
+ id: response.id,
66
+ code: response.code,
67
+ status: response.status,
68
+ header: response.headers && response.headers.toJSON(),
69
+ stream: response.stream && {
70
+ type: 'Base64',
71
+ data: response.stream.toString('base64')
72
+ },
73
+ responseTime: response.responseTime
74
+ };
75
+ };
76
+
50
77
  /**
51
78
  * Add options
52
79
  * stopOnError:Boolean
@@ -173,7 +200,15 @@ module.exports = {
173
200
 
174
201
  // also the test object requires the updated request object (since auth helpers may modify it)
175
202
  request && (ctxTemplate.request = request);
176
- response && (ctxTemplate.response = response);
203
+
204
+ // @note convert response instance to plain object.
205
+ // we want to avoid calling Response.toJSON() which triggers toJSON on Response.stream buffer.
206
+ // Because that increases the size of stringified object by 3 times.
207
+ // Also, that increases the total number of tokens (buffer.data) whereas Buffer.toString
208
+ // generates a single string that is easier to stringify and sent over the UVM bridge.
209
+ response && (ctxTemplate.response = getResponseJSON(response));
210
+
211
+ // set cookies for this transaction
177
212
  cookies && (ctxTemplate.cookies = cookies);
178
213
 
179
214
  // the context template also has a test object to store assertions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postman-runtime",
3
- "version": "7.19.0",
3
+ "version": "7.21.0",
4
4
  "description": "Underlying library of executing Postman Collections (used by Newman)",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -30,21 +30,20 @@
30
30
  "license": "Apache-2.0",
31
31
  "dependencies": {
32
32
  "async": "2.6.2",
33
- "aws4": "1.8.0",
34
- "btoa": "1.2.1",
33
+ "aws4": "1.9.0",
35
34
  "crypto-js": "3.1.9-1",
36
35
  "eventemitter3": "4.0.0",
37
- "handlebars": "4.4.3",
36
+ "handlebars": "4.5.3",
38
37
  "http-reasons": "0.1.0",
39
38
  "httpntlm": "1.7.6",
40
39
  "inherits": "2.0.4",
41
40
  "lodash": "4.17.15",
42
41
  "node-oauth1": "1.2.2",
43
42
  "performance-now": "2.1.0",
44
- "postman-collection": "3.5.4",
45
- "postman-request": "2.88.1-postman.15",
46
- "postman-sandbox": "3.4.0",
47
- "postman-url-encoder": "1.0.2",
43
+ "postman-collection": "3.5.5",
44
+ "postman-request": "2.88.1-postman.16",
45
+ "postman-sandbox": "3.5.2",
46
+ "postman-url-encoder": "1.0.3",
48
47
  "resolve-from": "5.0.0",
49
48
  "serialised-error": "1.1.3",
50
49
  "tough-cookie": "3.0.1",
@@ -58,21 +57,22 @@
58
57
  "eslint": "5.16.0",
59
58
  "eslint-plugin-jsdoc": "8.7.0",
60
59
  "eslint-plugin-lodash": "5.1.0",
61
- "eslint-plugin-mocha": "6.2.0",
60
+ "eslint-plugin-mocha": "6.2.2",
62
61
  "eslint-plugin-security": "1.4.0",
63
62
  "graphql": "14.5.8",
64
63
  "http-proxy": "1.18.0",
65
64
  "istanbul": "0.4.5",
66
65
  "js-yaml": "3.13.1",
67
66
  "jsdoc": "3.6.3",
68
- "jsdoc-to-markdown": "5.0.2",
69
- "mocha": "6.2.1",
67
+ "jsdoc-to-markdown": "5.0.3",
68
+ "mocha": "6.2.2",
70
69
  "parse-gitignore": "0.5.1",
71
70
  "postman-jsdoc-theme": "0.0.3",
72
71
  "recursive-readdir": "2.2.2",
73
72
  "server-destroy": "1.0.1",
74
73
  "shelljs": "0.8.3",
75
74
  "sinon": "7.5.0",
75
+ "teleport-javascript": "1.0.0",
76
76
  "tmp": "0.1.0",
77
77
  "yankee": "1.0.8"
78
78
  },
@@ -1,11 +1,14 @@
1
1
  const fs = require('fs'),
2
2
  net = require('net'),
3
+ url = require('url'),
4
+ dns = require('dns'),
3
5
  _ = require('lodash'),
4
6
  path = require('path'),
5
7
  http = require('http'),
6
8
  https = require('https'),
7
9
  crypto = require('crypto'),
8
10
  GraphQL = require('graphql'),
11
+ ntlmUtils = require('httpntlm').ntlm,
9
12
  enableServerDestroy = require('server-destroy');
10
13
 
11
14
  /**
@@ -206,6 +209,7 @@ function createHTTPServer () {
206
209
  * @param {Object} [options] - Additional options to configure proxy server
207
210
  * @param {Object} [options.auth] - Proxy authentication, Basic auth
208
211
  * @param {String} [options.agent] - Agent used for http(s).request
212
+ * @param {Boolean} [options.useIPv6] - If true, force using IPv6 address while forwarding request.
209
213
  *
210
214
  * @example
211
215
  * var s = createProxyServer({
@@ -244,14 +248,22 @@ function createProxyServer (options) {
244
248
  req.headers = Object.assign(req.headers, options.headers || {});
245
249
 
246
250
  // forward request to the origin and pipe the response
247
- var fwd = agent.request({
248
- host: req.headers.host,
249
- path: req.url,
250
- method: req.method.toLowerCase(),
251
- headers: req.headers
252
- }, function (resp) {
253
- resp.pipe(res);
254
- });
251
+ var requestUrl = url.parse(req.url),
252
+ fwd = agent.request({
253
+ host: requestUrl.hostname,
254
+ path: requestUrl.path,
255
+ port: requestUrl.port,
256
+ method: req.method.toLowerCase(),
257
+ headers: req.headers,
258
+ lookup: options.useIPv6 && function (hostname, options, callback) {
259
+ !options && (options = {});
260
+ options.family = 6;
261
+
262
+ return dns.lookup(hostname, options, callback);
263
+ }
264
+ }, function (resp) {
265
+ resp.pipe(res);
266
+ });
255
267
 
256
268
  req.pipe(fwd);
257
269
  });
@@ -497,6 +509,106 @@ function createEdgeGridAuthServer (options) {
497
509
  return server;
498
510
  }
499
511
 
512
+ /**
513
+ * Creates an NTLM server.
514
+ *
515
+ * @param {Object} options - The options for the server
516
+ * @param {String} options.username - Username for authentication
517
+ * @param {String} options.password - Password for authentication
518
+ * @param {String} options.domain - Domain name for authentication
519
+ * @param {String} options.workstation - Workstation for authentication
520
+ * @param {Boolean} options.debug - Enable logging of requests
521
+ *
522
+ * @return {Object} - http server
523
+ */
524
+ function createNTLMServer (options) {
525
+ options = options || {};
526
+
527
+ var type2Message = 'NTLM ' +
528
+ 'TlRMTVNTUAACAAAAHgAeADgAAAAFgoqiBevywvJykjAAAAAAAAAAAJgAmABWAAAA' +
529
+ 'CgC6RwAAAA9EAEUAUwBLAFQATwBQAC0ASgBTADQAVQBKAFQARAACAB4ARABFAFMA' +
530
+ 'SwBUAE8AUAAtAEoAUwA0AFUASgBUAEQAAQAeAEQARQBTAEsAVABPAFAALQBKAFMA' +
531
+ 'NABVAEoAVABEAAQAHgBEAEUAUwBLAFQATwBQAC0ASgBTADQAVQBKAFQARAADAB4A' +
532
+ 'RABFAFMASwBUAE8AUAAtAEoAUwA0AFUASgBUAEQABwAIADmguzCHn9UBAAAAAA==',
533
+ parsedType2Message = ntlmUtils.parseType2Message(type2Message, _.noop),
534
+
535
+ username = options.username || 'username',
536
+ password = options.password || 'password',
537
+ domain = options.domain || '',
538
+ workstation = options.workstation || '',
539
+
540
+ type1Message = ntlmUtils.createType1Message({
541
+ domain,
542
+ workstation
543
+ }),
544
+ type3Message = ntlmUtils.createType3Message(parsedType2Message, {
545
+ domain,
546
+ workstation,
547
+ username,
548
+ password
549
+ }),
550
+
551
+ handler = function (req, res) {
552
+ var authHeaders = req.headers.authorization;
553
+
554
+ // send type2 message and ask for type3 message
555
+ if (authHeaders && authHeaders.startsWith(type1Message.slice(0, 20))) {
556
+ res.writeHead(401, {
557
+
558
+ // @note we're sending a 'Negotiate' header here to make
559
+ // sure that runtime can handle it.
560
+ 'www-authenticate': [type2Message, 'Negotiate']
561
+ });
562
+
563
+ options.debug && console.info('401: got type1 message');
564
+ }
565
+
566
+ // successful auth
567
+ // @note we don't check if the username and password are correct
568
+ // because I don't know how.
569
+ else if (authHeaders && authHeaders.startsWith(type3Message.slice(0, 100))) {
570
+ res.writeHead(200);
571
+
572
+ options.debug && console.info('200: got type3 message');
573
+ }
574
+
575
+ // no valid auth headers, ask for type1 message
576
+ else {
577
+ res.writeHead(401, {
578
+ 'www-authenticate': ['NTLM', 'Negotiate']
579
+ });
580
+
581
+ options.debug && console.info('401: got no authorization header');
582
+ }
583
+
584
+ res.end();
585
+ },
586
+ server = http.createServer(handler);
587
+
588
+ enableServerDestroy(server);
589
+
590
+ return server;
591
+ }
592
+
593
+ /**
594
+ * Custom junk bytes response server.
595
+ *
596
+ * `/${bytes}` returns binary response of given bytes size.
597
+ */
598
+ function createBytesServer () {
599
+ var server = http.createServer(function (req, res) {
600
+ var bytes = Number(req.url.substr(1)) || 0; // remove leading /
601
+
602
+ res.writeHead(200);
603
+ res.write(Buffer.alloc(bytes));
604
+ res.end();
605
+ });
606
+
607
+ enableServerDestroy(server);
608
+
609
+ return server;
610
+ }
611
+
500
612
  module.exports = {
501
613
  createSSLServer,
502
614
  createHTTPServer,
@@ -504,5 +616,7 @@ module.exports = {
504
616
  createRawEchoServer,
505
617
  createGraphQLServer,
506
618
  createRedirectServer,
507
- createEdgeGridAuthServer
619
+ createEdgeGridAuthServer,
620
+ createNTLMServer,
621
+ createBytesServer
508
622
  };