postman-runtime 7.37.3 → 7.39.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.
@@ -226,6 +226,21 @@ function computeBodyHash (body, algorithm, digestEncoding, callback) {
226
226
  return callback();
227
227
  }
228
228
 
229
+ /**
230
+ * increase nonce count by 1
231
+ *
232
+ * @param {String} nonceCount - nonce count
233
+ */
234
+ function increaseNonceCount (nonceCount) {
235
+ const nc = _.parseInt(nonceCount);
236
+
237
+ if (_.isNaN(nc)) {
238
+ return ONE;
239
+ }
240
+
241
+ return _.padStart((nc + 1).toString(), ONE.length, '0');
242
+ }
243
+
229
244
  /**
230
245
  * All the auth definition parameters excluding username and password should be stored and resued.
231
246
  *
@@ -298,6 +313,7 @@ module.exports = {
298
313
  }
299
314
 
300
315
  var code,
316
+ nonceCount,
301
317
  realm,
302
318
  nonce,
303
319
  qop,
@@ -306,6 +322,7 @@ module.exports = {
306
322
  authParams = {};
307
323
 
308
324
  code = response.code;
325
+ nonceCount = auth.get('nonceCount');
309
326
  authHeader = _getDigestAuthHeader(response.headers);
310
327
 
311
328
  // If code is forbidden or unauthorized, and an auth header exists,
@@ -337,6 +354,8 @@ module.exports = {
337
354
  return done(null, false);
338
355
  }
339
356
 
357
+ auth.set('nonceCount', increaseNonceCount(nonceCount));
358
+
340
359
  done(null, true);
341
360
  },
342
361
 
@@ -88,15 +88,37 @@ var _ = require('lodash'),
88
88
  return encrypt.digest('base64');
89
89
  },
90
90
 
91
+ /**
92
+ * Trim the body if it exceeds the max body limit.
93
+ *
94
+ * @param {String|Buffer} body Request body
95
+ * @param {Number} size Maximum body size to hash
96
+ */
97
+ limitRequestBody = function (body, size) {
98
+ if (!body || size <= 0) { return EMPTY; }
99
+
100
+ if (!Buffer.isBuffer(body)) {
101
+ // We are converting the body to a buffer to obtain the exact byte length and to precisely trim the body.
102
+ body = Buffer.from(body);
103
+ }
104
+
105
+ if (body.byteLength > size) {
106
+ body = body.subarray(0, size);
107
+ }
108
+
109
+ return body;
110
+ },
111
+
91
112
  /**
92
113
  * Calculates body hash with given algorithm and digestEncoding.
93
114
  *
94
115
  * @param {RequestBody} body Request body
95
116
  * @param {String} algorithm Hash algorithm to use
96
117
  * @param {String} digestEncoding Encoding of the hash
118
+ * @param {Number} maxBodySize Maximum body size to hash
97
119
  * @param {Function} callback Callback function that will be called with body hash
98
120
  */
