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.
- package/CHANGELOG.yaml +20 -0
- package/dist/index.js +1 -1
- package/lib/authorizer/asap.js +253 -0
- package/lib/authorizer/index.js +1 -0
- package/lib/requester/core-body-builder.js +3 -8
- package/lib/requester/core.js +34 -5
- package/lib/runner/request-helpers-presend.js +14 -0
- package/package.json +9 -8
|
@@ -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
|
+
};
|
package/lib/authorizer/index.js
CHANGED
|
@@ -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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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);
|
package/lib/requester/core.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
296
|
-
|
|
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) {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
49
|
-
"httpntlm": "1.8.
|
|
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
|
|
57
|
+
"postman-collection": "4.2.1",
|
|
57
58
|
"postman-request": "2.88.1-postman.33",
|
|
58
|
-
"postman-sandbox": "4.2.
|
|
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.
|
|
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.
|
|
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.
|
|
96
|
+
"terser": "^5.22.0",
|
|
96
97
|
"tmp": "^0.2.1",
|
|
97
98
|
"webpack": "^5.86.0",
|
|
98
99
|
"yankee": "^1.0.8"
|