postman-runtime 7.51.1 → 7.52.0-beta.2
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 +4 -0
- package/dist/index.js +1 -1
- package/lib/runner/extensions/item.command.js +174 -131
- package/lib/runner/extensions/request.command.js +7 -5
- package/lib/runner/index.js +10 -1
- package/lib/runner/resolve-secrets.js +311 -0
- package/package.json +2 -2
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
var _ = require('lodash'),
|
|
2
|
+
async = require('async'),
|
|
3
|
+
sdk = require('postman-collection'),
|
|
4
|
+
|
|
5
|
+
DEFAULT_TIMEOUT = 5000,
|
|
6
|
+
DEFAULT_RETRY_COUNT = 0;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract all variables that have a `source` property from a variable scope.
|
|
10
|
+
*
|
|
11
|
+
* @param {VariableScope|Object} scope - scope
|
|
12
|
+
* @param {Set} [usedVariables] - optional set of variable keys to filter by
|
|
13
|
+
* @returns {Array} - variables
|
|
14
|
+
*/
|
|
15
|
+
function extractSourceVariables (scope, usedVariables) {
|
|
16
|
+
if (!scope) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var values = _.get(scope, 'values'),
|
|
21
|
+
sourceVariables = [];
|
|
22
|
+
|
|
23
|
+
if (!values) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (values.members && Array.isArray(values.members)) {
|
|
28
|
+
values.members.forEach(function (variable) {
|
|
29
|
+
if (variable && variable.source && variable.type === 'secret') {
|
|
30
|
+
if (!usedVariables || usedVariables.has(variable.key)) {
|
|
31
|
+
sourceVariables.push(variable);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else if (Array.isArray(values)) {
|
|
37
|
+
values.forEach(function (variable) {
|
|
38
|
+
if (variable && variable.source && variable.type === 'secret') {
|
|
39
|
+
if (!usedVariables || usedVariables.has(variable.key)) {
|
|
40
|
+
sourceVariables.push(variable);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return sourceVariables;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Find all variable substitutions used in the request item.
|
|
51
|
+
* Scans URL, headers, body, auth, and other request components.
|
|
52
|
+
*
|
|
53
|
+
* @param {Item} item - The request item to scan
|
|
54
|
+
* @returns {Set} - Set of variable keys used in the item
|
|
55
|
+
*/
|
|
56
|
+
function findUsedVariables (item) {
|
|
57
|
+
var variableSet = new Set(),
|
|
58
|
+
itemJson,
|
|
59
|
+
substitutions;
|
|
60
|
+
|
|
61
|
+
if (item) {
|
|
62
|
+
itemJson = typeof item.toJSON === 'function' ? item.toJSON() : item;
|
|
63
|
+
substitutions = sdk.Property.findSubstitutions(itemJson);
|
|
64
|
+
substitutions.forEach(function (v) { variableSet.add(v); });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return variableSet;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build context object for resolver function
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} payload - Request payload
|
|
74
|
+
* @param {String} variableKey - The key of the variable being resolved
|
|
75
|
+
* @returns {Object} - Context object
|
|
76
|
+
*/
|
|
77
|
+
function buildResolverContext (payload, variableKey) {
|
|
78
|
+
return {
|
|
79
|
+
item: payload.item,
|
|
80
|
+
variableKey: variableKey
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a standardized secret error object
|
|
86
|
+
*
|
|
87
|
+
* @param {String} code - Error code
|
|
88
|
+
* @param {String} message - Human readable error message
|
|
89
|
+
* @param {Object} details - Additional error details
|
|
90
|
+
* @returns {Error} - Error object with additional properties
|
|
91
|
+
*/
|
|
92
|
+
function createSecretError (code, message, details) {
|
|
93
|
+
var error = new Error(message);
|
|
94
|
+
|
|
95
|
+
error.code = code;
|
|
96
|
+
error.details = details;
|
|
97
|
+
|
|
98
|
+
return error;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Execute a single resolver with timeout and retry support
|
|
103
|
+
*
|
|
104
|
+
* @param {Function} resolver - The resolver function
|
|
105
|
+
* @param {Object} secret - Provider-specific secret object (e.g. source.postman)
|
|
106
|
+
* @param {Object} context - Resolver context
|
|
107
|
+
* @param {Object} options - Execution options
|
|
108
|
+
* @param {Number} options.timeout - Timeout in ms
|
|
109
|
+
* @param {Number} options.retryCount - Number of retries
|
|
110
|
+
* @param {Function} callback - Callback(err, resolvedValue)
|
|
111
|
+
*/
|
|
112
|
+
function executeResolver (resolver, secret, context, options, callback) {
|
|
113
|
+
var timeout = options.timeout || DEFAULT_TIMEOUT,
|
|
114
|
+
retriesLeft = options.retryCount || DEFAULT_RETRY_COUNT,
|
|
115
|
+
timeoutId,
|
|
116
|
+
completed = false;
|
|
117
|
+
|
|
118
|
+
function attemptResolve () {
|
|
119
|
+
var result;
|
|
120
|
+
|
|
121
|
+
timeoutId = setTimeout(function () {
|
|
122
|
+
if (completed) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (retriesLeft > 0) {
|
|
127
|
+
retriesLeft--;
|
|
128
|
+
|
|
129
|
+
return attemptResolve();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
completed = true;
|
|
133
|
+
callback(new Error('Secret resolution timed out after ' + timeout + 'ms'));
|
|
134
|
+
}, timeout);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
result = resolver(secret, context);
|
|
138
|
+
|
|
139
|
+
// promise-based resolver handling
|
|
140
|
+
if (result && typeof result.then === 'function') {
|
|
141
|
+
result
|
|
142
|
+
.then(function (resolvedValue) {
|
|
143
|
+
if (completed) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
clearTimeout(timeoutId);
|
|
147
|
+
completed = true;
|
|
148
|
+
callback(null, resolvedValue);
|
|
149
|
+
})
|
|
150
|
+
.catch(function (err) {
|
|
151
|
+
if (completed) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
clearTimeout(timeoutId);
|
|
155
|
+
|
|
156
|
+
if (retriesLeft > 0) {
|
|
157
|
+
retriesLeft--;
|
|
158
|
+
|
|
159
|
+
return attemptResolve();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
completed = true;
|
|
163
|
+
callback(err);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// synchronous resolver handling
|
|
167
|
+
else if (result !== undefined) {
|
|
168
|
+
if (completed) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
clearTimeout(timeoutId);
|
|
172
|
+
completed = true;
|
|
173
|
+
|
|
174
|
+
return callback(null, result);
|
|
175
|
+
}
|
|
176
|
+
// assume callback-based in other cases
|
|
177
|
+
else {
|
|
178
|
+
if (completed) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
clearTimeout(timeoutId);
|
|
182
|
+
completed = true;
|
|
183
|
+
|
|
184
|
+
return callback(null, undefined);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
if (completed) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
clearTimeout(timeoutId);
|
|
192
|
+
|
|
193
|
+
if (retriesLeft > 0) {
|
|
194
|
+
retriesLeft--;
|
|
195
|
+
|
|
196
|
+
return attemptResolve();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
completed = true;
|
|
200
|
+
|
|
201
|
+
return callback(err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
attemptResolve();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Scans variable scopes for secrets and resolves them using configured resolvers.
|
|
210
|
+
* Resolves secrets in parallel. If any resolution fails, stops and returns the error.
|
|
211
|
+
*
|
|
212
|
+
* @param {Object} payload - Request payload
|
|
213
|
+
* @param {Object} payload.item - The request item
|
|
214
|
+
* @param {Object} payload.environment - Environment scope
|
|
215
|
+
* @param {Object} payload.globals - Globals scope
|
|
216
|
+
* @param {Object} payload.collectionVariables - Collection variables scope
|
|
217
|
+
* @param {Object} secretResolvers - Object mapping provider names to resolver configs
|
|
218
|
+
* @param {Function} callback - callback(err)
|
|
219
|
+
*/
|
|
220
|
+
function resolveSecrets (payload, secretResolvers, callback) {
|
|
221
|
+
if (!secretResolvers || typeof secretResolvers !== 'object' || _.isEmpty(secretResolvers)) {
|
|
222
|
+
return callback();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
var usedVariables = findUsedVariables(payload.item),
|
|
226
|
+
scopesToScan = [
|
|
227
|
+
{ name: 'environment', scope: payload.environment },
|
|
228
|
+
{ name: 'globals', scope: payload.globals },
|
|
229
|
+
{ name: 'collectionVariables', scope: payload.collectionVariables }
|
|
230
|
+
],
|
|
231
|
+
variablesToResolve = [];
|
|
232
|
+
|
|
233
|
+
scopesToScan.forEach(function (scopeInfo) {
|
|
234
|
+
var sourceVars = extractSourceVariables(scopeInfo.scope, usedVariables);
|
|
235
|
+
|
|
236
|
+
sourceVars.forEach(function (variable) {
|
|
237
|
+
variablesToResolve.push({
|
|
238
|
+
scopeName: scopeInfo.name,
|
|
239
|
+
scope: scopeInfo.scope,
|
|
240
|
+
variable: variable
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (variablesToResolve.length === 0) {
|
|
246
|
+
return callback();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async.each(variablesToResolve, function (item, next) {
|
|
250
|
+
var variable = item.variable,
|
|
251
|
+
source = variable.source,
|
|
252
|
+
sourceProvider = source && source.provider,
|
|
253
|
+
secret = sourceProvider && (source[sourceProvider] || null),
|
|
254
|
+
resolverConfig = sourceProvider && secretResolvers[sourceProvider],
|
|
255
|
+
context,
|
|
256
|
+
errorDetails;
|
|
257
|
+
|
|
258
|
+
// No resolver for this provider or invalid source - keep placeholder value
|
|
259
|
+
if (!resolverConfig || typeof resolverConfig.resolver !== 'function' || !secret) {
|
|
260
|
+
return next();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
context = buildResolverContext(payload, variable.key);
|
|
264
|
+
errorDetails = {
|
|
265
|
+
key: variable.key,
|
|
266
|
+
provider: sourceProvider,
|
|
267
|
+
resolverId: resolverConfig.id || sourceProvider,
|
|
268
|
+
resolverName: resolverConfig.name || sourceProvider
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
executeResolver(resolverConfig.resolver,
|
|
272
|
+
secret,
|
|
273
|
+
context,
|
|
274
|
+
{
|
|
275
|
+
timeout: resolverConfig.timeout || DEFAULT_TIMEOUT,
|
|
276
|
+
retryCount: resolverConfig.retryCount || DEFAULT_RETRY_COUNT
|
|
277
|
+
},
|
|
278
|
+
function (err, resolvedValue) {
|
|
279
|
+
if (err) {
|
|
280
|
+
var isTimeout = err.message && err.message.includes('timed out'),
|
|
281
|
+
errorCode = isTimeout ?
|
|
282
|
+
'SECRET_RESOLUTION_TIMEOUT' : 'SECRET_RESOLUTION_FAILED',
|
|
283
|
+
secretError = createSecretError(errorCode,
|
|
284
|
+
'Failed to resolve secret: ' + err.message,
|
|
285
|
+
errorDetails);
|
|
286
|
+
|
|
287
|
+
// Blocking error - pass to next() to stop processing and fail the request
|
|
288
|
+
return next(secretError);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!_.isNil(resolvedValue)) {
|
|
292
|
+
if (typeof variable.set === 'function') {
|
|
293
|
+
variable.set(resolvedValue);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
variable.value = resolvedValue;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return next();
|
|
301
|
+
});
|
|
302
|
+
}, function (err) {
|
|
303
|
+
callback(err);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
resolveSecrets,
|
|
309
|
+
extractSourceVariables,
|
|
310
|
+
findUsedVariables
|
|
311
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postman-runtime",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.52.0-beta.2",
|
|
4
4
|
"description": "Underlying library of executing Postman Collections",
|
|
5
5
|
"author": "Postman Inc.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"node-forge": "1.3.3",
|
|
55
55
|
"node-oauth1": "1.3.0",
|
|
56
56
|
"performance-now": "2.1.0",
|
|
57
|
-
"postman-collection": "5.
|
|
57
|
+
"postman-collection": "5.3.0-beta.1",
|
|
58
58
|
"postman-request": "2.88.1-postman.48",
|
|
59
59
|
"postman-sandbox": "6.4.0",
|
|
60
60
|
"postman-url-encoder": "3.0.8",
|