-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[alerts] allow action types to escape their own mustache templates
resolves #79371 resolves #75601 resolves #62928 In this PR, we allow action types to determine how to escape the variables used in their parameters, when rendered as mustache templates. Prior to this, action parameters were recursively rendered as mustache templates using the default mustache templating, by the alerts library. The default mustache templating used html escaping. Action types opt-in to the new capability via a new optional method in the action type, `renderParameterTemplates()`. If not provided, the previous recursive rendering is done, but now with no escaping at all. For #75601, added toString() methods to mustache object variables which allow them to be used in a template and expanded to JSON, for experimentation / discovery of context variables. For #62928, changed the mustache template rendering to be replaced with the error message, if an error occurred, so at least you can now see that an error occurred. Useful to diagnose problems with invalid mustache templates.
- Loading branch information
Showing
21 changed files
with
922 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
x-pack/plugins/actions/server/lib/mustache_renderer.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { renderMustacheString, renderMustacheObject } from './mustache_renderer'; | ||
|
||
const variables = { | ||
a: 1, | ||
b: '2', | ||
c: false, | ||
d: null, | ||
e: undefined, | ||
f: { | ||
g: 3, | ||
}, | ||
lt: '<', | ||
gt: '>', | ||
amp: '&', | ||
nl: '\n', | ||
dq: '"', | ||
bt: '`', | ||
bs: '\\', | ||
st: '*', | ||
ul: '_', | ||
st_lt: '*<', | ||
}; | ||
|
||
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'); | ||
}); | ||
|
||
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); | ||
}); | ||
|
||
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); | ||
}); | ||
|
||
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); | ||
}); | ||
|
||
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('`_`'); | ||
// html escapes not needed when using backtic escaping | ||
expect(renderMustacheString('{{st_lt}}', variables, 'slack')).toBe('`*<`'); | ||
}); | ||
|
||
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); | ||
}); | ||
|
||
it('handles errors', () => { | ||
expect(renderMustacheString('{{a}', variables, 'none')).toMatchInlineSnapshot( | ||
`"error rendering mustache template \\"{{a}\\": Unclosed tag at 4"` | ||
); | ||
}); | ||
}); | ||
|
||
const object = { | ||
literal: 0, | ||
literals: { | ||
a: 1, | ||
b: '2', | ||
c: true, | ||
d: null, | ||
e: undefined, | ||
eval: '{{lt}}{{b}}{{gt}}', | ||
}, | ||
list: ['{{a}}', '{{bt}}{{st}}{{bt}}'], | ||
object: { | ||
a: ['{{a}}', '{{bt}}{{st}}{{bt}}'], | ||
}, | ||
}; | ||
|
||
describe('renderMustacheObject()', () => { | ||
it('handles deep objects', () => { | ||
expect(renderMustacheObject(object, variables)).toMatchInlineSnapshot(` | ||
Object { | ||
"list": Array [ | ||
"1", | ||
"\`*\`", | ||
], | ||
"literal": 0, | ||
"literals": Object { | ||
"a": 1, | ||
"b": "2", | ||
"c": true, | ||
"d": null, | ||
"e": undefined, | ||
"eval": "<2>", | ||
}, | ||
"object": Object { | ||
"a": Array [ | ||
"1", | ||
"\`*\`", | ||
], | ||
}, | ||
} | ||
`); | ||
}); | ||
|
||
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(` | ||
Array [ | ||
"1", | ||
] | ||
`); | ||
}); | ||
|
||
it('handles errors', () => { | ||
expect(renderMustacheObject({ a: '{{a}' }, variables)).toMatchInlineSnapshot(` | ||
Object { | ||
"a": "error rendering mustache template \\"{{a}\\": Unclosed tag at 4", | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
describe('augmented object variables', () => { | ||
const deepVariables = { | ||
a: 1, | ||
b: { c: 2, d: [3, 4] }, | ||
e: [5, { f: 6, g: 7 }], | ||
}; | ||
expect(renderMustacheObject({ x: '{{a}} - {{b}} -- {{e}} ' }, deepVariables)) | ||
.toMatchInlineSnapshot(` | ||
Object { | ||
"x": "1 - {\\"c\\":2,\\"d\\":[3,4]} -- 5,{\\"f\\":6,\\"g\\":7} ", | ||
} | ||
`); | ||
|
||
const expected = '1 - {"c":2,"d":[3,4]} -- 5,{"f":6,"g":7}'; | ||
expect(renderMustacheString('{{a}} - {{b}} -- {{e}}', deepVariables, 'none')).toEqual(expected); | ||
}); | ||
}); |
Oops, something went wrong.