Merge pull request #13 from servisbot/MVP-4521-fix-org-variable

Added logic to fix the org variable if its been changed
This commit is contained in:
Steve Walsh 2021-07-14 08:48:09 +01:00 committed by GitHub
commit 5516bbc931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 295 additions and 126 deletions

View File

@ -1,4 +1,5 @@
const clone = require('clone'); const clone = require('clone');
const _ = require('lodash');
const variablesToCheck = [ const variablesToCheck = [
'logger.metadata.organization', 'logger.metadata.organization',
@ -41,21 +42,38 @@ module.exports = class PayloadValidator {
} }
} }
/**
* Gets the value at the given location in the given object
*
* @param {*} object
* @param {*} location
* @returns
*/
getValue(object, location) { getValue(object, location) {
return location.split('.').reduce((p, c) => (p && p[c]) || null, object); return _.get(object, location);
} }
verify(_after) { /**
if (this.isValidBefore) { * Sets the value at the given location in the given object to the given value
try { *
let after = _after; * @param {*} object
if (Array.isArray(after)) { * @param {*} location
after = after.find((msg) => !!msg); * @param {*} value
* @returns
*/
setValue(object, location, value) {
return _.set(object, location, value);
} }
variablesToCheck.forEach((location) => {
if (this.getValue(this.before, location) !== this.getValue(after, location)) { /**
* Log that we had a invalid payload modification
* @param {*} beforeValue
* @param {*} afterValue
* @param {*} location
*/
logException(beforeValue, afterValue, location) {
const details = { const details = {
message: `msg.${location} changed from "${this.getValue(this.before, location)}" to "${this.getValue(after, location)}" for bot "${this.bot}"`, message: `msg.${location} changed from "${beforeValue}" to "${afterValue}" for bot "${this.bot}"`,
nodeId: this.nodeId, nodeId: this.nodeId,
workerId: this.workerId workerId: this.workerId
}; };
@ -68,13 +86,42 @@ module.exports = class PayloadValidator {
conversationId: this.conversationId conversationId: this.conversationId
}); });
} }
/**
* Ensures that the organization in the before matches the org in the after
* If they are different then it attempts to set the organization back to the correct one
* @param {*} after
* @returns
*/
ensureOrganizationNotModified(after) {
variablesToCheck.forEach((location) => {
const beforeValue = this.getValue(this.before, location);
const afterValue = this.getValue(after, location);
if (beforeValue !== afterValue) {
this.logException(beforeValue, afterValue, location);
// attempt to set the value back to its correct one
after = this.setValue(after, location, beforeValue);
if (!_.has(after, location, beforeValue)) {
this.logger.error(`Cant set value as ${location} is no longer assessable in after`);
}
}
}); });
} catch (e) {
console.log('Error while trying to verify variable changes'); return after;
console.log(e); }
verify(after) {
if (this.isValidBefore) {
if (Array.isArray(after)) {
const afterIndex = after.findIndex(msg => !!msg);
after[afterIndex] = this.ensureOrganizationNotModified(after[afterIndex]);
return after;
}
return this.ensureOrganizationNotModified(after);
} }
} else {
console.log('Error while trying to verify variable changes, wasn\'t initted with correct object'); console.log('Error while trying to verify variable changes, wasn\'t initted with correct object');
} return after;
} }
}; };

3
package-lock.json generated
View File

