postman-runtime 7.52.0-beta.3 → 7.52.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 +3 -2
- package/README.md +5 -5
- package/dist/index.js +1 -1
- package/lib/runner/extensions/event.command.js +9 -9
- package/lib/runner/extensions/item.command.js +180 -112
- package/lib/runner/index.js +5 -5
- package/lib/runner/resolve-secrets.js +18 -11
- package/lib/runner/util.js +83 -45
- package/package.json +3 -3
|
@@ -39,18 +39,18 @@ var _ = require('lodash'),
|
|
|
39
39
|
getCookieDomain, // fn
|
|
40
40
|
postProcessContext, // fn
|
|
41
41
|
sanitizeFiles, // fn
|
|
42
|
-
|
|
42
|
+
maskForbiddenSecrets; // fn
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Clones variable scopes and masks variables in
|
|
46
|
-
* Used to hide
|
|
45
|
+
* Clones variable scopes and masks variables in forbiddenSecretKeys (Set of "scopeName:variableKey").
|
|
46
|
+
* Used to hide secrets not allowed in scripts while keeping them available for request substitution.
|
|
47
47
|
*
|
|
48
48
|
* @param {Object} context - Context with environment, globals, collectionVariables
|
|
49
|
-
* @param {Set}
|
|
49
|
+
* @param {Set} forbiddenSecretKeys - Set of "scopeName:variableKey" for secrets with allowedInScript=false
|
|
50
50
|
* @returns {Object} - Context with cloned, masked scopes
|
|
51
51
|
*/
|
|
52
|
-
|
|
53
|
-
if (!
|
|
52
|
+
maskForbiddenSecrets = function (context, forbiddenSecretKeys) {
|
|
53
|
+
if (!forbiddenSecretKeys || forbiddenSecretKeys.size === 0) {
|
|
54
54
|
return context;
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -68,7 +68,7 @@ maskUnsafeSecrets = function (context, unsafeSecretKeys) {
|
|
|
68
68
|
maskedScope = new sdk.VariableScope(scope.toJSON ? scope.toJSON() : scope);
|
|
69
69
|
|
|
70
70
|
maskedScope.values.each(function (variable) {
|
|
71
|
-
if (variable &&
|
|
71
|
+
if (variable && forbiddenSecretKeys.has(scopeName + ':' + variable.key)) {
|
|
72
72
|
variable.set(undefined);
|
|
73
73
|
}
|
|
74
74
|
});
|
|
@@ -555,8 +555,8 @@ module.exports = {
|
|
|
555
555
|
executeScript = ({ resolvedPackages }, done) => {
|
|
556
556
|
var contextToUse = _.pick(payload.context, SAFE_CONTEXT_VARIABLES);
|
|
557
557
|
|
|
558
|
-
if (payload.context.
|
|
559
|
-
contextToUse =
|
|
558
|
+
if (payload.context._forbiddenSecretKeys) {
|
|
559
|
+
contextToUse = maskForbiddenSecrets(contextToUse, payload.context._forbiddenSecretKeys);
|
|
560
560
|
}
|
|
561
561
|
|
|
562
562
|
// finally execute the script
|
|
@@ -3,6 +3,7 @@ var _ = require('lodash'),
|
|
|
3
3
|
Response = require('postman-collection').Response,
|
|
4
4
|
visualizer = require('../../visualizer'),
|
|
5
5
|
resolveSecrets = require('../resolve-secrets').resolveSecrets,
|
|
6
|
+
{ resolveUrlString } = require('../util'),
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* List of request properties which can be mutated via pre-request
|
|
@@ -140,21 +141,26 @@ module.exports = {
|
|
|
140
141
|
this.triggers.beforeItem(null, coords, item);
|
|
141
142
|
|
|
142
143
|
// Build payload for secret resolution
|
|
143
|
-
// Only scan environment, globals, and collectionVariables for secrets
|
|
144
144
|
secretResolutionPayload = {
|
|
145
145
|
item,
|
|
146
146
|
environment,
|
|
147
147
|
globals,
|
|
148
|
-
collectionVariables
|
|
148
|
+
collectionVariables,
|
|
149
|
+
vaultSecrets,
|
|
150
|
+
_variables,
|
|
151
|
+
data
|
|
149
152
|
};
|
|
150
153
|
|
|
151
154
|
/**
|
|
152
155
|
* Execute the main item flow (delay, prerequest, request, test)
|
|
153
156
|
*
|
|
154
157
|
* @param {Array} [secretResolutionErrors] - Errors from partial secret resolution
|
|
155
|
-
* @param {Set} [
|
|
158
|
+
* @param {Set} [forbiddenSecretKeys] - Set of scopeName:variableKey for secrets with allowedInScript=false
|
|
156
159
|
*/
|
|
157
|
-
executeItemFlow = function (secretResolutionErrors,
|
|
160
|
+
executeItemFlow = function (secretResolutionErrors, forbiddenSecretKeys) {
|
|
161
|
+
var baselineUrl = secretResolver ?
|
|
162
|
+
resolveUrlString(item, secretResolutionPayload) : '';
|
|
163
|
+
|
|
158
164
|
self.queueDelay(function () {
|
|
159
165
|
// create the context object for scripts to run
|
|
160
166
|
ctxTemplate = {
|
|
@@ -165,7 +171,7 @@ module.exports = {
|
|
|
165
171
|
environment: environment,
|
|
166
172
|
data: data,
|
|
167
173
|
request: item.request,
|
|
168
|
-
|
|
174
|
+
_forbiddenSecretKeys: forbiddenSecretKeys
|
|
169
175
|
};
|
|
170
176
|
|
|
171
177
|
// @todo make it less nested by coding Instruction.thenQueue
|
|
@@ -211,104 +217,165 @@ module.exports = {
|
|
|
211
217
|
}
|
|
212
218
|
});
|
|
213
219
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
visualizerResult
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
220
|
+
var postPreReqPayload,
|
|
221
|
+
postPreReqUrl,
|
|
222
|
+
proceedWithRequest = function () {
|
|
223
|
+
this.queue('request', {
|
|
224
|
+
item: item,
|
|
225
|
+
vaultSecrets: ctxTemplate.vaultSecrets,
|
|
226
|
+
globals: ctxTemplate.globals,
|
|
227
|
+
environment: ctxTemplate.environment,
|
|
228
|
+
collectionVariables: ctxTemplate.collectionVariables,
|
|
229
|
+
_variables: ctxTemplate._variables,
|
|
230
|
+
data: ctxTemplate.data,
|
|
231
|
+
coords: coords,
|
|
232
|
+
source: 'collection'
|
|
233
|
+
}).done(function (result, requestError) {
|
|
234
|
+
!result && (result = {});
|
|
235
|
+
|
|
236
|
+
var request = result.request,
|
|
237
|
+
response = result.response,
|
|
238
|
+
cookies = result.cookies;
|
|
239
|
+
|
|
240
|
+
if ((stopOnError || stopOnFailure) && requestError) {
|
|
241
|
+
// @todo - should this trigger receive error?
|
|
242
|
+
this.triggers.item(null, coords, item);
|
|
243
|
+
|
|
244
|
+
return callback && callback.call(this, requestError, {
|
|
245
|
+
request
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// also the test object requires the updated request object
|
|
250
|
+
// (since auth helpers may modify it)
|
|
251
|
+
request && (ctxTemplate.request = request);
|
|
252
|
+
|
|
253
|
+
// @note convert response instance to plain object.
|
|
254
|
+
// we want to avoid calling Response.toJSON() which triggers
|
|
255
|
+
// toJSON on Response.stream buffer.
|
|
256
|
+
// Because that increases the size of stringified object by 3 times.
|
|
257
|
+
// Also, that increases the total number of tokens
|
|
258
|
+
// (buffer.data) whereas Buffer.toString generates a single
|
|
259
|
+
// string that is easier to stringify and sent over the
|
|
260
|
+
// UVM bridge.
|
|
261
|
+
response && (ctxTemplate.response = getResponseJSON(response));
|
|
262
|
+
|
|
263
|
+
// set cookies for this transaction
|
|
264
|
+
cookies && (ctxTemplate.cookies = cookies);
|
|
265
|
+
|
|
266
|
+
// the context template also has a test object to store assertions
|
|
267
|
+
ctxTemplate.tests = {}; // @todo remove
|
|
268
|
+
|
|
269
|
+
this.queue('event', {
|
|
270
|
+
name: 'test',
|
|
271
|
+
item: item,
|
|
272
|
+
coords: coords,
|
|
273
|
+
context: ctxTemplate,
|
|
274
|
+
// No need to include vaultSecrets here as runtime
|
|
275
|
+
// takes care of tracking internally
|
|
276
|
+
trackContext: ['tests', 'globals', 'environment', 'collectionVariables'],
|
|
277
|
+
stopOnScriptError: stopOnError,
|
|
278
|
+
abortOnFailure: abortOnFailure,
|
|
279
|
+
stopOnFailure: stopOnFailure
|
|
280
|
+
}).done(function (testExecutions, testExecutionError) {
|
|
281
|
+
var visualizerData = extractVisualizerData(prereqExecutions, testExecutions),
|
|
282
|
+
visualizerResult;
|
|
283
|
+
|
|
284
|
+
if (visualizerData) {
|
|
285
|
+
visualizer.processTemplate(visualizerData.template,
|
|
286
|
+
visualizerData.data,
|
|
287
|
+
visualizerData.options,
|
|
288
|
+
function (err, processedTemplate) {
|
|
289
|
+
visualizerResult = {
|
|
290
|
+
// bubble up the errors while processing
|
|
291
|
+
// template through visualizer result
|
|
292
|
+
error: err,
|
|
293
|
+
|
|
294
|
+
// add processed template and data to visualizer result
|
|
295
|
+
processedTemplate: processedTemplate,
|
|
296
|
+
data: visualizerData.data
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// trigger an event saying that item has been processed
|
|
300
|
+
this.triggers.item(null, coords, item, visualizerResult,
|
|
301
|
+
secretResolutionErrors && secretResolutionErrors.length ?
|
|
302
|
+
{ secretResolutionErrors } : undefined);
|
|
303
|
+
}.bind(this));
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
// trigger an event saying that item has been processed
|
|
307
|
+
// @todo - should this trigger receive error?
|
|
308
|
+
this.triggers.item(null, coords, item, null,
|
|
287
309
|
secretResolutionErrors && secretResolutionErrors.length ?
|
|
288
310
|
{ secretResolutionErrors } : undefined);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
testExecutionError : null, {
|
|
305
|
-
prerequest: prereqExecutions,
|
|
306
|
-
request: request,
|
|
307
|
-
response: response,
|
|
308
|
-
test: testExecutions
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// reset mutated request with original request instance
|
|
314
|
+
// @note request mutations are not persisted across iterations
|
|
315
|
+
item.request = originalRequest;
|
|
316
|
+
|
|
317
|
+
callback && callback.call(this, ((stopOnError || stopOnFailure) &&
|
|
318
|
+
testExecutionError) ?
|
|
319
|
+
testExecutionError : null, {
|
|
320
|
+
prerequest: prereqExecutions,
|
|
321
|
+
request: request,
|
|
322
|
+
response: response,
|
|
323
|
+
test: testExecutions
|
|
324
|
+
});
|
|
325
|
+
});
|
|
309
326
|
});
|
|
310
|
-
});
|
|
311
|
-
|
|
327
|
+
}.bind(this);
|
|
328
|
+
|
|
329
|
+
if (secretResolver) {
|
|
330
|
+
postPreReqPayload = {
|
|
331
|
+
item: item,
|
|
332
|
+
environment: ctxTemplate.environment,
|
|
333
|
+
globals: ctxTemplate.globals,
|
|
334
|
+
collectionVariables: ctxTemplate.collectionVariables,
|
|
335
|
+
vaultSecrets: ctxTemplate.vaultSecrets,
|
|
336
|
+
_variables: ctxTemplate._variables,
|
|
337
|
+
data: ctxTemplate.data
|
|
338
|
+
};
|
|
339
|
+
postPreReqUrl = resolveUrlString(item, postPreReqPayload);
|
|
340
|
+
|
|
341
|
+
if (postPreReqUrl !== baselineUrl) {
|
|
342
|
+
return resolveSecrets(postPreReqPayload, secretResolver,
|
|
343
|
+
function (err, secErrors, newForbiddenKeys) {
|
|
344
|
+
if (err) {
|
|
345
|
+
self.triggers.item(err, coords, item, null,
|
|
346
|
+
{ hasSecretResolutionFailed: true });
|
|
347
|
+
|
|
348
|
+
callback && callback.call(self, err, {
|
|
349
|
+
request: null
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (secErrors && secErrors.length) {
|
|
356
|
+
secretResolutionErrors = (secretResolutionErrors || [])
|
|
357
|
+
.concat(secErrors);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (newForbiddenKeys && newForbiddenKeys.size > 0) {
|
|
361
|
+
if (!forbiddenSecretKeys) {
|
|
362
|
+
forbiddenSecretKeys = newForbiddenKeys;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
newForbiddenKeys.forEach(function (key) {
|
|
366
|
+
forbiddenSecretKeys.add(key);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
ctxTemplate._forbiddenSecretKeys = forbiddenSecretKeys;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
proceedWithRequest();
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
proceedWithRequest();
|
|
312
379
|
});
|
|
313
380
|
}.bind(self), {
|
|
314
381
|
time: delay,
|
|
@@ -319,20 +386,21 @@ module.exports = {
|
|
|
319
386
|
|
|
320
387
|
// Resolve secrets before pre-request scripts so scripts can access resolved values via related pm APIs
|
|
321
388
|
if (secretResolver) {
|
|
322
|
-
resolveSecrets(secretResolutionPayload, secretResolver,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
389
|
+
resolveSecrets(secretResolutionPayload, secretResolver,
|
|
390
|
+
function (err, secErrors, forbiddenSecretKeys) {
|
|
391
|
+
if (err) {
|
|
392
|
+
// Fatal: stop immediately, request is never executed
|
|
393
|
+
self.triggers.item(err, coords, item, null, { hasSecretResolutionFailed: true });
|
|
394
|
+
|
|
395
|
+
callback && callback.call(self, err, {
|
|
396
|
+
request: null
|
|
397
|
+
});
|
|
330
398
|
|
|
331
|
-
|
|
332
|
-
|
|
399
|
+
return next();
|
|
400
|
+
}
|
|
333
401
|
|
|
334
|
-
|
|
335
|
-
|
|
402
|
+
executeItemFlow(secErrors, forbiddenSecretKeys);
|
|
403
|
+
});
|
|
336
404
|
}
|
|
337
405
|
else {
|
|
338
406
|
executeItemFlow();
|
package/lib/runner/index.js
CHANGED
|
@@ -97,16 +97,16 @@ _.assign(Runner.prototype, {
|
|
|
97
97
|
* @param {String} [options.entrypoint.lookupStrategy=idOrName] strategy to lookup the entrypoint [idOrName, path]
|
|
98
98
|
* @param {Array<String>} [options.entrypoint.path] path to lookup
|
|
99
99
|
* @param {Object} [options.run] Run-specific options, such as options related to the host
|
|
100
|
-
* @param {Function} [options.secretResolver] - Function({ secrets,
|
|
101
|
-
* Receives: secrets (array of { scopeName, scope, variable, context }),
|
|
102
|
-
*
|
|
100
|
+
* @param {Function} [options.secretResolver] - Function({ secrets, url }, callback) that resolves secrets.
|
|
101
|
+
* Receives: secrets (array of { scopeName, scope, variable, context }), url (request URL without query).
|
|
102
|
+
* Callback is (err, result).
|
|
103
103
|
* On fatal error: callback(err) — request execution stops.
|
|
104
104
|
* On success: callback(null, result) where result is Array<{ resolvedValue?: string,
|
|
105
|
-
* error?: Error,
|
|
105
|
+
* error?: Error, allowedInScript?: boolean }>;
|
|
106
106
|
* result[i] corresponds to secrets[i].
|
|
107
107
|
* resolvedValue: resolved string (undefined if failed/skipped).
|
|
108
108
|
* error: Error when resolution failed for particular secret.
|
|
109
|
-
*
|
|
109
|
+
* allowedInScript: if true, value is exposed to scripts via pm.environment/pm.variables;
|
|
110
110
|
* if false/undefined, masked from scripts. Runtime applies values.
|
|
111
111
|
*
|
|
112
112
|
* @param {Function} callback -
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
var _ = require('lodash')
|
|
1
|
+
var _ = require('lodash'),
|
|
2
|
+
{ resolveUrlString } = require('./util');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Extract all variables with secret === true from a variable scope.
|
|
@@ -43,16 +44,20 @@ function buildResolverContext (payload, variableKey) {
|
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Scans variable scopes for secrets and invokes the secretResolver with them.
|
|
46
|
-
* Consumer returns array of { resolvedValue, error,
|
|
47
|
+
* Consumer returns array of { resolvedValue, error, allowedInScript }; runtime applies values.
|
|
47
48
|
*
|
|
48
49
|
* @param {Object} payload - Request payload
|
|
49
50
|
* @param {Object} payload.item - The request item
|
|
50
51
|
* @param {Object} payload.environment - Environment scope
|
|
51
52
|
* @param {Object} payload.globals - Globals scope
|
|
52
53
|
* @param {Object} payload.collectionVariables - Collection variables scope
|
|
53
|
-
* @param {
|
|
54
|
-
*
|
|
55
|
-
* @param {
|
|
54
|
+
* @param {Object} payload.vaultSecrets - Vault secrets scope
|
|
55
|
+
* @param {Object} payload._variables - Local variables scope
|
|
56
|
+
* @param {Object} payload.data - Iteration data
|
|
57
|
+
* @param {Function} secretResolver - Function({ secrets, urlString }, callback) that resolves secrets.
|
|
58
|
+
* Callback is (err, result) where result is Array<{ resolvedValue?: string, error?: Error,
|
|
59
|
+
* allowedInScript?: boolean }>.
|
|
60
|
+
* @param {Function} callback - callback(err, secretResolutionErrors, forbiddenSecretKeys)
|
|
56
61
|
*/
|
|
57
62
|
function resolveSecrets (payload, secretResolver, callback) {
|
|
58
63
|
if (!secretResolver || typeof secretResolver !== 'function') {
|
|
@@ -64,7 +69,9 @@ function resolveSecrets (payload, secretResolver, callback) {
|
|
|
64
69
|
{ name: 'globals', scope: payload.globals },
|
|
65
70
|
{ name: 'collectionVariables', scope: payload.collectionVariables }
|
|
66
71
|
],
|
|
67
|
-
secrets = []
|
|
72
|
+
secrets = [],
|
|
73
|
+
url = resolveUrlString(payload.item, payload),
|
|
74
|
+
urlString = typeof url.toString === 'function' ? url.toString() : String(url);
|
|
68
75
|
|
|
69
76
|
scopesToScan.forEach(function (scopeInfo) {
|
|
70
77
|
var secretVars = extractSecretVariables(scopeInfo.scope);
|
|
@@ -83,7 +90,7 @@ function resolveSecrets (payload, secretResolver, callback) {
|
|
|
83
90
|
return callback();
|
|
84
91
|
}
|
|
85
92
|
|
|
86
|
-
secretResolver({ secrets,
|
|
93
|
+
secretResolver({ secrets, urlString }, function (err, result) {
|
|
87
94
|
if (err) {
|
|
88
95
|
return callback(err);
|
|
89
96
|
}
|
|
@@ -103,17 +110,17 @@ function resolveSecrets (payload, secretResolver, callback) {
|
|
|
103
110
|
result.filter(function (entry) { return entry && entry.error; })
|
|
104
111
|
.map(function (entry) { return entry.error; }) :
|
|
105
112
|
[],
|
|
106
|
-
|
|
113
|
+
forbiddenSecretKeys = new Set();
|
|
107
114
|
|
|
108
115
|
if (result && Array.isArray(result)) {
|
|
109
116
|
result.forEach(function (entry, i) {
|
|
110
|
-
if (i < secrets.length && entry && entry.
|
|
111
|
-
|
|
117
|
+
if (i < secrets.length && entry && entry.allowedInScript === false) {
|
|
118
|
+
forbiddenSecretKeys.add(secrets[i].scopeName + ':' + secrets[i].variable.key);
|
|
112
119
|
}
|
|
113
120
|
});
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
callback(null, secretResolutionErrors,
|
|
123
|
+
callback(null, secretResolutionErrors, forbiddenSecretKeys);
|
|
117
124
|
});
|
|
118
125
|
}
|
|
119
126
|
|