From 53e69835ffb1eff5c41cc7bd0faf348bacf11e53 Mon Sep 17 00:00:00 2001 From: d98762625 Date: Fri, 5 Jun 2020 14:42:20 +0100 Subject: [PATCH] Formally disallow flowcontrol operations from being used in bake recipes --- src/node/NodeRecipe.mjs | 8 +- src/node/api.mjs | 37 +++--- src/node/config/scripts/generateNodeIndex.mjs | 5 +- tests/node/tests/nodeApi.mjs | 105 +++++++++--------- 4 files changed, 81 insertions(+), 74 deletions(-) diff --git a/src/node/NodeRecipe.mjs b/src/node/NodeRecipe.mjs index b623f611..e955f76e 100644 --- a/src/node/NodeRecipe.mjs +++ b/src/node/NodeRecipe.mjs @@ -28,16 +28,22 @@ class NodeRecipe { * @param {String | Function | Object} ing */ _validateIngredient(ing) { + // CASE operation name given. Find operation and validate if (typeof ing === "string") { const op = operations.find((op) => { return sanitise(op.opName) === sanitise(ing); }); if (op) { - return op; + return this._validateIngredient(op); } else { throw new TypeError(`Couldn't find an operation with name '${ing}'.`); } + // CASE operation given. Check its a chef operation and check its not flowcontrol } else if (typeof ing === "function") { + if (ing.flowControl) { + throw new TypeError(`flowControl operations like ${ing.opName} are not currently allowed in recipes for chef.bake`); + } + if (operations.includes(ing)) { return ing; } else { diff --git a/src/node/api.mjs b/src/node/api.mjs index 1ac8e42b..7a8f8fdc 100644 --- a/src/node/api.mjs +++ b/src/node/api.mjs @@ -177,6 +177,7 @@ export function _wrap(OpClass) { // Check to see if class's run function is async. const opInstance = new OpClass(); const isAsync = opInstance.run.constructor.name === "AsyncFunction"; + const isFlowControl = opInstance.flowControl; let wrapped; @@ -192,8 +193,9 @@ export function _wrap(OpClass) { wrapped = async (input, args=null) => { const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args); - // SPECIAL CASE for Magic. - if (opInstance.flowControl) { + // SPECIAL CASE for Magic. Other flowControl operations will + // not work because the opList is not passed through. + if (isFlowControl) { opInstance.ingValues = transformedArgs; const state = { @@ -241,6 +243,8 @@ export function _wrap(OpClass) { // used in chef.help wrapped.opName = OpClass.name; wrapped.args = createArgInfo(opInstance); + // Used in NodeRecipe to check for flowControl ops + wrapped.flowControl = isFlowControl; return wrapped; } @@ -315,25 +319,18 @@ export function help(input) { /** - * bake [Wrapped] - Perform an array of operations on some input. - * @returns {Function} + * bake + * + * @param {*} input - some input for a recipe. + * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig - + * An operation, operation name, or an array of either. + * @returns {NodeDish} of the result + * @throws {TypeError} if invalid recipe given. */ -export function bake() { - - /** - * bake - * - * @param {*} input - some input for a recipe. - * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig - - * An operation, operation name, or an array of either. - * @returns {SyncDish} of the result - * @throws {TypeError} if invalid recipe given. - */ - return function(input, recipeConfig) { - const recipe = new NodeRecipe(recipeConfig); - const dish = ensureIsDish(input); - return recipe.execute(dish); - }; +export function bake(input, recipeConfig) { + const recipe = new NodeRecipe(recipeConfig); + const dish = ensureIsDish(input); + return recipe.execute(dish); } diff --git a/src/node/config/scripts/generateNodeIndex.mjs b/src/node/config/scripts/generateNodeIndex.mjs index 81ca8fe6..13c9a842 100644 --- a/src/node/config/scripts/generateNodeIndex.mjs +++ b/src/node/config/scripts/generateNodeIndex.mjs @@ -100,8 +100,7 @@ Object.keys(operations).forEach((op) => { code += `]; -const prebaked = bake(operations); -chef.bake = prebaked; +chef.bake = bake; export default chef; // Operations as top level exports. @@ -114,7 +113,7 @@ Object.keys(operations).forEach((op) => { }); code += " NodeDish as Dish,\n"; -code += " prebaked as bake,\n"; +code += " bake,\n"; code += " help,\n"; code += " OperationError,\n"; code += " ExcludedOperationError,\n"; diff --git a/tests/node/tests/nodeApi.mjs b/tests/node/tests/nodeApi.mjs index a4e907b8..2b413f45 100644 --- a/tests/node/tests/nodeApi.mjs +++ b/tests/node/tests/nodeApi.mjs @@ -13,10 +13,10 @@ import assert from "assert"; import it from "../assertionHandler.mjs"; import chef from "../../../src/node/index.mjs"; -import OperationError from "../../../src/core/errors/OperationError.mjs"; +import { OperationError, ExcludedOperationError } from "../../../src/core/errors/index.mjs"; import NodeDish from "../../../src/node/NodeDish.mjs"; -import { toBase32} from "../../../src/node/index.mjs"; +import { toBase32, magic} from "../../../src/node/index.mjs"; import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addApiTests([ @@ -181,22 +181,17 @@ TestRegister.addApiTests([ }), it("chef.bake: should complain if recipe isnt a valid object", () => { - try { - chef.bake("some input", 3264); - } catch (e) { - assert.strictEqual(e.name, "TypeError"); - assert.strictEqual(e.message, "Recipe can only contain function names or functions"); - } + assert.throws(() => chef.bake("some input", 3264), { + name: "TypeError", + message: "Recipe can only contain function names or functions" + }); }), it("chef.bake: Should complain if string op is invalid", () => { - try { - chef.bake("some input", "not a valid operation"); - assert.fail("Shouldn't be hit"); - } catch (e) { - assert.strictEqual(e.name, "TypeError"); - assert.strictEqual(e.message, "Couldn't find an operation with name 'not a valid operation'."); - } + assert.throws(() => chef.bake("some input", "not a valid operation"), { + name: "TypeError", + message: "Couldn't find an operation with name 'not a valid operation'." + }); }), it("chef.bake: Should take an input and an operation and perform it", () => { @@ -205,13 +200,10 @@ TestRegister.addApiTests([ }), it("chef.bake: Should complain if an invalid operation is inputted", () => { - try { - chef.bake("https://google.com/search?q=help", () => {}); - assert.fail("Shouldn't be hit"); - } catch (e) { - assert.strictEqual(e.name, "TypeError"); - assert.strictEqual(e.message, "Inputted function not a Chef operation."); - } + assert.throws(() => chef.bake("https://google.com/search?q=help", () => {}), { + name: "TypeError", + message: "Inputted function not a Chef operation." + }); }), it("chef.bake: accepts an array of operation names and performs them all in order", () => { @@ -241,12 +233,10 @@ TestRegister.addApiTests([ }), it("should complain if an invalid operation is inputted as part of array", () => { - try { - chef.bake("something", [() => {}]); - } catch (e) { - assert.strictEqual(e.name, "TypeError"); - assert.strictEqual(e.message, "Inputted function not a Chef operation."); - } + assert.throws(() => chef.bake("something", [() => {}]), { + name: "TypeError", + message: "Inputted function not a Chef operation." + }); }), it("chef.bake: should take single JSON object describing op and args OBJ", () => { @@ -275,15 +265,13 @@ TestRegister.addApiTests([ }), it("chef.bake: should error if op in JSON is not chef op", () => { - try { - chef.bake("some input", { - op: () => {}, - args: ["Colon"], - }); - } catch (e) { - assert.strictEqual(e.name, "TypeError"); - assert.strictEqual(e.message, "Inputted function not a Chef operation."); - } + assert.throws(() => chef.bake("some input", { + op: () => {}, + args: ["Colon"], + }), { + name: "TypeError", + message: "Inputted function not a Chef operation." + }); }), it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => { @@ -357,22 +345,38 @@ TestRegister.addApiTests([ assert.strictEqual(result.toString(), "begin_something_aaaaaaaaaaaaaa_end_something"); }), - it("Excluded operations: throw a sensible error when you try and call one", () => { - try { - chef.fork(); - } catch (e) { - assert.strictEqual(e.type, "ExcludedOperationError"); - assert.strictEqual(e.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef."); - } + it("chef.bake: cannot accept flowControl operations in recipe", () => { + assert.throws(() => chef.bake("some input", "magic"), { + name: "TypeError", + message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake" + }); + assert.throws(() => chef.bake("some input", magic), { + name: "TypeError", + message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake" + }); + assert.throws(() => chef.bake("some input", ["to base 64", "magic"]), { + name: "TypeError", + message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake" + }); }), it("Excluded operations: throw a sensible error when you try and call one", () => { - try { - chef.renderImage(); - } catch (e) { - assert.strictEqual(e.type, "ExcludedOperationError"); - assert.strictEqual(e.message, "Sorry, the RenderImage operation is not available in the Node.js version of CyberChef."); - } + assert.throws(chef.fork, + (err) => { + assert(err instanceof ExcludedOperationError); + assert.deepEqual(err.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef."); + return true; + }, + "Unexpected error type" + ); + assert.throws(chef.javaScriptBeautify, + (err) => { + assert(err instanceof ExcludedOperationError); + assert.deepEqual(err.message, "Sorry, the JavaScriptBeautify operation is not available in the Node.js version of CyberChef."); + return true; + }, + "Unexpected error type" + ); }), it("Operation arguments: should be accessible from operation object if op has array arg", () => { @@ -405,4 +409,5 @@ TestRegister.addApiTests([ assert.equal(chef.convertDistance.args.inputUnits.options[0], "Nanometres (nm)"); assert.equal(chef.defangURL.args.process.options[1], "Only full URLs"); }), + ]);