postman-runtime 7.32.3 → 7.34.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.
@@ -0,0 +1,253 @@
1
+ var jose = require('jose'),
2
+ uuid = require('uuid'),
3
+ nodeForge = require('node-forge'),
4
+ AUTHORIZATION = 'Authorization',
5
+ AUTHORIZATION_PREFIX = 'Bearer ',
6
+ ASAP_PARAMETERS = [
7
+ 'alg',
8
+ 'kid',
9
+ 'iss',
10
+ 'exp',
11
+ 'aud',
12
+ 'sub',
13
+ 'privateKey',
14
+ 'claims'
15
+ ],
16
+ DEFAULT_EXPIRY = 3600,
17
+ DEFAULT_ALGORITHM = 'RS256',
18
+ // No synchronous algorithms supported in ASAP, Ref:
19
+ // https://s2sauth.bitbucket.io/spec/#overview
20
+ ALGORITHMS_SUPPORTED = {
21
+ RS256: 'RS256',
22
+ RS384: 'RS384',
23
+ RS512: 'RS512',
24
+ PS256: 'PS256',
25
+ PS384: 'PS384',
26
+ PS512: 'PS512',
27
+ ES256: 'ES256',
28
+ ES384: 'ES384',
29
+ ES512: 'ES512'
30
+ },
31
+
32
+ // eslint-disable-next-line no-useless-escape
33
+ DATA_URI_PATTERN = /^data:application\/pkcs8;kid=([\w.\-\+/]+);base64,([a-zA-Z0-9+/=]+)$/;
34
+
35
+ function removeNewlines (str) {
36
+ return str.replace(/\n/g, '');
37
+ }
38
+
39
+
40
+ function trimStringDoubleQuotes (str) {
41
+ return str.replace(/^"(.*)"$/, '$1');
42
+ }
43
+
44
+ function parsePrivateKey (keyId, privateKey) {
45
+ privateKey = trimStringDoubleQuotes(privateKey);
46
+ var uriDecodedPrivateKey = decodeURIComponent(privateKey),
47
+ match,
48
+ privateKeyDerBuffer,
49
+ privateKeyAsn1,
50
+ privateKeyInfo,
51
+ pkcs1pem,
52
+ base64pkcs1,
53
+ privateKeyInfoPKCS1,
54
+ privateKeyInfoPKCS8,
55
+ asn1pkcs1privateKey,
56
+ pkcs8pem;
57
+
58
+ if (!uriDecodedPrivateKey.startsWith('data:')) {
59
+ return uriDecodedPrivateKey;
60
+ }
61
+
62
+ match = DATA_URI_PATTERN.exec(uriDecodedPrivateKey);
63
+
64
+ if (!match) {
65
+ throw new Error('Malformed Data URI');
66
+ }
67
+
68
+ if (keyId !== match[1]) {
69
+ throw new Error('Supplied key id does not match the one included in data uri.');
70
+ }
71
+
72
+ // Convert DER to PEM if needed
73
+ // Create a private key from the DER buffer
74
+ privateKeyDerBuffer = nodeForge.util.decode64(match[2]);
75
+ privateKeyAsn1 = nodeForge.asn1.fromDer(privateKeyDerBuffer);
76
+ privateKeyInfo = nodeForge.pki.privateKeyFromAsn1(privateKeyAsn1);
77
+ pkcs1pem = nodeForge.pki.privateKeyToPem(privateKeyInfo);
78
+ base64pkcs1 = pkcs1pem.toString('base64').trim();
79
+
80
+ // convert the PKCS#1 key generated to PKCS#8 format
81
+ privateKeyInfoPKCS1 = nodeForge.pki.privateKeyFromPem(base64pkcs1);
82
+ asn1pkcs1privateKey = nodeForge.pki.privateKeyToAsn1(privateKeyInfoPKCS1);
83
+ privateKeyInfoPKCS8 = nodeForge.pki.wrapRsaPrivateKey(asn1pkcs1privateKey);
84
+ pkcs8pem = nodeForge.pki.privateKeyInfoToPem(privateKeyInfoPKCS8);
85
+
86
+ return pkcs8pem.toString('base64').trim();
87
+ }
88
+
89
+ /**
90
+ * @implements {AuthHandlerInterface}
91
+ */
92
+ module.exports = {
93
+ /**
94
+ * @property {AuthHandlerInterface~AuthManifest}
95
+ */
96
+ manifest: {
97
+ info: {
98
+ name: 'asap',
99
+ version: '1.0.0'
100
+ },
101
+ updates: [
102
+ {
103
+ property: AUTHORIZATION,
104
+ type: 'header'
105
+ }
106
+ ]
107
+ },
108
+
109
+ /**
110
+ * Initializes an item (extracts parameters from intermediate requests if any, etc)
111
+ * before the actual authorization step.
112
+ *
113
+ * @param {AuthInterface} auth -
114
+ * @param {Response} response -
115
+ * @param {AuthHandlerInterface~authInitHookCallback} done -
116
+ */
117
+ init: function (auth, response, done) {
118
+ done(null);
119
+ },
120
+
121
+ /**
122
+ * Verifies whether the request has valid basic auth credentials (which is always).
123
+ * Sanitizes the auth parameters if needed.
124
+ *
125
+ * @param {AuthInterface} auth -
126
+ * @param {AuthHandlerInterface~authPreHookCallback} done -
127
+ */
128
+ pre: function (auth, done) {
129
+ done(null, true);
130
+ },
131
+
132
+ /**
133
+ * Verifies whether the basic auth succeeded.
134
+ *
135
+ * @param {AuthInterface} auth -
136
+ * @param {Response} response -
137
+ * @param {AuthHandlerInterface~authPostHookCallback} done -
138
+ */
139
+ post: function (auth, response, done) {
140
+ done(null, true);
141
+ },
142
+
143
+ /**
144
+ * Signs a request.
145
+ *
146
+ * @param {AuthInterface} auth -
147
+ * @param {Request} request -
148
+ * @param {AuthHandlerInterface~authSignHookCallback} done -
149
+ */
150
+ sign: function (auth, request, done) {
151
+ var params = auth.get(ASAP_PARAMETERS),
152
+ claims = params.claims || {},
153
+ issuer,
154
+ subject,
155
+ audience,
156
+ jwtTokenId,
157
+ currentTimeStamp,
158
+ issuedAt,
159
+ expiry,
160
+ expiryTimestamp,
161
+ privateKey,
162
+ kid,
163
+ alg;
164
+
165
+ if (typeof claims === 'string') {
166
+ const trimmedClaims = claims.trim();
167
+
168
+ try {
169
+ claims = trimmedClaims && JSON.parse(trimmedClaims);
170
+ }
171
+ catch (err) {
172
+ return done(new Error('Failed to parse claims'));
173
+ }
174
+ }
175
+
176
+ // Give priority to the claims object, if present
177
+ issuer = claims.iss || params.iss;
178
+
179
+ // Atlassian wants subject to fall back to issuer if not present
180
+ subject = claims.sub || params.sub || issuer;
181
+ audience = claims.aud || params.aud;
182
+
183
+ // Default to a uuid, this is mandatory in ASAP
184
+ jwtTokenId = claims.jti || uuid.v4();
185
+ currentTimeStamp = Math.floor(Date.now() / 1000);
186
+ issuedAt = claims.iat || currentTimeStamp;
187
+
188
+ // Check if expiry is present in claims or params, parse it to int
189
+ expiry = (params.exp && parseInt(params.exp, 10)) ||
190
+ DEFAULT_EXPIRY;
191
+
192
+ // Allow overriding expiry timestamp in claims. The ASAP and JWT specs
193
+ // mention exp to be a timestamp rather than a duration, so we let the
194
+ // override pass through.
195
+ expiryTimestamp = (claims.exp && parseInt(claims.exp, 10)) ||
196
+ currentTimeStamp + expiry;
197
+ privateKey = params.privateKey;
198
+ kid = claims.kid || params.kid;
199
+
200
+ // Atlassian's internal tool for generating keys uses RS256 by default
201
+ alg = params.alg || DEFAULT_ALGORITHM;
202
+
203
+ // Validation
204
+ if (!kid || !issuer || !audience || !jwtTokenId || !privateKey || !kid) {
205
+ return done(new Error('One or more of required claims missing'));
206
+ }
207
+
208
+ if (!ALGORITHMS_SUPPORTED[alg]) {
209
+ return done(new Error('invalid algorithm'));
210
+ }
211
+
212
+ if (typeof privateKey !== 'string') {
213
+ return done(new Error('privateKey must be a string'));
214
+ }
215
+
216
+ try {
217
+ privateKey = removeNewlines(privateKey);
218
+ privateKey = parsePrivateKey(kid, privateKey);
219
+ }
220
+ catch (err) {
221
+ return done(new Error('Failed to parse private key.'));
222
+ }
223
+
224
+ jose.importPKCS8(privateKey, alg)
225
+ .then((signKey) => {
226
+ return new jose.SignJWT(claims)
227
+ .setProtectedHeader({ alg, kid })
228
+
229
+ // This will be system generated if not present
230
+ .setIssuedAt(issuedAt)
231
+ .setIssuer(issuer)
232
+ .setSubject(subject)
233
+ .setJti(jwtTokenId)
234
+ .setAudience(audience)
235
+ .setExpirationTime(expiryTimestamp)
236
+ .sign(signKey);
237
+ })
238
+ .then((token) => {
239
+ request.removeHeader(AUTHORIZATION, { ignoreCase: true });
240
+
241
+ request.addHeader({
242
+ key: AUTHORIZATION,
243
+ value: AUTHORIZATION_PREFIX + token,
244
+ system: true
245
+ });
246
+
247
+ return done();
248
+ })
249
+ .catch(() => {
250
+ done(new Error('Failed to sign request with key.'));
251
+ });
252
+ }
253
+ };
@@ -86,6 +86,7 @@ _.forEach({
86
86
  hawk: require('./hawk'),
87
87
  oauth1: require('./oauth1'),
88
88
  oauth2: require('./oauth2'),
89
+ asap: require('./asap'),
89
90
  ntlm: require('./ntlm'),
90
91
  apikey: require('./apikey'),
91
92
  edgegrid: require('./edgegrid'),
@@ -154,18 +154,13 @@ formDataBodyReducer = function (data, param) {
154
154
  formParam.value = E; // make sure value is not null/undefined ever
155
155
  }
156
156
 
157
- // if data has a truthy content type, we mutate the value to take the options. we are assuming that
158
- // blank string will not be considered as an accepted content type.
159
157
  if (param.contentType && typeof param.contentType === STRING) {
160
158
  (options || (options = {})).contentType = param.contentType;
161
159
  }
162
160
 
163
- // additionally parse the file name and length if sent
164
- // @note: Add support for fileName & fileLength option in Schema & SDK.
165
- // The filepath property overrides filename and may contain a relative path.
166
- if (typeof param.fileName === STRING) { (options || (options = {})).filename = param.fileName; }
167
- if (typeof param.fileLength === 'number') { (options || (options = {})).knownLength = param.fileLength; }
168
-
161
+ if (typeof param.fileName === STRING) {
162
+ (options || (options = {})).filename = param.fileName;
163
+ }
169
164
 
170
165
  // if options were set, add them to formParam
171
166
  options && (formParam.options = options);
@@ -229,7 +229,10 @@ var dns = require('dns'),
229
229
  // returning error synchronously causes uncaught error because listeners are not attached to error events
230
230
  // on socket yet
231
231
  return setImmediate(function () {
232
- callback(null, resolvedAddr, resolvedFamily);
232
+ // when options.all is set, the callback expects an array of addresses
233
+ return options.all ?
234
+ callback(null, [{ address: resolvedAddr, family: resolvedFamily }]) :
235
+ callback(null, resolvedAddr, resolvedFamily);
233
236
  });
234
237
  }
235
238
 
@@ -289,20 +292,46 @@ var dns = require('dns'),
289
292
  // - localhost
290
293
  // - *.localhost
291
294
  if (getTLD(lowercaseHost) !== LOCALHOST) {
295
+ // when options.all is set, the callback expects an array of addresses
296
+ if (options.all) {
297
+ return _lookup(options, hostLookup, lowercaseHost, function (err, addresses) {
298
+ if (err) { return callback(err); }
299
+
300
+ // error out if any of the resolved addresses are restricted
301
+ if (_.some(addresses, (addr) => {
302
+ return self.isAddressRestricted(addr && addr.address, networkOpts);
303
+ })) {
304
+ return callback(new Error(ERROR_ADDRESS_RESOLVE + hostname));
305
+ }
306
+
307
+ return callback(null, addresses);
308
+ });
309
+ }
310
+
292
311
  return _lookup(options, hostLookup, lowercaseHost, function (err, addr, family) {
293
312
  if (err) { return callback(err); }
294
313
 
295
- return callback(self.isAddressRestricted(addr, networkOpts) ?
296
- new Error(ERROR_ADDRESS_RESOLVE + hostname) : null, addr, family);
314
+ // error out if the resolved address is restricted
315
+ if (self.isAddressRestricted(addr, networkOpts)) {
316
+ return callback(new Error(ERROR_ADDRESS_RESOLVE + hostname));
317
+ }
318
+
319
+ return callback(null, addr, family);
297
320
  });
298
321
  }
299
322
 
300
323
  // Try checking if we can connect to IPv6 localhost ('::1')
301
324
  connect(LOCAL_IPV6, lookupOptions.port, function (err) {
302
325
  // use IPv4 if we cannot connect to IPv6
303
- if (err) { return callback(null, LOCAL_IPV4, 4); }
326
+ if (err) {
327
+ return options.all ?
328
+ callback(null, [{ address: LOCAL_IPV4, family: 4 }]) :
329
+ callback(null, LOCAL_IPV4, 4);
330
+ }
304
331
 
305
- callback(null, LOCAL_IPV6, 6);
332
+ return options.all ?
333
+ callback(null, [{ address: LOCAL_IPV6, family: 6 }]) :
334
+ callback(null, LOCAL_IPV6, 6);
306
335
  });
307
336
  },
