diff --git a/x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts b/x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts index 1e33f8b220cff..bd5195423986f 100644 --- a/x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts +++ b/x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts @@ -6,10 +6,16 @@ */ import dedent from 'dedent'; - +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { renderMustacheString } from './mustache_renderer'; +const logger = loggingSystemMock.create().get(); + describe('mustache lambdas', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + describe('FormatDate', () => { it('date with defaults is successful', () => { const timeStamp = '2022-11-29T15:52:44Z'; @@ -17,7 +23,9 @@ describe('mustache lambdas', () => { {{#FormatDate}} {{timeStamp}} {{/FormatDate}} `.trim(); - expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual('2022-11-29 03:52pm'); + expect(renderMustacheString(logger, template, { timeStamp }, 'none')).toEqual( + '2022-11-29 03:52pm' + ); }); it('date with a time zone is successful', () => { @@ -26,7 +34,9 @@ describe('mustache lambdas', () => { {{#FormatDate}} {{timeStamp}} ; America/New_York {{/FormatDate}} `.trim(); - expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual('2022-11-29 10:52am'); + expect(renderMustacheString(logger, template, { timeStamp }, 'none')).toEqual( + '2022-11-29 10:52am' + ); }); it('date with a format is successful', () => { @@ -35,7 +45,7 @@ describe('mustache lambdas', () => { {{#FormatDate}} {{timeStamp}} ;; dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}} `.trim(); - expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual( + expect(renderMustacheString(logger, template, { timeStamp }, 'none')).toEqual( 'Tuesday Nov 29th 2022 15:52:44.000' ); }); @@ -46,41 +56,48 @@ describe('mustache lambdas', () => { {{#FormatDate}} {{timeStamp}};America/New_York;dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}} `.trim(); - expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( + expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual( 'Tuesday Nov 29th 2022 10:52:44.000' ); }); - it('empty date produces error', () => { + it('empty date logs and returns error string', () => { const timeStamp = ''; const template = dedent` {{#FormatDate}} {{/FormatDate}} `.trim(); - expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( - 'error rendering mustache template "{{#FormatDate}} {{/FormatDate}}": date is empty' + expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual( + 'date is empty' ); + expect(logger.warn).toHaveBeenCalledWith(`mustache render error: date is empty`); }); - it('invalid date produces error', () => { + it('invalid date logs and returns error string', () => { const timeStamp = 'this is not a d4t3'; const template = dedent` {{#FormatDate}}{{timeStamp}}{{/FormatDate}} `.trim(); - expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( - 'error rendering mustache template "{{#FormatDate}}{{timeStamp}}{{/FormatDate}}": invalid date "this is not a d4t3"' + expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual( + 'invalid date "this is not a d4t3"' + ); + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: invalid date "this is not a d4t3"` ); }); - it('invalid timezone produces error', () => { + it('invalid timezone logs and returns error string', () => { const timeStamp = '2023-04-10T23:52:39'; const template = dedent` {{#FormatDate}}{{timeStamp}};NotATime Zone!{{/FormatDate}} `.trim(); - expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( - 'error rendering mustache template "{{#FormatDate}}{{timeStamp}};NotATime Zone!{{/FormatDate}}": unknown timeZone value "NotATime Zone!"' + expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual( + 'unknown timeZone value "NotATime Zone!"' + ); + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: unknown timeZone value "NotATime Zone!"` ); }); @@ -92,7 +109,7 @@ describe('mustache lambdas', () => { // not clear how to force an error, it pretty much does something with // ANY string - expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( + expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual( 'gamrbamg2' // a => am/pm (so am here); e => day of week ); }); @@ -114,11 +131,11 @@ describe('mustache lambdas', () => { {{/context}} `.trim(); - const result = renderMustacheString(template, vars, 'none'); + const result = renderMustacheString(logger, template, vars, 'none'); expect(result).toEqual(`1\n2\n3\n`); }); - it('invalid expression produces error', () => { + it('invalid expression logs and returns error string', () => { const vars = { context: { a: { b: 1 }, @@ -129,9 +146,12 @@ describe('mustache lambdas', () => { {{#EvalMath}} ) 1 ++++ 0 ( {{/EvalMath}} `.trim(); - const result = renderMustacheString(template, vars, 'none'); + const result = renderMustacheString(logger, template, vars, 'none'); expect(result).toEqual( - `error rendering mustache template "{{#EvalMath}} ) 1 ++++ 0 ( {{/EvalMath}}": error evaluating tinymath expression ") 1 ++++ 0 (": Failed to parse expression. Expected "(", function, literal, or whitespace but ")" found.` + 'error evaluating tinymath expression ") 1 ++++ 0 (": Failed to parse expression. Expected "(", function, literal, or whitespace but ")" found.' + ); + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: error evaluating tinymath expression ") 1 ++++ 0 (": Failed to parse expression. Expected "(", function, literal, or whitespace but ")" found.` ); }); }); @@ -147,7 +167,7 @@ describe('mustache lambdas', () => { const hjson = ` { # specify rate in requests/second (because comments are helpful!) - rate: 1000 + rate: 1000 a: {{context.a}} a_b: {{context.a.b}} @@ -166,7 +186,7 @@ describe('mustache lambdas', () => { {{#ParseHjson}} ${hjson} {{/ParseHjson}} `.trim(); - const result = renderMustacheString(template, vars, 'none'); + const result = renderMustacheString(logger, template, vars, 'none'); expect(JSON.parse(result)).toMatchInlineSnapshot(` Object { "a": Object { @@ -189,13 +209,18 @@ describe('mustache lambdas', () => { `); }); - it('renders an error message on parse errors', () => { + it('logs an error message and returns error string on parse errors', () => { const template = dedent` {{#ParseHjson}} [1,2,3,,] {{/ParseHjson}} `.trim(); - const result = renderMustacheString(template, {}, 'none'); - expect(result).toMatch(/^error rendering mustache template .*/); + const result = renderMustacheString(logger, template, {}, 'none'); + expect(result).toEqual( + `error parsing Hjson \"[1,2,3,,]\": Found a punctuator character ',' when expecting a quoteless string (check your syntax) at line 1,7 >>>1,2,3,,] ...` + ); + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: error parsing Hjson \"[1,2,3,,]\": Found a punctuator character ',' when expecting a quoteless string (check your syntax) at line 1,7 >>>1,2,3,,] ...` + ); }); }); @@ -206,16 +231,19 @@ describe('mustache lambdas', () => { {{#FormatNumber}} {{num}}; en-US; style: currency, currency: EUR {{/FormatNumber}} `.trim(); - expect(renderMustacheString(template, { num }, 'none')).toEqual('€42.00'); + expect(renderMustacheString(logger, template, { num }, 'none')).toEqual('€42.00'); }); - it('renders an error message on errors', () => { + it('logs an error message and returns empty string on errors', () => { const num = 'nope;;'; const template = dedent` {{#FormatNumber}} {{num}} {{/FormatNumber}} `.trim(); - expect(renderMustacheString(template, { num }, 'none')).toEqual(`invalid number: 'nope'`); + expect(renderMustacheString(logger, template, { num }, 'none')).toEqual( + `invalid number: 'nope'` + ); + expect(logger.warn).toHaveBeenCalledWith(`mustache render error: invalid number: 'nope'`); }); }); }); diff --git a/x-pack/plugins/actions/server/lib/mustache_lambdas.ts b/x-pack/plugins/actions/server/lib/mustache_lambdas.ts index 02ea659785aad..8d4a87556b70f 100644 --- a/x-pack/plugins/actions/server/lib/mustache_lambdas.ts +++ b/x-pack/plugins/actions/server/lib/mustache_lambdas.ts @@ -8,6 +8,7 @@ import * as tinymath from '@kbn/tinymath'; import { parse as hjsonParse } from 'hjson'; import moment from 'moment-timezone'; +import { Logger } from '@kbn/core/server'; import { formatNumber } from './number_formatter'; @@ -16,96 +17,102 @@ type Variables = Record; const DefaultDateTimeZone = 'UTC'; const DefaultDateFormat = 'YYYY-MM-DD hh:mma'; -export function getMustacheLambdas(): Variables { - return getLambdas(); +export function getMustacheLambdas(logger: Logger): Variables { + return getLambdas(logger); } const TimeZoneSet = new Set(moment.tz.names()); type RenderFn = (text: string) => string; -function getLambdas() { +function getLambdas(logger: Logger) { return { EvalMath: () => // mustache invokes lamdas with `this` set to the current "view" (variables) function (this: Variables, text: string, render: RenderFn) { - return evalMath(this, render(text.trim())); + return evalMath(this, render(text.trim()), logger); }, ParseHjson: () => function (text: string, render: RenderFn) { - return parseHjson(render(text.trim())); + return parseHjson(render(text.trim()), logger); }, FormatDate: () => function (text: string, render: RenderFn) { const dateString = render(text.trim()).trim(); - return formatDate(dateString); + return formatDate(dateString, logger); }, FormatNumber: () => function (text: string, render: RenderFn) { const numberString = render(text.trim()).trim(); - return formatNumber(numberString); + return formatNumber(logger, numberString); }, }; } -function evalMath(vars: Variables, o: unknown): string { +function evalMath(vars: Variables, o: unknown, logger: Logger): string { const expr = `${o}`; try { const result = tinymath.evaluate(expr, vars); return `${result}`; } catch (err) { - throw new Error(`error evaluating tinymath expression "${expr}": ${err.message}`); + return logAndReturnErr( + logger, + `error evaluating tinymath expression "${expr}": ${err.message}` + ); } } -function parseHjson(o: unknown): string { +function parseHjson(o: unknown, logger: Logger): string { const hjsonObject = `${o}`; let object: unknown; try { object = hjsonParse(hjsonObject); } catch (err) { - throw new Error(`error parsing Hjson "${hjsonObject}": ${err.message}`); + return logAndReturnErr(logger, `error parsing Hjson "${hjsonObject}": ${err.message}`); } return JSON.stringify(object); } -function formatDate(dateString: unknown): string { +function formatDate(dateString: unknown, logger: Logger): string { const { date, timeZone, format } = splitDateString(`${dateString}`); if (date === '') { - throw new Error(`date is empty`); + return logAndReturnErr(logger, `date is empty`); } if (isNaN(new Date(date).valueOf())) { - throw new Error(`invalid date "${date}"`); + return logAndReturnErr(logger, `invalid date "${date}"`); } let mDate: moment.Moment; try { mDate = moment(date); if (!mDate.isValid()) { - throw new Error(`date is invalid`); + return logAndReturnErr(logger, `invalid date "${date}"`); } } catch (err) { - throw new Error(`error evaluating moment date "${date}": ${err.message}`); + return logAndReturnErr(logger, `error evaluating moment date "${date}": ${err.message}`); } if (!TimeZoneSet.has(timeZone)) { - throw new Error(`unknown timeZone value "${timeZone}"`); + return logAndReturnErr(logger, `unknown timeZone value "${timeZone}"`); } try { mDate.tz(timeZone); } catch (err) { - throw new Error(`error evaluating moment timeZone "${timeZone}": ${err.message}`); + return logAndReturnErr( + logger, + `error evaluating moment timeZone "${timeZone}": ${err.message}` + ); } try { return mDate.format(format); } catch (err) { - throw new Error(`error evaluating moment format "${format}": ${err.message}`); + return logAndReturnErr(logger, `error evaluating moment format "${format}": ${err.message}`); } } @@ -118,3 +125,8 @@ function splitDateString(dateString: string) { format: format || DefaultDateFormat, }; } + +function logAndReturnErr(logger: Logger, errMessage: string): string { + logger.warn(`mustache render error: ${errMessage}`); + return errMessage; +} diff --git a/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts b/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts index 14bd5f47507e7..ed07b6899a318 100644 --- a/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts +++ b/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { renderMustacheString, renderMustacheStringNoEscape, @@ -12,6 +13,8 @@ import { Escape, } from './mustache_renderer'; +const logger = loggingSystemMock.create().get(); + const variables = { a: 1, b: '2', @@ -38,97 +41,121 @@ const variables = { }; describe('mustache_renderer', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + describe('renderMustacheString()', () => { for (const escapeVal of ['none', 'slack', 'markdown', 'json']) { const escape = escapeVal as Escape; it(`handles basic templating that does not need escaping for ${escape}`, () => { - expect(renderMustacheString('', variables, escape)).toBe(''); - expect(renderMustacheString('{{a}}', variables, escape)).toBe('1'); - expect(renderMustacheString('{{b}}', variables, escape)).toBe('2'); - expect(renderMustacheString('{{c}}', variables, escape)).toBe('false'); - expect(renderMustacheString('{{d}}', variables, escape)).toBe(''); - expect(renderMustacheString('{{e}}', variables, escape)).toBe(''); + expect(renderMustacheString(logger, '', variables, escape)).toBe(''); + expect(renderMustacheString(logger, '{{a}}', variables, escape)).toBe('1'); + expect(renderMustacheString(logger, '{{b}}', variables, escape)).toBe('2'); + expect(renderMustacheString(logger, '{{c}}', variables, escape)).toBe('false'); + expect(renderMustacheString(logger, '{{d}}', variables, escape)).toBe(''); + expect(renderMustacheString(logger, '{{e}}', variables, escape)).toBe(''); if (escape === 'json') { - expect(renderMustacheString('{{f}}', variables, escape)).toBe('{\\"g\\":3,\\"h\\":null}'); + expect(renderMustacheString(logger, '{{f}}', variables, escape)).toBe( + '{\\"g\\":3,\\"h\\":null}' + ); } else if (escape === 'markdown') { - expect(renderMustacheString('{{f}}', variables, escape)).toBe('\\{"g":3,"h":null\\}'); + expect(renderMustacheString(logger, '{{f}}', variables, escape)).toBe( + '\\{"g":3,"h":null\\}' + ); } else { - expect(renderMustacheString('{{f}}', variables, escape)).toBe('{"g":3,"h":null}'); + expect(renderMustacheString(logger, '{{f}}', variables, escape)).toBe('{"g":3,"h":null}'); } - expect(renderMustacheString('{{f.g}}', variables, escape)).toBe('3'); - expect(renderMustacheString('{{f.h}}', variables, escape)).toBe(''); - expect(renderMustacheString('{{i}}', variables, escape)).toBe('42,43,44'); + expect(renderMustacheString(logger, '{{f.g}}', variables, escape)).toBe('3'); + expect(renderMustacheString(logger, '{{f.h}}', variables, escape)).toBe(''); + expect(renderMustacheString(logger, '{{i}}', variables, escape)).toBe('42,43,44'); if (escape === 'markdown') { - expect(renderMustacheString('{{i.asJSON}}', variables, escape)).toBe('\\[42,43,44\\]'); + expect(renderMustacheString(logger, '{{i.asJSON}}', variables, escape)).toBe( + '\\[42,43,44\\]' + ); } else { - expect(renderMustacheString('{{i.asJSON}}', variables, escape)).toBe('[42,43,44]'); + expect(renderMustacheString(logger, '{{i.asJSON}}', variables, escape)).toBe( + '[42,43,44]' + ); } }); } it('handles escape:none with commonly escaped strings', () => { - expect(renderMustacheString('{{lt}}', variables, 'none')).toBe(variables.lt); - expect(renderMustacheString('{{gt}}', variables, 'none')).toBe(variables.gt); - expect(renderMustacheString('{{amp}}', variables, 'none')).toBe(variables.amp); - expect(renderMustacheString('{{nl}}', variables, 'none')).toBe(variables.nl); - expect(renderMustacheString('{{dq}}', variables, 'none')).toBe(variables.dq); - expect(renderMustacheString('{{bt}}', variables, 'none')).toBe(variables.bt); - expect(renderMustacheString('{{bs}}', variables, 'none')).toBe(variables.bs); - expect(renderMustacheString('{{st}}', variables, 'none')).toBe(variables.st); - expect(renderMustacheString('{{ul}}', variables, 'none')).toBe(variables.ul); + expect(renderMustacheString(logger, '{{lt}}', variables, 'none')).toBe(variables.lt); + expect(renderMustacheString(logger, '{{gt}}', variables, 'none')).toBe(variables.gt); + expect(renderMustacheString(logger, '{{amp}}', variables, 'none')).toBe(variables.amp); + expect(renderMustacheString(logger, '{{nl}}', variables, 'none')).toBe(variables.nl); + expect(renderMustacheString(logger, '{{dq}}', variables, 'none')).toBe(variables.dq); + expect(renderMustacheString(logger, '{{bt}}', variables, 'none')).toBe(variables.bt); + expect(renderMustacheString(logger, '{{bs}}', variables, 'none')).toBe(variables.bs); + expect(renderMustacheString(logger, '{{st}}', variables, 'none')).toBe(variables.st); + expect(renderMustacheString(logger, '{{ul}}', variables, 'none')).toBe(variables.ul); }); it('handles escape:markdown with commonly escaped strings', () => { - expect(renderMustacheString('{{lt}}', variables, 'markdown')).toBe(variables.lt); - expect(renderMustacheString('{{gt}}', variables, 'markdown')).toBe(variables.gt); - expect(renderMustacheString('{{amp}}', variables, 'markdown')).toBe(variables.amp); - expect(renderMustacheString('{{nl}}', variables, 'markdown')).toBe(variables.nl); - expect(renderMustacheString('{{dq}}', variables, 'markdown')).toBe(variables.dq); - expect(renderMustacheString('{{bt}}', variables, 'markdown')).toBe('\\' + variables.bt); - expect(renderMustacheString('{{bs}}', variables, 'markdown')).toBe('\\' + variables.bs); - expect(renderMustacheString('{{st}}', variables, 'markdown')).toBe('\\' + variables.st); - expect(renderMustacheString('{{ul}}', variables, 'markdown')).toBe('\\' + variables.ul); - expect(renderMustacheString('{{vl}}', variables, 'markdown')).toBe('\\' + variables.vl); + expect(renderMustacheString(logger, '{{lt}}', variables, 'markdown')).toBe(variables.lt); + expect(renderMustacheString(logger, '{{gt}}', variables, 'markdown')).toBe(variables.gt); + expect(renderMustacheString(logger, '{{amp}}', variables, 'markdown')).toBe(variables.amp); + expect(renderMustacheString(logger, '{{nl}}', variables, 'markdown')).toBe(variables.nl); + expect(renderMustacheString(logger, '{{dq}}', variables, 'markdown')).toBe(variables.dq); + expect(renderMustacheString(logger, '{{bt}}', variables, 'markdown')).toBe( + '\\' + variables.bt + ); + expect(renderMustacheString(logger, '{{bs}}', variables, 'markdown')).toBe( + '\\' + variables.bs + ); + expect(renderMustacheString(logger, '{{st}}', variables, 'markdown')).toBe( + '\\' + variables.st + ); + expect(renderMustacheString(logger, '{{ul}}', variables, 'markdown')).toBe( + '\\' + variables.ul + ); + expect(renderMustacheString(logger, '{{vl}}', variables, 'markdown')).toBe( + '\\' + variables.vl + ); }); it('handles triple escapes', () => { - expect(renderMustacheString('{{{bt}}}', variables, 'markdown')).toBe(variables.bt); - expect(renderMustacheString('{{{bs}}}', variables, 'markdown')).toBe(variables.bs); - expect(renderMustacheString('{{{st}}}', variables, 'markdown')).toBe(variables.st); - expect(renderMustacheString('{{{ul}}}', variables, 'markdown')).toBe(variables.ul); + expect(renderMustacheString(logger, '{{{bt}}}', variables, 'markdown')).toBe(variables.bt); + expect(renderMustacheString(logger, '{{{bs}}}', variables, 'markdown')).toBe(variables.bs); + expect(renderMustacheString(logger, '{{{st}}}', variables, 'markdown')).toBe(variables.st); + expect(renderMustacheString(logger, '{{{ul}}}', variables, 'markdown')).toBe(variables.ul); }); it('handles escape:slack with commonly escaped strings', () => { - expect(renderMustacheString('{{lt}}', variables, 'slack')).toBe('<'); - expect(renderMustacheString('{{gt}}', variables, 'slack')).toBe('>'); - expect(renderMustacheString('{{amp}}', variables, 'slack')).toBe('&'); - expect(renderMustacheString('{{nl}}', variables, 'slack')).toBe(variables.nl); - expect(renderMustacheString('{{dq}}', variables, 'slack')).toBe(variables.dq); - expect(renderMustacheString('{{bt}}', variables, 'slack')).toBe(`'`); - expect(renderMustacheString('{{bs}}', variables, 'slack')).toBe(variables.bs); - expect(renderMustacheString('{{st}}', variables, 'slack')).toBe('`*`'); - expect(renderMustacheString('{{ul}}', variables, 'slack')).toBe('`_`'); + expect(renderMustacheString(logger, '{{lt}}', variables, 'slack')).toBe('<'); + expect(renderMustacheString(logger, '{{gt}}', variables, 'slack')).toBe('>'); + expect(renderMustacheString(logger, '{{amp}}', variables, 'slack')).toBe('&'); + expect(renderMustacheString(logger, '{{nl}}', variables, 'slack')).toBe(variables.nl); + expect(renderMustacheString(logger, '{{dq}}', variables, 'slack')).toBe(variables.dq); + expect(renderMustacheString(logger, '{{bt}}', variables, 'slack')).toBe(`'`); + expect(renderMustacheString(logger, '{{bs}}', variables, 'slack')).toBe(variables.bs); + expect(renderMustacheString(logger, '{{st}}', variables, 'slack')).toBe('`*`'); + expect(renderMustacheString(logger, '{{ul}}', variables, 'slack')).toBe('`_`'); // html escapes not needed when using backtic escaping - expect(renderMustacheString('{{st_lt}}', variables, 'slack')).toBe('`*<`'); - expect(renderMustacheString('{{link}}', variables, 'slack')).toBe('https://te_st.com/'); + expect(renderMustacheString(logger, '{{st_lt}}', variables, 'slack')).toBe('`*<`'); + expect(renderMustacheString(logger, '{{link}}', variables, 'slack')).toBe( + 'https://te_st.com/' + ); }); it('handles escape:json with commonly escaped strings', () => { - expect(renderMustacheString('{{lt}}', variables, 'json')).toBe(variables.lt); - expect(renderMustacheString('{{gt}}', variables, 'json')).toBe(variables.gt); - expect(renderMustacheString('{{amp}}', variables, 'json')).toBe(variables.amp); - expect(renderMustacheString('{{nl}}', variables, 'json')).toBe('\\n'); - expect(renderMustacheString('{{dq}}', variables, 'json')).toBe('\\"'); - expect(renderMustacheString('{{bt}}', variables, 'json')).toBe(variables.bt); - expect(renderMustacheString('{{bs}}', variables, 'json')).toBe('\\\\'); - expect(renderMustacheString('{{st}}', variables, 'json')).toBe(variables.st); - expect(renderMustacheString('{{ul}}', variables, 'json')).toBe(variables.ul); + expect(renderMustacheString(logger, '{{lt}}', variables, 'json')).toBe(variables.lt); + expect(renderMustacheString(logger, '{{gt}}', variables, 'json')).toBe(variables.gt); + expect(renderMustacheString(logger, '{{amp}}', variables, 'json')).toBe(variables.amp); + expect(renderMustacheString(logger, '{{nl}}', variables, 'json')).toBe('\\n'); + expect(renderMustacheString(logger, '{{dq}}', variables, 'json')).toBe('\\"'); + expect(renderMustacheString(logger, '{{bt}}', variables, 'json')).toBe(variables.bt); + expect(renderMustacheString(logger, '{{bs}}', variables, 'json')).toBe('\\\\'); + expect(renderMustacheString(logger, '{{st}}', variables, 'json')).toBe(variables.st); + expect(renderMustacheString(logger, '{{ul}}', variables, 'json')).toBe(variables.ul); }); it('handles errors', () => { - expect(renderMustacheString('{{a}', variables, 'none')).toMatchInlineSnapshot( + expect(renderMustacheString(logger, '{{a}', variables, 'none')).toMatchInlineSnapshot( `"error rendering mustache template \\"{{a}\\": Unclosed tag at 4"` ); }); @@ -285,7 +312,7 @@ describe('mustache_renderer', () => { describe('renderMustacheObject()', () => { it('handles deep objects', () => { - expect(renderMustacheObject(object, variables)).toMatchInlineSnapshot(` + expect(renderMustacheObject(logger, object, variables)).toMatchInlineSnapshot(` Object { "list": Array [ "1", @@ -311,12 +338,12 @@ describe('mustache_renderer', () => { }); it('handles primitive objects', () => { - expect(renderMustacheObject(undefined, variables)).toMatchInlineSnapshot(`undefined`); - expect(renderMustacheObject(null, variables)).toMatchInlineSnapshot(`null`); - expect(renderMustacheObject(0, variables)).toMatchInlineSnapshot(`0`); - expect(renderMustacheObject(true, variables)).toMatchInlineSnapshot(`true`); - expect(renderMustacheObject('{{a}}', variables)).toMatchInlineSnapshot(`"1"`); - expect(renderMustacheObject(['{{a}}'], variables)).toMatchInlineSnapshot(` + expect(renderMustacheObject(logger, undefined, variables)).toMatchInlineSnapshot(`undefined`); + expect(renderMustacheObject(logger, null, variables)).toMatchInlineSnapshot(`null`); + expect(renderMustacheObject(logger, 0, variables)).toMatchInlineSnapshot(`0`); + expect(renderMustacheObject(logger, true, variables)).toMatchInlineSnapshot(`true`); + expect(renderMustacheObject(logger, '{{a}}', variables)).toMatchInlineSnapshot(`"1"`); + expect(renderMustacheObject(logger, ['{{a}}'], variables)).toMatchInlineSnapshot(` Array [ "1", ] @@ -324,7 +351,7 @@ describe('mustache_renderer', () => { }); it('handles errors', () => { - expect(renderMustacheObject({ a: '{{a}' }, variables)).toMatchInlineSnapshot(` + expect(renderMustacheObject(logger, { a: '{{a}' }, variables)).toMatchInlineSnapshot(` Object { "a": "error rendering mustache template \\"{{a}\\": Unclosed tag at 4", } @@ -338,7 +365,7 @@ describe('mustache_renderer', () => { b: { c: 2, d: [3, 4] }, e: [5, { f: 6, g: 7 }], }; - expect(renderMustacheObject({ x: '{{a}} - {{b}} -- {{e}} ' }, deepVariables)) + expect(renderMustacheObject(logger, { x: '{{a}} - {{b}} -- {{e}} ' }, deepVariables)) .toMatchInlineSnapshot(` Object { "x": "1 - {\\"c\\":2,\\"d\\":[3,4]} -- 5,{\\"f\\":6,\\"g\\":7} ", @@ -346,10 +373,12 @@ describe('mustache_renderer', () => { `); const expected = '1 - {"c":2,"d":[3,4]} -- 5,{"f":6,"g":7}'; - expect(renderMustacheString('{{a}} - {{b}} -- {{e}}', deepVariables, 'none')).toEqual(expected); + expect(renderMustacheString(logger, '{{a}} - {{b}} -- {{e}}', deepVariables, 'none')).toEqual( + expected + ); - expect(renderMustacheString('{{e}}', deepVariables, 'none')).toEqual('5,{"f":6,"g":7}'); - expect(renderMustacheString('{{e.asJSON}}', deepVariables, 'none')).toEqual( + expect(renderMustacheString(logger, '{{e}}', deepVariables, 'none')).toEqual('5,{"f":6,"g":7}'); + expect(renderMustacheString(logger, '{{e.asJSON}}', deepVariables, 'none')).toEqual( '[5,{"f":6,"g":7}]' ); }); @@ -395,6 +424,7 @@ describe('mustache_renderer', () => { expect( renderMustacheObject( + logger, { x: '{{context.0._source.kibana.alert.rule.category}} - {{context.0._score.test}} - {{context.0._source.kibana.alert.time_range.gte}}', }, @@ -408,6 +438,7 @@ describe('mustache_renderer', () => { expect( renderMustacheString( + logger, '{{context.0._source.kibana.alert.rule.category}} - {{context.0._score.test}} - {{context.0._source.kibana.alert.time_range.gte}}', dotVariables, 'none' @@ -416,12 +447,13 @@ describe('mustache_renderer', () => { }); it('should replace single value with the object', () => { - expect(renderMustacheObject({ x: '{{a}}' }, { a: 1, 'a.b': 2 })).toMatchInlineSnapshot(` + expect(renderMustacheObject(logger, { x: '{{a}}' }, { a: 1, 'a.b': 2 })) + .toMatchInlineSnapshot(` Object { "x": "{\\"b\\":2}", } `); - expect(renderMustacheString('{{a}}', { a: 1, 'a.b': 2 }, 'none')).toEqual('{"b":2}'); + expect(renderMustacheString(logger, '{{a}}', { a: 1, 'a.b': 2 }, 'none')).toEqual('{"b":2}'); }); }); }); diff --git a/x-pack/plugins/actions/server/lib/mustache_renderer.ts b/x-pack/plugins/actions/server/lib/mustache_renderer.ts index c478d7e9ea1c3..bd35e499fb426 100644 --- a/x-pack/plugins/actions/server/lib/mustache_renderer.ts +++ b/x-pack/plugins/actions/server/lib/mustache_renderer.ts @@ -7,6 +7,7 @@ import Mustache from 'mustache'; import { isString, isPlainObject, cloneDeepWith, merge } from 'lodash'; +import { Logger } from '@kbn/core/server'; import { getMustacheLambdas } from './mustache_lambdas'; export type Escape = 'markdown' | 'slack' | 'json' | 'none'; @@ -25,9 +26,14 @@ export function renderMustacheStringNoEscape(string: string, variables: Variable } // return a rendered mustache template given the specified variables and escape -export function renderMustacheString(string: string, variables: Variables, escape: Escape): string { +export function renderMustacheString( + logger: Logger, + string: string, + variables: Variables, + escape: Escape +): string { const augmentedVariables = augmentObjectVariables(variables); - const lambdas = getMustacheLambdas(); + const lambdas = getMustacheLambdas(logger); const previousMustacheEscape = Mustache.escape; Mustache.escape = getEscape(escape); @@ -43,13 +49,17 @@ export function renderMustacheString(string: string, variables: Variables, escap } // return a cloned object with all strings rendered as mustache templates -export function renderMustacheObject(params: Params, variables: Variables): Params { +export function renderMustacheObject( + logger: Logger, + params: Params, + variables: Variables +): Params { const augmentedVariables = augmentObjectVariables(variables); const result = cloneDeepWith(params, (value: unknown) => { if (!isString(value)) return; // since we're rendering a JS object, no escaping needed - return renderMustacheString(value, augmentedVariables, 'none'); + return renderMustacheString(logger, value, augmentedVariables, 'none'); }); // The return type signature for `cloneDeep()` ends up taking the return diff --git a/x-pack/plugins/actions/server/lib/number_formatter.test.ts b/x-pack/plugins/actions/server/lib/number_formatter.test.ts index e054b5a7e6ebe..c8ab55119a343 100644 --- a/x-pack/plugins/actions/server/lib/number_formatter.test.ts +++ b/x-pack/plugins/actions/server/lib/number_formatter.test.ts @@ -5,52 +5,76 @@ * 2.0. */ +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { formatNumber } from './number_formatter'; +const logger = loggingSystemMock.create().get(); + describe('formatNumber()', () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + it('using defaults is successful', () => { - expect(formatNumber('1;;')).toMatchInlineSnapshot(`"1"`); + expect(formatNumber(logger, '1;;')).toMatchInlineSnapshot(`"1"`); }); it('error cases handled', () => { - expect(formatNumber('1')).toMatchInlineSnapshot(`"invalid format, missing semicolons: '1'"`); - expect(formatNumber('nope;;')).toMatchInlineSnapshot(`"invalid number: 'nope'"`); - expect(formatNumber('1;; nah')).toMatchInlineSnapshot( - `"invalid options: missing colon in option: 'nah'"` + expect(formatNumber(logger, '1')).toEqual(`invalid format, missing semicolons: '1'`); + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: invalid format, missing semicolons: '1'` + ); + + expect(formatNumber(logger, 'nope;;')).toEqual(`invalid number: 'nope'`); + expect(logger.warn).toHaveBeenCalledWith(`mustache render error: invalid number: 'nope'`); + + expect(formatNumber(logger, '1;; nah')).toEqual( + `invalid options: missing colon in option: 'nah'` ); - expect(formatNumber('1;; minimumIntegerDigits: N.O.')).toMatchInlineSnapshot( - `"error formatting number: minimumIntegerDigits value is out of range."` + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: invalid options: missing colon in option: 'nah'` + ); + + expect(formatNumber(logger, '1;; minimumIntegerDigits: N.O.')).toEqual( + 'error formatting number: minimumIntegerDigits value is out of range.' ); - expect(formatNumber('1;; compactDisplay: uhuh')).toMatchInlineSnapshot( - `"error formatting number: Value uhuh out of range for Intl.NumberFormat options property compactDisplay"` + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: error formatting number: minimumIntegerDigits value is out of range.` + ); + + expect(formatNumber(logger, '1;; compactDisplay: uhuh')).toEqual( + 'error formatting number: Value uhuh out of range for Intl.NumberFormat options property compactDisplay' + ); + expect(logger.warn).toHaveBeenCalledWith( + `mustache render error: error formatting number: Value uhuh out of range for Intl.NumberFormat options property compactDisplay` ); }); it('using locales is successful', () => { - expect(formatNumber('1000; de-DE;')).toMatchInlineSnapshot(`"1.000"`); + expect(formatNumber(logger, '1000; de-DE;')).toMatchInlineSnapshot(`"1.000"`); }); it('option compactDisplay is successful', () => { expect( - formatNumber(' 1000;; notation: compact, compactDisplay: short, ') + formatNumber(logger, ' 1000;; notation: compact, compactDisplay: short, ') ).toMatchInlineSnapshot(`"1K"`); }); it('option currency is successful', () => { - expect(formatNumber('1000;; currency: EUR, style: currency')).toMatchInlineSnapshot( + expect(formatNumber(logger, '1000;; currency: EUR, style: currency')).toMatchInlineSnapshot( `"€1,000.00"` ); }); it('option currencyDisplay is successful', () => { expect( - formatNumber('1000;; currency: EUR, style: currency, currencyDisplay: name') + formatNumber(logger, '1000;; currency: EUR, style: currency, currencyDisplay: name') ).toMatchInlineSnapshot(`"1,000.00 euros"`); }); it('option currencySign is successful', () => { expect( - formatNumber('-1;; currency: EUR, style: currency, currencySign: accounting') + formatNumber(logger, '-1;; currency: EUR, style: currency, currencySign: accounting') ).toMatchInlineSnapshot(`"(€1.00)"`); }); @@ -60,34 +84,36 @@ describe('formatNumber()', () => { it.skip('option localeMatcher is successful', () => {}); it('option notation is successful', () => { - expect(formatNumber('1000;; notation: engineering')).toMatchInlineSnapshot(`"1E3"`); + expect(formatNumber(logger, '1000;; notation: engineering')).toMatchInlineSnapshot(`"1E3"`); }); it('option numberingSystem is successful', () => { - expect(formatNumber('1;; numberingSystem: fullwide')).toMatchInlineSnapshot(`"1"`); + expect(formatNumber(logger, '1;; numberingSystem: fullwide')).toMatchInlineSnapshot(`"1"`); }); it('option signDisplay is successful', () => { - expect(formatNumber('1;; signDisplay: always')).toMatchInlineSnapshot(`"+1"`); + expect(formatNumber(logger, '1;; signDisplay: always')).toMatchInlineSnapshot(`"+1"`); }); it('option style is successful', () => { - expect(formatNumber('1;; style: percent')).toMatchInlineSnapshot(`"100%"`); + expect(formatNumber(logger, '1;; style: percent')).toMatchInlineSnapshot(`"100%"`); }); it('option unit is successful', () => { - expect(formatNumber('1;; style: unit, unit: acre-per-liter')).toMatchInlineSnapshot(`"1 ac/L"`); + expect(formatNumber(logger, '1;; style: unit, unit: acre-per-liter')).toMatchInlineSnapshot( + `"1 ac/L"` + ); }); it('option unitDisplay is successful', () => { expect( - formatNumber('1;; style: unit, unit: petabyte, unitDisplay: narrow') + formatNumber(logger, '1;; style: unit, unit: petabyte, unitDisplay: narrow') ).toMatchInlineSnapshot(`"1PB"`); }); it('option useGrouping is successful', () => { - expect(formatNumber('1000;; useGrouping: true ')).toMatchInlineSnapshot(`"1,000"`); - expect(formatNumber('1000;; useGrouping: false')).toMatchInlineSnapshot(`"1000"`); + expect(formatNumber(logger, '1000;; useGrouping: true ')).toMatchInlineSnapshot(`"1,000"`); + expect(formatNumber(logger, '1000;; useGrouping: false')).toMatchInlineSnapshot(`"1000"`); }); // not yet supported in node.js @@ -103,22 +129,28 @@ describe('formatNumber()', () => { it.skip('option trailingZeroDisplay is successful', () => {}); it('option minimumIntegerDigits is successful', () => { - expect(formatNumber('1;; minimumIntegerDigits: 7')).toMatchInlineSnapshot(`"0,000,001"`); + expect(formatNumber(logger, '1;; minimumIntegerDigits: 7')).toMatchInlineSnapshot( + `"0,000,001"` + ); }); it('option minimumFractionDigits is successful', () => { - expect(formatNumber('1;; minimumFractionDigits: 3')).toMatchInlineSnapshot(`"1.000"`); + expect(formatNumber(logger, '1;; minimumFractionDigits: 3')).toMatchInlineSnapshot(`"1.000"`); }); it('option maximumFractionDigits is successful', () => { - expect(formatNumber('1.234;; maximumFractionDigits: 2')).toMatchInlineSnapshot(`"1.23"`); + expect(formatNumber(logger, '1.234;; maximumFractionDigits: 2')).toMatchInlineSnapshot( + `"1.23"` + ); }); it('option minimumSignificantDigits is successful', () => { - expect(formatNumber('1;; minimumSignificantDigits: 3')).toMatchInlineSnapshot(`"1.00"`); + expect(formatNumber(logger, '1;; minimumSignificantDigits: 3')).toMatchInlineSnapshot(`"1.00"`); }); it('option maximumSignificantDigits is successful', () => { - expect(formatNumber('123456;; maximumSignificantDigits: 4')).toMatchInlineSnapshot(`"123,500"`); + expect(formatNumber(logger, '123456;; maximumSignificantDigits: 4')).toMatchInlineSnapshot( + `"123,500"` + ); }); }); diff --git a/x-pack/plugins/actions/server/lib/number_formatter.ts b/x-pack/plugins/actions/server/lib/number_formatter.ts index b8a13efa39828..f1a2b3367d52f 100644 --- a/x-pack/plugins/actions/server/lib/number_formatter.ts +++ b/x-pack/plugins/actions/server/lib/number_formatter.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { Logger } from '@kbn/core/server'; const DEFAULT_LOCALES = ['en-US']; @@ -30,24 +31,28 @@ const DEFAULT_LOCALES = ['en-US']; * @param numberAndFormat string containing a number and formatting options * @returns number formatted according to the options */ -export function formatNumber(numberLocalesOptions: string): string { +export function formatNumber(logger: Logger, numberLocalesOptions: string): string { const [numString, localesString, optionsString] = splitNumberLocalesOptions(numberLocalesOptions); if (localesString === undefined || optionsString === undefined) { - return `invalid format, missing semicolons: '${numberLocalesOptions}'`; + return logAndReturnErr(logger, `invalid format, missing semicolons: '${numberLocalesOptions}'`); } const num = parseFloat(numString); - if (isNaN(num)) return `invalid number: '${numString}'`; + if (isNaN(num)) { + return logAndReturnErr(logger, `invalid number: '${numString}'`); + } const locales = getLocales(localesString); const [options, optionsError] = getOptions(optionsString); - if (optionsError) return `invalid options: ${optionsError}`; + if (optionsError) { + return logAndReturnErr(logger, `invalid options: ${optionsError}`); + } try { return new Intl.NumberFormat(locales, options).format(num); } catch (err) { - return `error formatting number: ${err.message}`; + return logAndReturnErr(logger, `error formatting number: ${err.message}`); } } @@ -110,3 +115,8 @@ function splitNumberLocalesOptions( const [num, locales, options] = numberLocalesOptions.split(';', 3); return [num.trim(), locales?.trim(), options?.trim()]; } + +function logAndReturnErr(logger: Logger, errMessage: string): string { + logger.warn(`mustache render error: ${errMessage}`); + return errMessage; +} diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 70a2cfd9f8e85..7655ad6de945d 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -59,7 +59,14 @@ export function renderActionParameterTemplatesDefault( params: Record, variables: Record ) { - return renderActionParameterTemplates(undefined, actionTypeId, actionId, params, variables); + return renderActionParameterTemplates( + logger, + undefined, + actionTypeId, + actionId, + params, + variables + ); } const createServicesMock = () => { diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 60a27eb04e411..6479ed1b1d05a 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -585,7 +585,7 @@ export class ActionsPlugin implements Plugin - renderActionParameterTemplates(actionTypeRegistry, ...args), + renderActionParameterTemplates(this.logger, actionTypeRegistry, ...args), }; } @@ -743,6 +743,7 @@ export class ActionsPlugin implements Plugin( + logger: Logger, actionTypeRegistry: ActionTypeRegistry | undefined, actionTypeId: string, actionId: string, @@ -751,8 +752,8 @@ export function renderActionParameterTemplates { const actionId = 'action-id'; const { renderParameterTemplates } = actionTypeRegistry.register.mock.calls[0][0]; - const rendered = renderParameterTemplates?.(params, variables, actionId); + const rendered = renderParameterTemplates?.(logger, params, variables, actionId); - expect(mockRenderParameterTemplates).toHaveBeenCalledWith(params, variables, actionId); + expect(mockRenderParameterTemplates).toHaveBeenCalledWith(logger, params, variables, actionId); expect(rendered).toBe(renderedVariables); }); }); diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 0d9cc99ac6186..343d9b3dde4f6 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -119,6 +119,7 @@ export interface ActionValidationService { } export type RenderParameterTemplates = ( + logger: Logger, params: Params, variables: Record, actionId?: string diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.test.ts index bdbe792115562..08b421b61a7b8 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { renderParameterTemplates } from './render'; import Mustache from 'mustache'; @@ -16,16 +17,20 @@ const params = { }; const variables = { domain: 'm0zepcuuu2' }; +const logger = loggingSystemMock.createLogger(); describe('Bedrock - renderParameterTemplates', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); it('should not render body on test action', () => { const testParams = { subAction: 'test', subActionParams: { body: 'test_json' } }; - const result = renderParameterTemplates(testParams, variables); + const result = renderParameterTemplates(logger, testParams, variables); expect(result).toEqual(testParams); }); it('should rendered body with variables', () => { - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( JSON.stringify({ @@ -39,7 +44,7 @@ describe('Bedrock - renderParameterTemplates', () => { jest.spyOn(Mustache, 'render').mockImplementation(() => { throw new Error(errorMessage); }); - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( 'error rendering mustache template "{"domain":"{{domain}}"}": test error' ); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.ts index 34dd90ff2a862..21fd5290ba311 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/render.ts @@ -11,6 +11,7 @@ import { RenderParameterTemplates } from '@kbn/actions-plugin/server/types'; import { SUB_ACTION } from '../../../common/bedrock/constants'; export const renderParameterTemplates: RenderParameterTemplates = ( + logger, params, variables ) => { @@ -20,7 +21,7 @@ export const renderParameterTemplates: RenderParameterTemplates ...params, subActionParams: { ...params.subActionParams, - body: renderMustacheString(params.subActionParams.body as string, variables, 'json'), + body: renderMustacheString(logger, params.subActionParams.body as string, variables, 'json'), }, }; }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.test.ts index afff25ddd9129..8eb1420af4a16 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { renderParameterTemplates } from './render'; import Mustache from 'mustache'; @@ -17,20 +18,25 @@ const params = { }, }; +const logger = loggingSystemMock.createLogger(); const variables = { domain: 'm0zepcuuu2' }; describe('D3 Security - renderParameterTemplates', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it('should not render body on test action', () => { const testParams = { subAction: 'test', subActionParams: { ...params.subActionParams, body: 'test_json' }, }; - const result = renderParameterTemplates(testParams, variables); + const result = renderParameterTemplates(logger, testParams, variables); expect(result).toEqual(testParams); }); it('should rendered body with variables', () => { - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( JSON.stringify({ @@ -44,7 +50,7 @@ describe('D3 Security - renderParameterTemplates', () => { jest.spyOn(Mustache, 'render').mockImplementation(() => { throw new Error(errorMessage); }); - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( 'error rendering mustache template "{"domain":"{{domain}}"}": test error' ); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.ts b/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.ts index c5c6c5ef1daef..bd59118ac6bdf 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/d3security/render.ts @@ -11,6 +11,7 @@ import { RenderParameterTemplates } from '@kbn/actions-plugin/server/types'; import { SUB_ACTION } from '../../../common/d3security/constants'; export const renderParameterTemplates: RenderParameterTemplates = ( + logger, params, variables ) => { @@ -20,7 +21,7 @@ export const renderParameterTemplates: RenderParameterTemplates ...params, subActionParams: { ...params.subActionParams, - body: renderMustacheString(params.subActionParams.body as string, variables, 'json'), + body: renderMustacheString(logger, params.subActionParams.body as string, variables, 'json'), }, }; }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts index 5262577a9a932..fe18cf956c0e4 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts @@ -845,7 +845,11 @@ describe('execute()', () => { const variables = { rogue: '*bold*', }; - const renderedParams = connectorType.renderParameterTemplates!(paramsWithTemplates, variables); + const renderedParams = connectorType.renderParameterTemplates!( + mockedLogger, + paramsWithTemplates, + variables + ); expect(renderedParams.message).toBe('\\*bold\\*'); expect(renderedParams).toMatchInlineSnapshot(` @@ -887,7 +891,11 @@ describe('execute()', () => { const variables = { rogue: '*bold*', }; - const renderedParams = connectorType.renderParameterTemplates!(paramsWithTemplates, variables); + const renderedParams = connectorType.renderParameterTemplates!( + mockedLogger, + paramsWithTemplates, + variables + ); // Yes, this is tested in the snapshot below, but it's double-escaped there, // so easier to see here that the escaping is correct. expect(renderedParams.message).toBe('\\*bold\\*'); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts index 30d635cb0ced7..f1e9ab23d9793 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts @@ -8,6 +8,7 @@ import { curry } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; +import { Logger } from '@kbn/core/server'; import nodemailerGetService from 'nodemailer/lib/well-known'; import SMTPConnection from 'nodemailer/lib/smtp-connection'; import type { @@ -250,14 +251,15 @@ export function getConnectorType(params: GetConnectorTypeParams): EmailConnector } function renderParameterTemplates( + logger: Logger, params: ActionParamsType, variables: Record ): ActionParamsType { return { // most of the params need no escaping - ...renderMustacheObject(params, variables), + ...renderMustacheObject(logger, params, variables), // message however, needs to escaped as markdown - message: renderMustacheString(params.message, variables, 'markdown'), + message: renderMustacheString(logger, params.message, variables, 'markdown'), }; } diff --git a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts index 900f7cd334241..2b3fab30432fa 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.test.ts @@ -371,6 +371,7 @@ describe('execute()', () => { who: 'world', }; const renderedParams = connectorType.renderParameterTemplates!( + mockedLogger, paramsWithTemplates, variables, 'action-type-id' @@ -397,6 +398,7 @@ describe('execute()', () => { who: 'world', }; const renderedParams = connectorType.renderParameterTemplates!( + mockedLogger, paramsWithTemplates, variables, 'action-type-id' @@ -446,6 +448,7 @@ describe('execute()', () => { }, }; const renderedParams = connectorType.renderParameterTemplates!( + mockedLogger, paramsWithTemplates, variables, AlertHistoryEsIndexConnectorId @@ -526,6 +529,7 @@ describe('execute()', () => { }, }; const renderedParams = connectorType.renderParameterTemplates!( + mockedLogger, paramsWithTemplates, variables, AlertHistoryEsIndexConnectorId @@ -583,6 +587,7 @@ describe('execute()', () => { expect(() => connectorType.renderParameterTemplates!( + mockedLogger, paramsWithTemplates, variables, AlertHistoryEsIndexConnectorId diff --git a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts index 5057fdb5d8312..915a66568c20e 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/es_index/index.ts @@ -160,11 +160,16 @@ async function executor( } function renderParameterTemplates( + logger: Logger, params: ActionParamsType, variables: Record, actionId?: string ): ActionParamsType { - const { documents, indexOverride } = renderMustacheObject(params, variables); + const { documents, indexOverride } = renderMustacheObject( + logger, + params, + variables + ); if (actionId === AlertHistoryEsIndexConnectorId) { const alertHistoryDoc = buildAlertHistoryDocument(variables); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/render.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/render.test.ts index b9fff0362913c..e76b8331c041b 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/render.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/render.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { renderParameterTemplates } from './render'; import Mustache from 'mustache'; @@ -16,16 +17,17 @@ const params = { }; const variables = { domain: 'm0zepcuuu2' }; +const logger = loggingSystemMock.createLogger(); describe('OpenAI - renderParameterTemplates', () => { it('should not render body on test action', () => { const testParams = { subAction: 'test', subActionParams: { body: 'test_json' } }; - const result = renderParameterTemplates(testParams, variables); + const result = renderParameterTemplates(logger, testParams, variables); expect(result).toEqual(testParams); }); it('should rendered body with variables', () => { - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( JSON.stringify({ @@ -39,7 +41,7 @@ describe('OpenAI - renderParameterTemplates', () => { jest.spyOn(Mustache, 'render').mockImplementation(() => { throw new Error(errorMessage); }); - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( 'error rendering mustache template "{"domain":"{{domain}}"}": test error' ); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/render.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/render.ts index bb2f97d7ca0db..da79c1d305d1c 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/render.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/render.ts @@ -11,6 +11,7 @@ import { RenderParameterTemplates } from '@kbn/actions-plugin/server/types'; import { SUB_ACTION } from '../../../common/openai/constants'; export const renderParameterTemplates: RenderParameterTemplates = ( + logger, params, variables ) => { @@ -20,7 +21,7 @@ export const renderParameterTemplates: RenderParameterTemplates ...params, subActionParams: { ...params.subActionParams, - body: renderMustacheString(params.subActionParams.body as string, variables, 'json'), + body: renderMustacheString(logger, params.subActionParams.body as string, variables, 'json'), }, }; }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.test.ts index 69372cb19824c..530e4533592e5 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.test.ts @@ -5,10 +5,12 @@ * 2.0. */ +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { OpsgenieSubActions } from '../../../common'; import { renderParameterTemplates } from './render_template_variables'; const ruleTagsTemplate = '{{rule.tags}}'; +const logger = loggingSystemMock.createLogger(); describe('renderParameterTemplates', () => { const variables = { @@ -20,6 +22,7 @@ describe('renderParameterTemplates', () => { it('renders the rule.tags as a single string if subAction is not set to CreateAlert', () => { expect( renderParameterTemplates( + logger, { subAction: '', subActionParams: { @@ -43,6 +46,7 @@ describe('renderParameterTemplates', () => { it('does not transform the tags if the rule.tags string is not found', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: { @@ -66,6 +70,7 @@ describe('renderParameterTemplates', () => { it('transforms the rule.tags to an empty array when the field does not exist in the variable', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: { @@ -87,6 +92,7 @@ describe('renderParameterTemplates', () => { it('does not transform the tags when the field does not exist in the params', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: {}, @@ -104,6 +110,7 @@ describe('renderParameterTemplates', () => { it('replaces the rule.tags template with an array of strings', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: { @@ -132,6 +139,7 @@ describe('renderParameterTemplates', () => { it('replaces the rule.tags template with only a single instance of the rule.tags even when the mustache template exists multiple times', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: { @@ -161,6 +169,7 @@ describe('renderParameterTemplates', () => { it('replaces the rule.tags template with empty array and preserves the other values already in the array', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: { @@ -186,6 +195,7 @@ describe('renderParameterTemplates', () => { it('replaces the rule.tags template with variable value when the path is a full string', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: { @@ -212,6 +222,7 @@ describe('renderParameterTemplates', () => { it('replaces the rule.tags template and other templates', () => { expect( renderParameterTemplates( + logger, { subAction: OpsgenieSubActions.CreateAlert, subActionParams: { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.ts b/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.ts index 14670fe162ca6..42d4c42134efa 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/opsgenie/render_template_variables.ts @@ -15,17 +15,18 @@ import { OpsgenieSubActions } from '../../../common'; import { CreateAlertSubActionParams } from './types'; export const renderParameterTemplates: RenderParameterTemplates = ( + logger, params, variables ) => { if (!isCreateAlertSubAction(params) || !params.subActionParams.tags) { - return renderMustacheObject(params, variables); + return renderMustacheObject(logger, params, variables); } const foundRuleTagsTemplate = params.subActionParams.tags.includes(RULE_TAGS_TEMPLATE); if (!foundRuleTagsTemplate) { - return renderMustacheObject(params, variables); + return renderMustacheObject(logger, params, variables); } const paramsCopy = cloneDeep(params); @@ -39,7 +40,7 @@ export const renderParameterTemplates: RenderParameterTemplates ...getRuleTags(variables), ]); - return renderMustacheObject(paramsCopy, variables); + return renderMustacheObject(logger, paramsCopy, variables); }; type CreateAlertParams = CreateAlertSubActionParams & Record; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/render.ts b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/render.ts index 21f3bebdd8274..651fae8978141 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/render.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/render.ts @@ -9,6 +9,7 @@ import { map } from 'lodash'; import { set } from '@kbn/safer-lodash-set/fp'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { ExecutorParams } from '@kbn/actions-plugin/server/sub_action_framework/types'; +import { Logger } from '@kbn/core/server'; import { SUB_ACTION } from '../../../common/sentinelone/constants'; interface Context { @@ -16,6 +17,7 @@ interface Context { } export const renderParameterTemplates = ( + logger: Logger, params: ExecutorParams, variables: Record ) => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack/index.test.ts index f96cc176e467e..3f6203b725913 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack/index.test.ts @@ -344,7 +344,11 @@ describe('execute()', () => { const variables = { rogue: '*bold*', }; - const params = connectorType.renderParameterTemplates!(paramsWithTemplates, variables); + const params = connectorType.renderParameterTemplates!( + mockedLogger, + paramsWithTemplates, + variables + ); expect(params.message).toBe('`*bold*`'); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack/index.ts index d80a242bbc69b..ba1cf22b3ef98 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack/index.ts @@ -9,6 +9,7 @@ import { URL } from 'url'; import HttpProxyAgent from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { i18n } from '@kbn/i18n'; +import { Logger } from '@kbn/core/server'; import { schema, TypeOf } from '@kbn/config-schema'; import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -94,11 +95,12 @@ export function getConnectorType({ } function renderParameterTemplates( + logger: Logger, params: ActionParamsType, variables: Record ): ActionParamsType { return { - message: renderMustacheString(params.message, variables, 'slack'), + message: renderMustacheString(logger, params.message, variables, 'slack'), }; } diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts index 9821680741eee..59030a71aaa05 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts @@ -240,6 +240,7 @@ describe('execute', () => { }; const variables = { injected: '*foo*' }; const params = connectorType.renderParameterTemplates!( + mockedLogger, paramsWithTemplates, variables ) as PostMessageParams; @@ -266,6 +267,7 @@ describe('execute', () => { }; const variables = { name: '"Dwight"' }; const params = connectorType.renderParameterTemplates!( + mockedLogger, paramsWithTemplates, variables ) as PostMessageParams; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts index 5b8ded2ebbf1f..62e377dd623e1 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts @@ -10,6 +10,7 @@ import { AlertingConnectorFeatureId, SecurityConnectorFeatureId, } from '@kbn/actions-plugin/common/types'; +import { Logger } from '@kbn/core/server'; import { renderMustacheString } from '@kbn/actions-plugin/server/lib/mustache_renderer'; import type { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { i18n } from '@kbn/i18n'; @@ -69,13 +70,17 @@ const validateSlackUrl = (secretsObject: SlackApiSecrets, validatorServices: Val } }; -const renderParameterTemplates = (params: SlackApiParams, variables: Record) => { +const renderParameterTemplates = ( + logger: Logger, + params: SlackApiParams, + variables: Record +) => { if (params.subAction === 'postMessage') { return { subAction: params.subAction, subActionParams: { ...params.subActionParams, - text: renderMustacheString(params.subActionParams.text, variables, 'slack'), + text: renderMustacheString(logger, params.subActionParams.text, variables, 'slack'), }, }; } else if (params.subAction === 'postBlockkit') { @@ -83,7 +88,7 @@ const renderParameterTemplates = (params: SlackApiParams, variables: Record { describe('renderParameterTemplates', () => { it('should not render body on test action', () => { const testParams = { subAction: 'test', subActionParams: { body: 'test_json' } }; - const result = renderParameterTemplates(testParams, variables); + const result = renderParameterTemplates(logger, testParams, variables); expect(result).toEqual(testParams); }); it('should rendered body from variables with cleaned alerts on run action', () => { - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( JSON.stringify({ @@ -79,14 +81,14 @@ describe('Tines body render', () => { it('should rendered body from variables on run action without context.alerts', () => { const variablesWithoutAlerts = set('context.alerts', undefined, variables); - const result = renderParameterTemplates(params, variablesWithoutAlerts); + const result = renderParameterTemplates(logger, params, variablesWithoutAlerts); expect(result.subActionParams.body).toEqual(JSON.stringify(variablesWithoutAlerts)); }); it('should rendered body from variables on run action without context', () => { const variablesWithoutContext = set('context', undefined, variables); - const result = renderParameterTemplates(params, variablesWithoutContext); + const result = renderParameterTemplates(logger, params, variablesWithoutContext); expect(result.subActionParams.body).toEqual(JSON.stringify(variablesWithoutContext)); }); @@ -96,7 +98,7 @@ describe('Tines body render', () => { jest.spyOn(JSON, 'stringify').mockImplementationOnce(() => { throw new Error(errorMessage); }); - const result = renderParameterTemplates(params, variables); + const result = renderParameterTemplates(logger, params, variables); expect(result.subActionParams.body).toEqual( JSON.stringify({ error: { message: errorMessage } }) ); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/tines/render.ts b/x-pack/plugins/stack_connectors/server/connector_types/tines/render.ts index ca3fdf7de5b6c..360b430927f6a 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/tines/render.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/tines/render.ts @@ -15,6 +15,7 @@ interface Context { } export const renderParameterTemplates: RenderParameterTemplates = ( + logger, params, variables ) => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts index e970e1f678bde..4bf60d10eb789 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts @@ -227,7 +227,11 @@ describe('execute Torq action', () => { scalar: '1970', scalar_with_json_chars: 'noinjection", "here": "', }; - const params = actionType.renderParameterTemplates!(paramsWithTemplates, variables); + const params = actionType.renderParameterTemplates!( + mockedLogger, + paramsWithTemplates, + variables + ); expect(params.body).toBe( `{"x": ${templatedObject}, "y": "${variables.scalar}", "z": "${variables.scalar_with_json_chars}"}` ); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts index 9d805291cf92c..b58ab1f8a1aa7 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts @@ -88,11 +88,12 @@ export function getActionType(): TorqActionType { } function renderParameterTemplates( + logger: Logger, params: ActionParamsType, variables: Record ): ActionParamsType { if (!params.body) return params; - return renderMustacheObject(params, variables); + return renderMustacheObject(logger, params, variables); } function validateActionTypeConfig( diff --git a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts index 73b16a3748fd2..bd24314fa79b7 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts @@ -676,7 +676,11 @@ describe('execute()', () => { const variables = { rogue, }; - const params = connectorType.renderParameterTemplates!(paramsWithTemplates, variables); + const params = connectorType.renderParameterTemplates!( + mockedLogger, + paramsWithTemplates, + variables + ); // eslint-disable-next-line @typescript-eslint/no-explicit-any let paramsObject: any; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts index 2b17dcac9b913..62c0d6210b3d3 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { isString } from 'lodash'; import axios, { AxiosError, AxiosResponse } from 'axios'; import { schema, TypeOf } from '@kbn/config-schema'; +import { Logger } from '@kbn/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, getOrElse } from 'fp-ts/lib/Option'; import type { @@ -139,12 +140,13 @@ export function getConnectorType(): WebhookConnectorType { } function renderParameterTemplates( + logger: Logger, params: ActionParamsType, variables: Record ): ActionParamsType { if (!params.body) return params; return { - body: renderMustacheString(params.body, variables, 'json'), + body: renderMustacheString(logger, params.body, variables, 'json'), }; }