99
- computeBodyHash = function (body, algorithm, digestEncoding, callback) {
121
+ computeBodyHash = function (body, algorithm, digestEncoding, maxBodySize, callback) {
100
122
  if (!(body && algorithm && digestEncoding) || body.isEmpty()) { return callback(); }
101
123
 
102
124
  var hash = crypto.createHash(algorithm),
@@ -107,7 +129,7 @@ var _ = require('lodash'),
107
129
 
108
130
  if (body.mode === RequestBody.MODES.raw) {
109
131
  rawBody = bodyBuilder.raw(body.raw).body;
110
- hash.update(rawBody);
132
+ hash.update(limitRequestBody(rawBody, maxBodySize));
111
133
 
112
134
  return callback(hash.digest(digestEncoding));
113
135
  }
@@ -115,7 +137,7 @@ var _ = require('lodash'),
115
137
  if (body.mode === RequestBody.MODES.urlencoded) {
116
138
  urlencodedBody = bodyBuilder.urlencoded(body.urlencoded).form;
117
139
  urlencodedBody = urlEncoder.encodeQueryString(urlencodedBody);
118
- hash.update(urlencodedBody);
140
+ hash.update(limitRequestBody(urlencodedBody, maxBodySize));
119
141
 
120
142
  return callback(hash.digest(digestEncoding));
121
143
  }
@@ -129,9 +151,13 @@ var _ = require('lodash'),
129
151
 
130
152
  return originalReadStream.cloneReadStream(function (err, clonedStream) {
131
153
  if (err) { return callback(); }
154
+ let readBytes = 0;
132
155
 
133
156
  clonedStream.on('data', function (chunk) {
134
- hash.update(chunk);
157
+ if (readBytes <= maxBodySize) {
158
+ hash.update(limitRequestBody(chunk, maxBodySize - readBytes));
159
+ readBytes += chunk.byteLength;
160
+ }
135
161
  });
136
162
 
137
163
  clonedStream.on('end', function () {
@@ -142,7 +168,7 @@ var _ = require('lodash'),
142
168
 
143
169
  if (body.mode === RequestBody.MODES.graphql) {
144
170
  graphqlBody = bodyBuilder.graphql(body.graphql).body;
145
- hash.update(graphqlBody);
171
+ hash.update(limitRequestBody(graphqlBody, maxBodySize));
146
172
 
147
173
  return callback(hash.digest(digestEncoding));
148
174
  }
@@ -257,6 +283,7 @@ module.exports = {
257
283
  sign: function (auth, request, done) {
258
284
  var params = auth.get([
259
285
  'accessToken',
286
+ 'maxBodySize',
260
287
  'clientToken',
261
288
  'clientSecret',
262
289
  'baseURL',
@@ -279,6 +306,7 @@ module.exports = {
279
306
  params.timestamp = params.timestamp || getTimestamp();
280
307
  params.url = url;
281
308
  params.method = request.method;
309
+ params.maxBodySize = params.maxBodySize || 131072; // 128 KB
282
310
 
283
311
  // ensure that headers are case-insensitive as specified in the documentation
284
312
  params.headers = request.getHeaders({ enabled: true, ignoreCase: true });
@@ -292,7 +320,7 @@ module.exports = {
292
320
 
293
321
  // only calculate body hash for POST requests according to specification
294
322
  if (request.method === 'POST') {
295
- return computeBodyHash(request.body, 'sha256', 'base64', function (bodyHash) {
323
+ return computeBodyHash(request.body, 'sha256', 'base64', params.maxBodySize, function (bodyHash) {
296
324
  params.bodyHash = bodyHash;
297
325
 
298
326
  request.addHeader({
@@ -150,7 +150,10 @@ module.exports = {
150
150
  * @param {AuthHandlerInterface~authPreHookCallback} done -
151
151
  */
152
152
  pre: function (auth, done) {
153
- !auth.get(STATE) && auth.set(STATE, STATES.INITIALIZED);
153
+ if (!auth.get(STATE)) {
154
+ auth.set(STATE, STATES.INITIALIZED);
155
+ auth.set(NTLM_HEADER, undefined);
156
+ }
154
157
 
155
158
  done(null, true);
156
159
  },
@@ -176,10 +179,18 @@ module.exports = {
176
179
  challengeMessage, // type 2
177
180
  authenticateMessage, // type 3
178
181
  ntlmType2Header,
179
- parsedParameters;
182
+ parsedParameters,
183
+
184
+ // resets the state and NTLM header and exits replay loop
185
+ resetStateAndStop = function (err) {
186
+ auth.set(STATE, STATES.INITIALIZED);
187
+ auth.set(NTLM_HEADER, undefined);
188
+
189
+ return done(err || null, true);
190
+ };
180
191
 
181
192
  if (response.code !== 401 && response.code !== 403) {
182
- return done(null, true);
193
+ return resetStateAndStop();
183
194
  }
184
195
 
185
196
  // we try to extract domain from username if not specified.
@@ -193,7 +204,7 @@ module.exports = {
193
204
  if (state === STATES.INITIALIZED) {
194
205
  // Nothing to do if the server does not ask us for auth in the first place.
195
206
  if (!hasNTLMChallenge(response.headers)) {
196
- return done(null, true);
207
+ return resetStateAndStop();
197
208
  }
198
209
 
199
210
  // Create a type 1 message to send to the server
@@ -223,13 +234,13 @@ module.exports = {
223
234
  });
224
235
 
225
236
  if (!ntlmType2Header) {
226
- return done(new Error('ntlm: server did not send NTLM type 2 message'));
237
+ return resetStateAndStop(new Error('ntlm: server did not send NTLM type 2 message'));
227
238
  }
228
239
 
229
240
  challengeMessage = ntlmUtil.parseType2Message(ntlmType2Header.valueOf(), _.noop);
230
241
 
231
242
  if (!challengeMessage) {
232
- return done(new Error('ntlm: server did not correctly process authentication request'));
243
+ return resetStateAndStop(new Error('ntlm: server did not correctly process authentication request'));
233
244
  }
234
245
 
235
246
  authenticateMessage = ntlmUtil.createType3Message(challengeMessage, {
@@ -248,11 +259,11 @@ module.exports = {
248
259
  }
249
260
  else if (state === STATES.T3_MSG_CREATED) {
250
261
  // Means we have tried to authenticate, so we should stop here without worrying about anything
251
- return done(null, true);
262
+ return resetStateAndStop();
252
263
  }
253
264
 
254
265
  // We are in an undefined state
255
- return done(null, true);
266
+ return resetStateAndStop();
256
267
  },
257
268
 
258
269
  /**
@@ -7,7 +7,7 @@ var _ = require('lodash'),
7
7
  */
8
8
  FUNCTION = 'function',
9
9
 
10
- SAFE_CONTEXT_PROPERTIES = ['replayState', 'coords'];
10
+ SAFE_CONTEXT_PROPERTIES = ['replayState', 'coords', 'originalItem'];
11
11
 
12
12
  /**
13
13
  * Creates a context object to be used with `http-request.command` extension.
@@ -31,7 +31,7 @@ module.exports = function (payload, defaults) {
31
31
  !context.coords && (context.coords = payload.coords);
32
32
 
33
33
  // save original item for reference
34
- context.originalItem = payload.item;
34
+ context.originalItem = context.originalItem || payload.item;
35
35
 
36
36
  // we clone item from the payload, so that we can make any changes we need there, without mutating the
37
37
  // collection
@@ -83,7 +83,7 @@ module.exports = {
83
83
 
84
84
  // Helper function to call the afterRequest trigger.
85
85
  afterRequest = function (err, response, request, cookies, history) {
86
- self.triggers.request(err, context.coords, response, request, payload.item, cookies, history);
86
+ self.triggers.request(err, context.coords, response, request, context.item, cookies, history);
87
87
  };
88
88
 
89
89
  // Ensure that this is called.
@@ -159,7 +159,7 @@ module.exports = {
159
159
  var nextPayload = {
160
160
  response: res,
161
161
  request: req,
162
- item: context.originalItem,
162
+ item: context.item,
163
163
  cookies: cookies,
164
164
  coords: context.coords,
165
165
  history: history
@@ -188,9 +188,17 @@ module.exports = {
188
188
  // replay controller invokes callback no. 1 when replaying the request
189
189
  // invokes callback no. 2 when replay count has exceeded maximum limit
190
190
  // @note: errors in replayed requests are passed to callback no. 1
191
+ // @note: we are adding variables list to resolve variables after new context is created
191
192
  return replayController.requestReplay(context,
192
193
  context.item,
193
- { source: replayOptions.helper },
194
+ { source: replayOptions.helper, ..._.pick(payload, [
195
+ '_variables',
196
+ 'data',
197
+ 'environment',
198
+ 'collectionVariables',
199
+ 'globals',
200
+ 'vaultSecrets'
201
+ ]) },
194
202
  // new payload with response from replay is sent to `next`
195
203
  function (err, payloadFromReplay) { safeNext(err, payloadFromReplay); },
196
204
  // replay was stopped, move on with older payload
@@ -1,87 +1,7 @@
1
1
  var _ = require('lodash'),
2
- sdk = require('postman-collection'),
3
2
  createItemContext = require('../create-item-context'),
3
+ { resolveVariables } = require('../util');
4
4
 
5
- /**
6
- * Resolve variables in item and auth in context.
7
- *
8
- * @param {ItemContext} context -
9
- * @param {Item} [context.item] -
10
- * @param {RequestAuth} [context.auth] -
11
- * @param {Object} payload -
12
- * @param {VariableScope} payload._variables -
13
- * @param {Object} payload.data -
14
- * @param {VariableScope} payload.environment -
15
- * @param {VariableScope} payload.collectionVariables -
16
- * @param {VariableScope} payload.globals -
17
- * @param {VariableScope} payload.vaultSecrets -
18
- */
19
- resolveVariables = function (context, payload) {
20
- if (!(context.item && context.item.request)) { return; }
21
-
22
- // @todo - resolve variables in a more graceful way
23
- var variableDefinitions = [
24
- // extract the variable list from variable scopes
25
- // @note: this is the order of precedence for variable resolution - don't change it
26
- payload._variables.values,
27
- payload.data,
28
- payload.environment.values,
29
- payload.collectionVariables.values,
30
- payload.globals.values
31
- // @note vault variables are added later
32
- ],
33
-
34
- hasVaultSecrets = payload.vaultSecrets.values.count() > 0,
35
-
36
- urlObj = context.item.request.url,
37
- // @note URL string is used to resolve nested variables as URL parser doesn't support them well.
38
- urlString = urlObj.toString(),
39
- unresolvedUrlString = urlString,
40
-
41
- vaultVariables,
42
- vaultUrl,
43
- item,
44
- auth;
45
-
46
- if (hasVaultSecrets) {
47
- // get the vault variables that match the unresolved URL string
48
- vaultUrl = urlObj.protocol ? urlString : `http://${urlString}`; // force protocol
49
- vaultVariables = payload.vaultSecrets.__getMatchingVariables(vaultUrl);
50
-
51
- // resolve variables in URL string with initial vault variables
52
- urlString = sdk.Property.replaceSubstitutions(urlString, [...variableDefinitions, vaultVariables]);
53
-
54
- if (urlString !== unresolvedUrlString) {
55
- // get the final list of vault variables that match the resolved URL string
56
- vaultUrl = new sdk.Url(urlString).toString(true);
57
- vaultVariables = payload.vaultSecrets.__getMatchingVariables(vaultUrl);
58
-
59
- // resolve vault variables in URL string
60
- // @note other variable scopes are skipped as they are already resolved
61
- urlString = sdk.Property.replaceSubstitutions(urlString, [vaultVariables]);
62
- }
63
-
64
- // add vault variables to the list of variable definitions
65
- variableDefinitions.push(vaultVariables);
66
- }
67
- else if (urlString) {
68
- urlString = sdk.Property.replaceSubstitutions(urlString, variableDefinitions);
69
- }
70
-
71
- // @todo - no need to sync variables when SDK starts supporting resolution from scope directly
72
- // @todo - avoid resolving the entire item as this unnecessarily resolves URL
73
- item = context.item = new sdk.Item(context.item.toObjectResolved(null,
74
- variableDefinitions, { ignoreOwnVariables: true }));
75
-
76
- // re-parse and update the URL from the resolved string
77
- urlString && (item.request.url = new sdk.Url(urlString));
78
-
79
- auth = context.auth;
80
-
81
- // resolve variables in auth
82
- auth && (context.auth = new sdk.RequestAuth(auth.toObjectResolved(null,
83
- variableDefinitions, { ignoreOwnVariables: true })));
84
- };
85
5
 
86
6
  module.exports = {
87
7
  init: function (done) {
@@ -1,5 +1,6 @@
1
1
  var _ = require('lodash'),
2
2
  createItemContext = require('./create-item-context'),
3
+ { resolveVariables } = require('./util'),
3
4
 
4
5
  // total number of replays allowed
5
6
  MAX_REPLAY_COUNT = 3,
@@ -54,6 +55,8 @@ _.assign(ReplayController.prototype, /** @lends ReplayController.prototype */{
54
55
  // create item context from the new item
55
56
  payload.context = createItemContext(payload, context);
56
57
 
58
+ resolveVariables(payload.context, payload);
59
+
57
60
  this.run.immediate('httprequest', payload)
58
61
  .done(function (response) {
59
62
  success(null, response);
@@ -314,15 +314,18 @@ module.exports = [
314
314
 
315
315
  // prepare for sending intermediate request
316
316
  var replayController = new ReplayController(context.replayState, run),
317
- item = new sdk.Item({ request });
317
+ item = new sdk.Item({ request }),
318
+ originalItem = context.originalItem;
318
319
 
319
320
  // auth handler gave a no go, and an intermediate request.
320
321
  // make the intermediate request the response is passed to `init` hook
321
- replayController.requestReplay(context,
322
+ // we are overriding the originalItem because in pre we have a entire new request
323
+ replayController.requestReplay(_.assign(context, { originalItem: item }),
322
324
  item,
323
325
  // marks the auth as source for intermediate request
324
326
  { source: auth.type + DOT_AUTH },
325
327
  function (err, response) {
328
+ context.originalItem = originalItem; // reset the original item
326
329
  // errors for intermediate requests are passed to request callback
327
330
  // passing it here will add it to original request as well, so don't do it
328
331
  if (err) { return done(); }
@@ -343,6 +346,7 @@ module.exports = [
343
346
  });
344
347
  },
345
348
  function (err) {
349
+ context.originalItem = originalItem; // reset the original item
346
350
  // warn users that maximum retries have exceeded
347
351
  if (err) {
348
352
  run.triggers.console(context.coords,
@@ -1,4 +1,6 @@
1
1
  var { Url, UrlMatchPatternList, VariableList } = require('postman-collection'),
2
+ sdk = require('postman-collection'),
3
+ _ = require('lodash'),
2
4
 
3
5
  /**
4
6
  * @const
@@ -234,5 +236,87 @@ module.exports = {
234
236
 
235
237
  // mark the scope as a vault variable scope
236
238
  scope.__vaultVariableScope = true;
239
+ },
240
+
241
+ /**
242
+ * Resolve variables in item and auth in context.
243
+ *
244
+ * @param {ItemContext} context -
245
+ * @param {Item} [context.item] -
246
+ * @param {RequestAuth} [context.auth] -
247
+ * @param {Object} payload -
248
+ * @param {VariableScope} payload._variables -
249
+ * @param {Object} payload.data -
250
+ * @param {VariableScope} payload.environment -
251
+ * @param {VariableScope} payload.collectionVariables -
252
+ * @param {VariableScope} payload.globals -
253
+ * @param {VariableScope} payload.vaultSecrets -
254
+ */
255
+ resolveVariables (context, payload) {
256
+ if (!(context.item && context.item.request)) { return; }
257
+
258
+ // @todo - resolve variables in a more graceful way
259
+ var variableDefinitions = [
260
+ // extract the variable list from variable scopes
261
+ // @note: this is the order of precedence for variable resolution - don't change it
262
+ _.get(payload, '_variables.values', []),
263
+ _.get(payload, 'data', []),
264
+ _.get(payload, 'environment.values', []),
265
+ _.get(payload, 'collectionVariables.values', []),
266
+ _.get(payload, 'globals.values', [])
267
+ // @note vault variables are added later
268
+ ],
269
+ vaultValues = _.get(payload, 'vaultSecrets.values'),
270
+
271
+ hasVaultSecrets = vaultValues ? vaultValues.count() > 0 : false,
272
+
273
+ urlObj = context.item.request.url,
274
+ // @note URL string is used to resolve nested variables as URL parser doesn't support them well.
275
+ urlString = urlObj.toString(),
276
+ unresolvedUrlString = urlString,
277
+
278
+ vaultVariables,
279
+ vaultUrl,
280
+ item,
281
+ auth;
282
+
283
+ if (hasVaultSecrets) {
284
+ // get the vault variables that match the unresolved URL string
285
+ vaultUrl = urlObj.protocol ? urlString : `http://${urlString}`; // force protocol
286
+ vaultVariables = payload.vaultSecrets.__getMatchingVariables(vaultUrl);
287
+
288
+ // resolve variables in URL string with initial vault variables
289
+ urlString = sdk.Property.replaceSubstitutions(urlString, [...variableDefinitions, vaultVariables]);
290
+
291
+ if (urlString !== unresolvedUrlString) {
292
+ // get the final list of vault variables that match the resolved URL string
293
+ vaultUrl = new sdk.Url(urlString).toString(true);
294
+ vaultVariables = payload.vaultSecrets.__getMatchingVariables(vaultUrl);
295
+
296
+ // resolve vault variables in URL string
297
+ // @note other variable scopes are skipped as they are already resolved
298
+ urlString = sdk.Property.replaceSubstitutions(urlString, [vaultVariables]);
299
+ }
300
+
301
+ // add vault variables to the list of variable definitions
302
+ variableDefinitions.push(vaultVariables);
303
+ }
304
+ else if (urlString) {
305
+ urlString = sdk.Property.replaceSubstitutions(urlString, variableDefinitions);
306
+ }
307
+
308
+ // @todo - no need to sync variables when SDK starts supporting resolution from scope directly
309
+ // @todo - avoid resolving the entire item as this unnecessarily resolves URL
310
+ item = context.item = new sdk.Item(context.item.toObjectResolved(null,
311
+ variableDefinitions, { ignoreOwnVariables: true }));
312
+
313
+ // re-parse and update the URL from the resolved string
314
+ urlString && (item.request.url = new sdk.Url(urlString));
315
+
316
+ auth = context.auth;
317
+
318
+ // resolve variables in auth
319
+ auth && (context.auth = new sdk.RequestAuth(auth.toObjectResolved(null,
320
+ variableDefinitions, { ignoreOwnVariables: true })));
237
321
  }
238
322
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postman-runtime",
3
- "version": "7.37.3",
3
+ "version": "7.39.0",
4
4
  "description": "Underlying library of executing Postman Collections",
5
5
  "author": "Postman Inc.",
6
6
  "license": "Apache-2.0",
@@ -56,7 +56,7 @@
56
56
  "performance-now": "2.1.0",
57
57
  "postman-collection": "4.4.0",
58
58
  "postman-request": "2.88.1-postman.33",
59
- "postman-sandbox": "4.6.2",
59
+ "postman-sandbox": "4.7.1",
60
60
  "postman-url-encoder": "3.0.5",
61
61
  "serialised-error": "1.1.3",
62
62
  "strip-json-comments": "3.1.1",
@@ -93,7 +93,7 @@
93
93
  "shelljs": "^0.8.5",
94
94
  "sinon": "^12.0.1",
95
95
  "teleport-javascript": "^1.0.0",
96
- "terser": "^5.30.0",
96
+ "terser": "^5.30.2",
97
97
  "tmp": "^0.2.3",
98
98
  "webpack": "^5.91.0",
99
99
  "yankee": "^1.0.8"