@ -5104,8 +5104,7 @@
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true
}, },
"lodash.assign": { "lodash.assign": {
"version": "4.2.0", "version": "4.2.0",

View File

@ -11,9 +11,8 @@
"main": "red/red.js", "main": "red/red.js",
"scripts": { "scripts": {
"start": "node red.js", "start": "node red.js",
"test": "grunt test-nodes",
"build": "grunt build", "build": "grunt build",
"test:unit": "nyc node_modules/.bin/mocha --recursive test/unit/*" "test": "nyc node_modules/.bin/mocha --recursive test/unit/*"
}, },
"bin": { "bin": {
"node-red": "./red.js", "node-red": "./red.js",
@ -54,6 +53,7 @@
"js-yaml": "3.11.0", "js-yaml": "3.11.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.5.4", "jsonata": "1.5.4",
"lodash": "^4.17.21",
"media-typer": "0.3.0", "media-typer": "0.3.0",
"memorystore": "1.6.0", "memorystore": "1.6.0",
"mqtt": "2.18.0", "mqtt": "2.18.0",

View File

@ -1,99 +1,166 @@
const PayloadValidator = require('../../nodes/PayloadValidator')
const orgEvent = require('./fixtures/data/orgEvent')
const sinon = require('sinon'); const sinon = require('sinon');
const assert = require('assert'); const assert = require('assert');
const PayloadValidator = require('../../nodes/PayloadValidator');
const orgEvent = require('./fixtures/data/orgEvent');
describe.only('Unit: PayloadValidator', () => { describe('Unit: PayloadValidator', () => {
it('Should not log when no changes', () => { it('Should not log when no changes', () => {
const nodeId = 'abc-id' const nodeId = 'abc-id';
const workerId = 'worker-id' const workerId = 'worker-id';
const beforeEvent = orgEvent('before', workerId); const beforeEvent = orgEvent('before', workerId);
const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`); const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`);
payloadValidator.verify(beforeEvent); payloadValidator.verify(beforeEvent);
}); });
it('Should warn when org is overwritten', () => { it('Should warn when org is overwritten and fix it', () => {
const nodeId = 'abc-id' const nodeId = 'abc-id';
const workerId = 'worker-id' const workerId = 'worker-id';
const beforeEvent = orgEvent('before', workerId); const beforeEvent = orgEvent('before', workerId);
errorLogStub = sinon.stub(); const errorLogStub = sinon.stub();
appLogStub = sinon.stub(); const appLogStub = sinon.stub();
beforeEvent.logger.error = errorLogStub; beforeEvent.logger.error = errorLogStub;
beforeEvent.logger.app = { beforeEvent.logger.app = {
platform:{ platform: {
organization: appLogStub organization: appLogStub
} }
}; };
const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`); const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`);
const modifiedEvent = orgEvent('after'); const modifiedEvent = orgEvent('after');
const result = payloadValidator.verify(modifiedEvent);
payloadValidator.verify(modifiedEvent); assert(errorLogStub.callCount === 4);
assert(errorLogStub.callCount === 4) assert(appLogStub.callCount === 4);
assert(appLogStub.callCount === 4) const [[log1], [log2], [log3], [log4]] = appLogStub.args;
const [[log1], [log2], [log3], [log4]] = appLogStub.args assert(log1.details.message.includes('logger.metadata.organization'));
assert(log1.details.message.includes('logger.metadata.organization')) assert.strictEqual(log1.details.nodeId, nodeId);
assert.strictEqual(log1.details.nodeId, nodeId) assert.strictEqual(log1.details.workerId, workerId);
assert.strictEqual(log1.details.workerId, workerId) assert(log2.details.message.includes('payload.system.organization'));
assert(log2.details.message.includes('payload.system.organization')) assert.strictEqual(log2.details.nodeId, nodeId);
assert.strictEqual(log2.details.nodeId, nodeId) assert.strictEqual(log2.details.workerId, workerId);
assert.strictEqual(log2.details.workerId, workerId) assert(log3.details.message.includes('event.event.organization'));
assert(log3.details.message.includes('event.event.organization')) assert.strictEqual(log3.details.nodeId, nodeId);
assert.strictEqual(log3.details.nodeId, nodeId) assert.strictEqual(log3.details.workerId, workerId);
assert.strictEqual(log3.details.workerId, workerId) assert(log4.details.message.includes('event.event.token.contents.organization'));
assert(log4.details.message.includes('event.event.token.contents.organization')) assert.strictEqual(log4.details.nodeId, nodeId);
assert.strictEqual(log4.details.nodeId, nodeId) assert.strictEqual(log4.details.workerId, workerId);
assert.strictEqual(log4.details.workerId, workerId) // deleting due to sinon funness
delete result.logger.error;
delete result.logger.app;
delete beforeEvent.logger.error;
delete beforeEvent.logger.app;
assert.deepStrictEqual(result, beforeEvent);
}); });
it('Should warn when org is overwritten and fail to fix it due to overwriting payload', () => {
it('Should warn when org is deleted', () => { const nodeId = 'abc-id';
const nodeId = 'abc-id' const workerId = 'worker-id';
const workerId = 'worker-id'
const beforeEvent = orgEvent('before', workerId); const beforeEvent = orgEvent('before', workerId);
errorLogStub = sinon.stub(); const errorLogStub = sinon.stub();
appLogStub = sinon.stub(); const appLogStub = sinon.stub();
beforeEvent.logger.error = errorLogStub; beforeEvent.logger.error = errorLogStub;
beforeEvent.logger.app = { beforeEvent.logger.app = {
platform:{ platform: {
organization: appLogStub organization: appLogStub
} }
}; };
const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`); const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`);
const modifiedEvent = orgEvent('before');
modifiedEvent.payload = 'some text';
const result = payloadValidator.verify(modifiedEvent);
assert(errorLogStub.callCount === 1);
assert(appLogStub.callCount === 1);
const [[log]] = appLogStub.args;
assert(log.details.message.includes('msg.payload.system.organization'));
assert.strictEqual(log.details.nodeId, nodeId);
assert.strictEqual(log.details.workerId, workerId);
// deleting due to sinon funness
delete result.logger.error;
delete result.logger.app;
delete modifiedEvent.logger.error;
delete modifiedEvent.logger.app;
assert.deepStrictEqual(result, modifiedEvent);
});
it('Should warn when org is deleted and fix it', () => {
const nodeId = 'abc-id';
const workerId = 'worker-id';
const beforeEvent = orgEvent('before', workerId);
const errorLogStub = sinon.stub();
const appLogStub = sinon.stub();
beforeEvent.logger.error = errorLogStub;
beforeEvent.logger.app = {
platform: {
organization: appLogStub
}
};
const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`);
delete beforeEvent.logger.metadata.organization; delete beforeEvent.logger.metadata.organization;
delete beforeEvent.payload.system.organization; delete beforeEvent.payload.system.organization;
delete beforeEvent.event.event.organization; delete beforeEvent.event.event.organization;
delete beforeEvent.event.event.token.contents.organization; delete beforeEvent.event.event.token.contents.organization;
payloadValidator.verify(beforeEvent); const result = payloadValidator.verify(beforeEvent);
assert(errorLogStub.callCount === 4) assert(errorLogStub.callCount === 4);
assert(appLogStub.callCount === 4) assert(appLogStub.callCount === 4);
const [[log1], [log2], [log3], [log4]] = appLogStub.args const [[log1], [log2], [log3], [log4]] = appLogStub.args;
assert(log1.details.message.includes('logger.metadata.organization')) assert(log1.details.message.includes('logger.metadata.organization'));
assert.strictEqual(log1.details.nodeId, nodeId) assert.strictEqual(log1.details.nodeId, nodeId);
assert.strictEqual(log1.details.workerId, workerId) assert.strictEqual(log1.details.workerId, workerId);
assert(log2.details.message.includes('payload.system.organization')) assert(log2.details.message.includes('payload.system.organization'));
assert.strictEqual(log2.details.nodeId, nodeId) assert.strictEqual(log2.details.nodeId, nodeId);
assert.strictEqual(log2.details.workerId, workerId) assert.strictEqual(log2.details.workerId, workerId);
assert(log3.details.message.includes('event.event.organization')) assert(log3.details.message.includes('event.event.organization'));
assert.strictEqual(log3.details.nodeId, nodeId) assert.strictEqual(log3.details.nodeId, nodeId);
assert.strictEqual(log3.details.workerId, workerId) assert.strictEqual(log3.details.workerId, workerId);
assert(log4.details.message.includes('event.event.token.contents.organization')) assert(log4.details.message.includes('event.event.token.contents.organization'));
assert.strictEqual(log4.details.nodeId, nodeId) assert.strictEqual(log4.details.nodeId, nodeId);
assert.strictEqual(log4.details.workerId, workerId) assert.strictEqual(log4.details.workerId, workerId);
// deleting due to sinon funness
delete result.logger.error;
delete result.logger.app;
delete beforeEvent.logger.error;
delete beforeEvent.logger.app;
assert.deepStrictEqual(result, beforeEvent);
}); });
it('Should handle when after is an array', () =>{
const nodeId = 'abc-id' it('Should handle when after is an array and not find any issues', () => {
const workerId = 'worker-id' const nodeId = 'abc-id';
const workerId = 'worker-id';
const beforeEvent = orgEvent('before', workerId); const beforeEvent = orgEvent('before', workerId);
errorLogStub = sinon.stub(); const errorLogStub = sinon.stub();
appLogStub = sinon.stub(); const appLogStub = sinon.stub();
beforeEvent.logger.error = errorLogStub; beforeEvent.logger.error = errorLogStub;
beforeEvent.logger.app = { beforeEvent.logger.app = {
platform:{ platform: {
organization: appLogStub
}
};
const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`);
const after = orgEvent('before');
const [result] = payloadValidator.verify([after]);
// deleting due to sinon funness
delete result.logger.error;
delete result.logger.app;
delete beforeEvent.logger.error;
delete beforeEvent.logger.app;
assert.deepStrictEqual(result, beforeEvent);
});
it('Should handle when after is an array', () => {
const nodeId = 'abc-id';
const workerId = 'worker-id';
const beforeEvent = orgEvent('before', workerId);
const errorLogStub = sinon.stub();
const appLogStub = sinon.stub();
beforeEvent.logger.error = errorLogStub;
beforeEvent.logger.app = {
platform: {
organization: appLogStub organization: appLogStub
} }
}; };
@ -101,25 +168,31 @@ describe.only('Unit: PayloadValidator', () => {
const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`); const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`);
const modifiedEvent = orgEvent('before'); const modifiedEvent = orgEvent('before');
modifiedEvent.logger.metadata.organization = 'after' modifiedEvent.logger.metadata.organization = 'after';
payloadValidator.verify([modifiedEvent]); const [result] = payloadValidator.verify([modifiedEvent]);
assert(errorLogStub.callCount === 1) assert(errorLogStub.callCount === 1);
assert(appLogStub.callCount === 1) assert(appLogStub.callCount === 1);
const [[log1]] = appLogStub.args const [[log1]] = appLogStub.args;
assert(log1.details.message.includes('logger.metadata.organization')) assert(log1.details.message.includes('logger.metadata.organization'));
assert.strictEqual(log1.details.nodeId, nodeId) assert.strictEqual(log1.details.nodeId, nodeId);
assert.strictEqual(log1.details.workerId, workerId) assert.strictEqual(log1.details.workerId, workerId);
}) // deleting due to sinon funness
delete result.logger.error;
delete result.logger.app;
delete beforeEvent.logger.error;
delete beforeEvent.logger.app;
assert.deepStrictEqual(result, beforeEvent);
});
it('Should handle when after is an array in something other than first position', () =>{ it('Should handle when after is an array in something other than first position', () => {
const nodeId = 'abc-id' const nodeId = 'abc-id';
const workerId = 'worker-id' const workerId = 'worker-id';
const beforeEvent = orgEvent('before', workerId); const beforeEvent = orgEvent('before', workerId);
errorLogStub = sinon.stub(); const errorLogStub = sinon.stub();
appLogStub = sinon.stub(); const appLogStub = sinon.stub();
beforeEvent.logger.error = errorLogStub; beforeEvent.logger.error = errorLogStub;
beforeEvent.logger.app = { beforeEvent.logger.app = {
platform:{ platform: {
organization: appLogStub organization: appLogStub
} }
}; };
@ -127,23 +200,20 @@ describe.only('Unit: PayloadValidator', () => {
const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`); const payloadValidator = new PayloadValidator(beforeEvent, `before-${workerId}-${nodeId}`);
const modifiedEvent = orgEvent('before'); const modifiedEvent = orgEvent('before');
modifiedEvent.logger.metadata.organization = 'after' modifiedEvent.logger.metadata.organization = 'after';
payloadValidator.verify([null, null, modifiedEvent, null]); const [,, result] = payloadValidator.verify([null, null, modifiedEvent, null]);
assert(errorLogStub.callCount === 1) assert(errorLogStub.callCount === 1);
assert(appLogStub.callCount === 1) assert(appLogStub.callCount === 1);
const [[log1]] = appLogStub.args const [[log1]] = appLogStub.args;
assert(log1.details.message.includes('logger.metadata.organization')) assert(log1.details.message.includes('logger.metadata.organization'));
assert.strictEqual(log1.details.nodeId, nodeId) assert.strictEqual(log1.details.nodeId, nodeId);
assert.strictEqual(log1.details.workerId, workerId) assert.strictEqual(log1.details.workerId, workerId);
}) // deleting due to sinon funness
delete result.logger.error;
it('Should not die when error', () => { delete result.logger.app;
const beforeEvent = orgEvent('before'); delete beforeEvent.logger.error;
const payloadValidator = new PayloadValidator(beforeEvent); delete beforeEvent.logger.app;
assert.deepStrictEqual(result, beforeEvent);
const modifiedEvent = orgEvent('after');
payloadValidator.verify(modifiedEvent);
}); });
it('Should not die with initiating the class with bad object', () => { it('Should not die with initiating the class with bad object', () => {
@ -155,5 +225,58 @@ describe.only('Unit: PayloadValidator', () => {
payloadValidator.verify({}); payloadValidator.verify({});
}); });
describe('Get Value', () => {
let payloadValidator;
before(() => {
const event = orgEvent('event');
payloadValidator = new PayloadValidator(event, 'before-worker-id-nodeId');
});
it('Should set a root level variable', () => {
const location = 'hello';
const value = 'world';
const object = {
[location]: value
};
const result = payloadValidator.getValue(object, location);
assert.strictEqual(result, value);
});
it('Should set a nested variable', () => {
const value = 'world';
const object = {
should: {
have: {
hello: value
}
}
};
const location = 'should.have.hello';
const result = payloadValidator.getValue(object, location);
assert.strictEqual(result, value);
});
});
describe('Set Value', () => {
let payloadValidator;
before(() => {
const event = orgEvent('event');
payloadValidator = new PayloadValidator(event, 'before-worker-id-nodeId');
});
it('Should set a root level variable', () => {
const object = {};
const location = 'hello';
const value = 'world';
const result = payloadValidator.setValue(object, location, value);
assert.strictEqual(result[location], value);
});
it('Should set a nested variable', () => {
const object = {};
const location = 'should.set.hello';
const value = 'world';
const result = payloadValidator.setValue(object, location, value);
assert.strictEqual(result.should.set.hello, value);
});
});
}); });