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 d01d3c4a48f9e..e34aa85af7368 100644 --- a/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts +++ b/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderMustacheString, renderMustacheObject } from './mustache_renderer'; +import { renderMustacheString, renderMustacheObject, Escape } from './mustache_renderer'; const variables = { a: 1, @@ -14,7 +14,9 @@ const variables = { e: undefined, f: { g: 3, + h: null, }, + i: [42, 43, 44], lt: '<', gt: '>', amp: '&', @@ -29,15 +31,26 @@ const variables = { describe('mustache_renderer', () => { describe('renderMustacheString()', () => { - it('handles basic templating that does not need escaping', () => { - expect(renderMustacheString('', variables, 'none')).toBe(''); - expect(renderMustacheString('{{a}}', variables, 'none')).toBe('1'); - expect(renderMustacheString('{{b}}', variables, 'none')).toBe('2'); - expect(renderMustacheString('{{c}}', variables, 'none')).toBe('false'); - expect(renderMustacheString('{{d}}', variables, 'none')).toBe(''); - expect(renderMustacheString('{{e}}', variables, 'none')).toBe(''); - expect(renderMustacheString('{{f.g}}', variables, 'none')).toBe('3'); - }); + 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(''); + if (escape === 'markdown') { + expect(renderMustacheString('{{f}}', variables, escape)).toBe('\\[object Object\\]'); + } else { + expect(renderMustacheString('{{f}}', variables, escape)).toBe('[object Object]'); + } + expect(renderMustacheString('{{f.g}}', variables, escape)).toBe('3'); + expect(renderMustacheString('{{f.h}}', variables, escape)).toBe(''); + expect(renderMustacheString('{{i}}', variables, escape)).toBe('42,43,44'); + }); + } it('handles escape:none with commonly escaped strings', () => { expect(renderMustacheString('{{lt}}', variables, 'none')).toBe(variables.lt); diff --git a/x-pack/plugins/actions/server/lib/mustache_renderer.ts b/x-pack/plugins/actions/server/lib/mustache_renderer.ts index 5b4ab5f325484..ae17e12921726 100644 --- a/x-pack/plugins/actions/server/lib/mustache_renderer.ts +++ b/x-pack/plugins/actions/server/lib/mustache_renderer.ts @@ -7,7 +7,7 @@ import Mustache from 'mustache'; import { isString, cloneDeepWith } from 'lodash'; -type Escape = 'markdown' | 'slack' | 'json' | 'none'; +export type Escape = 'markdown' | 'slack' | 'json' | 'none'; type Variables = Record; // return a rendered mustache template given the specified variables and escape @@ -40,19 +40,20 @@ export function renderMustacheObject(params: Params, variables: Variable return (result as unknown) as Params; } -function getEscape(escape: Escape): (string: string) => string { +function getEscape(escape: Escape): (value: unknown) => string { if (escape === 'markdown') return escapeMarkdown; if (escape === 'slack') return escapeSlack; if (escape === 'json') return escapeJSON; return escapeNone; } -function escapeNone(value: string): string { - return value; +function escapeNone(value: unknown): string { + if (value == null) return ''; + return `${value}`; } // replace with JSON stringified version, removing leading and trailing double quote -function escapeJSON(value: string): string { +function escapeJSON(value: unknown): string { if (value == null) return ''; const quoted = JSON.stringify(`${value}`); @@ -62,28 +63,32 @@ function escapeJSON(value: string): string { // see: https://api.slack.com/reference/surfaces/formatting // but in practice, a bit more needs to be escaped, in drastic ways -function escapeSlack(value: string): string { +function escapeSlack(value: unknown): string { + if (value == null) return ''; + + const valueString = `${value}`; // if the value contains * or _, escape the whole thing with back tics - if (value.includes('_') || value.includes('*')) { + if (valueString.includes('_') || valueString.includes('*')) { // replace unescapable back tics with single quote - value = value.replace(/`/g, `'`); - return '`' + value + '`'; + return '`' + valueString.replace(/`/g, `'`) + '`'; } // otherwise, do "standard" escaping - value = value - .replace(/&/g, '&') - .replace(//g, '>') - // this isn't really standard escaping, but escaping back tics is problematic - .replace(/`/g, `'`); - - return value; + return ( + valueString + .replace(/&/g, '&') + .replace(//g, '>') + // this isn't really standard escaping, but escaping back tics is problematic + .replace(/`/g, `'`) + ); } // see: https://www.markdownguide.org/basic-syntax/#characters-you-can-escape -function escapeMarkdown(value: string): string { - return value +function escapeMarkdown(value: unknown): string { + if (value == null) return ''; + + return `${value}` .replace(/\\/g, '\\\\') .replace(/`/g, '\\`') .replace(/\*/g, '\\*')