From 8167475de1dd1b4033f19afdf1d21bc63477e891 Mon Sep 17 00:00:00 2001 From: Dev Sharma Date: Tue, 3 Oct 2023 11:04:34 +0530 Subject: [PATCH] added support of secret variable --- lib/collection/mutation-tracker.js | 2 +- lib/collection/property.js | 6 ++-- lib/collection/variable-scope.js | 57 ++++++++++++++++++++++++++---- lib/collection/variable.js | 21 +++++++++++ lib/index.js | 1 + test/unit/mutation-tracker.test.js | 4 +-- test/unit/variable-scope.test.js | 52 +++++++++++++++++++++++++++ test/unit/variable.test.js | 26 ++++++++++++++ types/index.d.ts | 22 ++++++++++-- 9 files changed, 176 insertions(+), 15 deletions(-) diff --git a/lib/collection/mutation-tracker.js b/lib/collection/mutation-tracker.js index 9482d38f4..925f93f85 100644 --- a/lib/collection/mutation-tracker.js +++ b/lib/collection/mutation-tracker.js @@ -21,7 +21,7 @@ var _ = require('../util').lodash, * @returns {Boolean} */ isPrimitiveMutation = function (mutation) { - return mutation && mutation.length <= 2; + return mutation && mutation.length <= 3; }, /** diff --git a/lib/collection/property.js b/lib/collection/property.js index 49d4ff429..33ce9a115 100644 --- a/lib/collection/property.js +++ b/lib/collection/property.js @@ -16,9 +16,9 @@ var _ = require('../util').lodash, * * @private * @param {*} value Any JS variable within which we are trying to discover {{variables}} - * @param {[Object]} seen Set of objects traversed before to avoid infinite recursion - * @param {[Object]} result Set of variables to accumulate result in the recursive call - * @returns {Object} Set of variables + * @param {Object[]} seen Set of objects traversed before to avoid infinite recursion + * @param {Object[]} result Set of variables to accumulate result in the recursive call + * @returns {Object[]} Set of variables */ function _findSubstitutions (value, seen = new Set(), result = new Set()) { if (!value || seen.has(value)) { diff --git a/lib/collection/variable-scope.js b/lib/collection/variable-scope.js index bab70895c..c1c8954ed 100644 --- a/lib/collection/variable-scope.js +++ b/lib/collection/variable-scope.js @@ -15,7 +15,8 @@ var _ = require('../util').lodash, SET: 'set', UNSET: 'unset' }, - + SECRET = 'secret', + DEFAULT = 'default', VariableScope; /** @@ -219,7 +220,7 @@ _.assign(VariableScope.prototype, /** @lends VariableScope.prototype */ { * * @param {String} key - The name of the variable to set. * @param {*} value - The value of the variable to be set. - * @param {Variable.types} [type] - Optionally, the value of the variable can be set to a type + * @param {'secret' | 'default'} [type] - Optionally, the value of the variable can be set to a type */ set: function (key, value, type) { var variable = this.values.oneNormalizedVariable(key), @@ -239,7 +240,15 @@ _.assign(VariableScope.prototype, /** @lends VariableScope.prototype */ { } // track the change if mutation tracking is enabled - this._postman_enableTracking && this.mutations.track(MUTATIONS.SET, key, value); + if (this._postman_enableTracking) { + // eslint-disable-next-line security/detect-possible-timing-attacks + if (type === SECRET || type === DEFAULT) { + this.mutations.track(MUTATIONS.SET, key, value, type); + } + else { + this.mutations.track(MUTATIONS.SET, key, value); + } + } }, /** @@ -359,13 +368,14 @@ _.assign(VariableScope.prototype, /** @lends VariableScope.prototype */ { * @param {String} instruction Instruction identifying the type of the mutation, e.g. `set`, `unset` * @param {String} key - * @param {*} value - + * @param {String} type - */ - applyMutation: function (instruction, key, value) { + applyMutation: function (instruction, key, value, type) { // we know that `set` and `unset` are the only supported instructions // and we know the parameter signature of both is the same as the items in a mutation /* istanbul ignore else */ if (this[instruction]) { - this[instruction](key, value); + this[instruction](key, value, type); } }, @@ -463,6 +473,41 @@ _.assign(VariableScope, /** @lends VariableScope */ { } }); +/** + * CollectionVariableScope are the variable scope that are defined at the collection level. + */ +class CollectionVariableScope extends VariableScope { + /** + * Creates a new variable, or updates an existing one. + * + * @param {String} key - The name of the variable to set. + * @param {*} value - The value of the variable to be set. + */ + set (key, value) { + var variable = this.values.oneNormalizedVariable(key), + + // create an object that will be used as setter + update = { key, value }; + + + // If a variable by the name key exists, update it's value and return. + // @note adds new variable if existing is disabled. Disabled variables are not updated. + if (variable && !variable.disabled) { + variable.update(update); + } + else { + this.values.add(update); + } + + // track the change if mutation tracking is enabled + if (this._postman_enableTracking) { + // eslint-disable-next-line security/detect-possible-timing-attacks + this.mutations.track(MUTATIONS.SET, key, value); + } + } +} + module.exports = { - VariableScope + VariableScope, + CollectionVariableScope }; diff --git a/lib/collection/variable.js b/lib/collection/variable.js index cac0a041f..4bf87ccaa 100644 --- a/lib/collection/variable.js +++ b/lib/collection/variable.js @@ -355,6 +355,27 @@ _.assign(Variable, /** @lends Variable */ { return val; // pass through }, + /** + * @param {*} val - + * @returns {*} + */ + out (val) { + return val; // pass through + } + }, + + /** + * A "secret" type value stores data as postman secret variables + */ + secret: { + /** + * @param {*} val - + * @returns {*} + */ + in (val) { + return val; // pass through + }, + /** * @param {*} val - * @returns {*} diff --git a/lib/index.js b/lib/index.js index f94407414..ed439d24c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -28,6 +28,7 @@ module.exports = { Variable: require('./collection/variable').Variable, VariableList: require('./collection/variable-list').VariableList, VariableScope: require('./collection/variable-scope').VariableScope, + CollectionVariableScope: require('./collection/variable-scope').CollectionVariableScope, ProxyConfig: require('./collection/proxy-config').ProxyConfig, ProxyConfigList: require('./collection/proxy-config-list').ProxyConfigList, Version: require('./collection/version').Version diff --git a/test/unit/mutation-tracker.test.js b/test/unit/mutation-tracker.test.js index 10f07c302..6b76f1334 100644 --- a/test/unit/mutation-tracker.test.js +++ b/test/unit/mutation-tracker.test.js @@ -163,8 +163,8 @@ describe('MutationTracker', function () { it('should not track invalid mutation format', function () { var tracker = new MutationTracker(); - // expected signature is two parameters - tracker.track('set', 'foo', 'bar', 'baz'); + // expected signature is three parameters + tracker.track('set', 'foo', 'bar', 'secret', 'baz'); expect(tracker.count()).to.eql(0); }); diff --git a/test/unit/variable-scope.test.js b/test/unit/variable-scope.test.js index 394f89532..3c07720c6 100644 --- a/test/unit/variable-scope.test.js +++ b/test/unit/variable-scope.test.js @@ -1315,6 +1315,30 @@ describe('VariableScope', function () { expect(scope.mutations.count()).to.equal(1); }); + it('should track set operations with datatype when datatype is secret', function () { + var scope = new VariableScope(); + + scope.enableTracking(); + + scope.set('foo', 'bar', 'secret'); + + expect(scope).to.have.property('mutations'); + expect(scope.mutations.count()).to.equal(1); + expect(scope.mutations.stream[0].length).to.equal(3); + }); + + it('should track set operations without datatype when datatype other than secret', function () { + var scope = new VariableScope(); + + scope.enableTracking(); + + scope.set('foo', 'bar', 'string'); + + expect(scope).to.have.property('mutations'); + expect(scope.mutations.count()).to.equal(1); + expect(scope.mutations.stream[0].length).to.equal(2); + }); + it('should track unset operations', function () { var scope = new VariableScope({ values: [{ @@ -1379,6 +1403,34 @@ describe('VariableScope', function () { expect(scope1.values).to.eql(scope2.values); }); + it('should be capable of being replayed with secret datatype', function () { + var initialState = { + values: [{ + key: 'foo', + value: 'foo' + }, { + key: 'bar', + value: 'bar' + }] + }, + scope1 = new VariableScope(initialState), + scope2 = new VariableScope(initialState); + + scope1.enableTracking(); + + // add a new key + scope1.set('baz', 'baz', 'secret'); + // update a key + scope1.set('foo', 'foo updated', 'secret'); + // remove a key + scope1.unset('bar'); + + // replay mutations on a different object + scope1.mutations.applyOn(scope2); + + expect(scope1.values).to.eql(scope2.values); + }); + it('should be serialized', function () { var scope = new VariableScope(), serialized, diff --git a/test/unit/variable.test.js b/test/unit/variable.test.js index 5eb44a96e..2e39d7f4b 100644 --- a/test/unit/variable.test.js +++ b/test/unit/variable.test.js @@ -128,6 +128,18 @@ describe('Variable', function () { }); }); + it('should prepopulate value and type when passed to the constructor (secret)', function () { + let v = new Variable({ + value: 'Picard', + type: 'secret' + }); + + expect(v).to.deep.include({ + value: 'Picard', + type: 'secret' + }); + }); + it('should typecast value during construction when type is provided (number)', function () { var v = new Variable({ value: '108', @@ -227,6 +239,20 @@ describe('Variable', function () { expect(v.get()).to.equal(jsonValue); }); + it('should support any data type if type is set to `secret`', function () { + var v = new Variable({ type: 'secret' }), + jsonValue = { iam: 'json' }; + + v.set('Picard'); + expect(v.get()).to.equal('Picard'); + + v.set(3.142); + expect(v.get()).to.equal(3.142); + + v.set(jsonValue); + expect(v.get()).to.equal(jsonValue); + }); + it('should type cast values to the specific type set', function () { var v = new Variable({ type: 'string' diff --git a/types/index.d.ts b/types/index.d.ts index d4a3e7fb3..dc46f310e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,4 @@ -// Type definitions for postman-collection 4.0.2 +// Type definitions for postman-collection 4.2.1 // Project: https://github.com/postmanlabs/postman-collection // Definitions by: PostmanLabs // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped @@ -2365,7 +2365,7 @@ declare module "postman-collection" { * @param value - The value of the variable to be set. * @param [type] - Optionally, the value of the variable can be set to a type */ - set(key: string, value: any, type?: Variable.types): void; + set(key: string, value: any, type?: 'secret' | 'default'): void; /** * Removes the variable with the specified name. * @param key - - @@ -2402,6 +2402,18 @@ declare module "postman-collection" { static isVariableScope(obj: any): boolean; } + /** + * CollectionVariableScope are the variable scope that are defined at the collection level. + */ + export class CollectionVariableScope { + /** + * Creates a new variable, or updates an existing one. + * @param key - The name of the variable to set. + * @param value - The value of the variable to be set. + */ + set(key: string, value: any): void; + } + export namespace Variable { /** * The object representation of a Variable consists the variable value and type. It also optionally includes the `id` @@ -2456,7 +2468,11 @@ declare module "postman-collection" { * Free-form type of a value. This is the default for any variable, unless specified otherwise. It ensures that * the variable can store data in any type and no conversion is done while using Variable.get. */ - any = "{\"in\":\"\",\"out\":\"\"}" + any = "{\"in\":\"\",\"out\":\"\"}", + /** + * A "secret" type value stores data as postman secret variables + */ + secret = "{\"in\":\"\",\"out\":\"\"}" } }