308
337
 
@@ -124,6 +124,13 @@ module.exports = [
124
124
  disableParam && (formparam.disabled = true);
125
125
  };
126
126
 
127
+ // handle base64 encoded file
128
+ if (!formparam.src && formparam.value && typeof formparam.value === STRING) {
129
+ formparam.value = Buffer.from(formparam.value, 'base64');
130
+
131
+ return callback();
132
+ }
133
+
127
134
  // handle missing file src
128
135
  if (!formparam.src || (paramIsComposite && !formparam.src.length)) {
129
136
  onLoadError(new Error('missing file source'), false);
@@ -188,6 +195,13 @@ module.exports = [
188
195
  },
189
196
  // file data
190
197
  file (filedata, next) {
198
+ // handle base64 encoded file
199
+ if (!filedata.src && filedata.content && typeof filedata.content === STRING) {
200
+ filedata.content = Buffer.from(filedata.content, 'base64');
201
+
202
+ return next();
203
+ }
204
+
191
205
  // eslint-disable-next-line security/detect-non-literal-fs-filename
192
206
  util.createReadStream(resolver, filedata.src, function (err, stream) {
193
207
  if (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postman-runtime",
3
- "version": "7.32.3",
3
+ "version": "7.34.0",
4
4
  "description": "Underlying library of executing Postman Collections",
5
5
  "author": "Postman Inc.",
6
6
  "license": "Apache-2.0",
@@ -45,17 +45,18 @@
45
45
  "@postman/tough-cookie": "4.1.3-postman.1",
46
46
  "async": "3.2.4",
47
47
  "aws4": "1.12.0",
48
- "handlebars": "4.7.7",
49
- "httpntlm": "1.8.12",
48
+ "handlebars": "4.7.8",
49
+ "httpntlm": "1.8.13",
50
50
  "jose": "4.14.4",
51
51
  "js-sha512": "0.8.0",
52
52
  "lodash": "4.17.21",
53
53
  "mime-types": "2.1.35",
54
+ "node-forge": "1.3.1",
54
55
  "node-oauth1": "1.3.0",
55
56
  "performance-now": "2.1.0",
56
- "postman-collection": "4.1.7",
57
+ "postman-collection": "4.2.1",
57
58
  "postman-request": "2.88.1-postman.33",
58
- "postman-sandbox": "4.2.6",
59
+ "postman-sandbox": "4.2.8",
59
60
  "postman-url-encoder": "3.0.5",
60
61
  "serialised-error": "1.1.3",
61
62
  "strip-json-comments": "3.1.1",
@@ -65,14 +66,14 @@
65
66
  "@postman/shipit": "^0.4.0",
66
67
  "ajv": "^8.12.0",
67
68
  "browserify": "^17.0.0",
68
- "chai": "^4.3.7",
69
+ "chai": "^4.3.10",
69
70
  "chalk": "^4.1.2",
70
71
  "dependency-check": "^4.1.0",
71
72
  "editorconfig": "^1.0.2",
72
73
  "eslint": "^7.32.0",
73
74
  "eslint-plugin-jsdoc": "^36.1.1",
74
75
  "eslint-plugin-lodash": "^7.4.0",
75
- "eslint-plugin-mocha": "^10.1.0",
76
+ "eslint-plugin-mocha": "^10.2.0",
76
77
  "eslint-plugin-security": "^1.7.1",
77
78
  "express": "^4.17.2",
78
79
  "graphql": "^15.7.2",
@@ -92,7 +93,7 @@
92
93
  "shelljs": "^0.8.5",
93
94
  "sinon": "^12.0.1",
94
95
  "teleport-javascript": "^1.0.0",
95
- "terser": "^5.17.7",
96
+ "terser": "^5.22.0",
96
97
  "tmp": "^0.2.1",
97
98
  "webpack": "^5.86.0",
98
99
  "yankee": "^1.0.8"