Skip to content

Commit a50a577

Browse files
committed
[ResponseOps] mustache lambdas for EncodeURI and EncodeURIComponent, take 2 (elastic#213859)
resolves elastic#168728 ## Release note Adds Mustache lambdas for alerting actions to encode URLs with `{{#EncodeURI}}` and `{{#EncodeURIComponent}}` using `encodeURI()` and `encodeURIComponent()`. doc to update, in a separate PR: elastic/docs-content#735 (cherry picked from commit a3aaa04)
1 parent 75fc4d5 commit a50a577

File tree

4 files changed

+117
-8
lines changed

4 files changed

+117
-8
lines changed

x-pack/platform/plugins/shared/actions/server/lib/mustache_lambdas.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,52 @@ describe('mustache lambdas', () => {
246246
expect(logger.warn).toHaveBeenCalledWith(`mustache render error: invalid number: 'nope'`);
247247
});
248248
});
249+
250+
describe('EncodeURI', () => {
251+
it('valid string is successful', () => {
252+
const uri = 'https://www.elastic.co?foo=bar&baz= qux'; // note the space
253+
const template = dedent`
254+
{{#EncodeURI}}{{uri}}{{/EncodeURI}}
255+
`.trim();
256+
257+
expect(renderMustacheString(logger, template, { uri }, 'none')).toEqual(
258+
'https://www.elastic.co?foo=bar&baz=%20qux'
259+
);
260+
});
261+
262+
it('logs an error message and returns the error message on errors', () => {
263+
const uri = '\uDC00'; // invalid UTF-8
264+
const template = dedent`
265+
{{#EncodeURI}} {{uri}} {{/EncodeURI}}
266+
`.trim();
267+
268+
const errMessage = `error evaluating encodeURI(\" ${uri} \"): URI malformed`;
269+
expect(renderMustacheString(logger, template, { uri }, 'none')).toBe(errMessage);
270+
expect(logger.warn).toHaveBeenCalledWith(`mustache render error: ${errMessage}`);
271+
});
272+
});
273+
274+
describe('EncodeURIComponent', () => {
275+
it('valid string is successful', () => {
276+
const uri = 'https://www.elastic.co?foo=bar&baz= qux'; // note the space
277+
const template = dedent`
278+
{{#EncodeURIComponent}}{{uri}} {{/EncodeURIComponent}}
279+
`.trim();
280+
281+
expect(renderMustacheString(logger, template, { uri }, 'none')).toEqual(
282+
'https%3A%2F%2Fwww.elastic.co%3Ffoo%3Dbar%26baz%3D%20qux%20'
283+
);
284+
});
285+
286+
it('logs an error message and returns the error message on errors', () => {
287+
const uri = '\uDC00'; // invalid UTF-8
288+
const template = dedent`
289+
{{#EncodeURIComponent}} {{uri}} {{/EncodeURIComponent}}
290+
`.trim();
291+
292+
const errMessage = `error evaluating encodeURIComponent(\" ${uri} \"): URI malformed`;
293+
expect(renderMustacheString(logger, template, { uri }, 'none')).toBe(errMessage);
294+
expect(logger.warn).toHaveBeenCalledWith(`mustache render error: ${errMessage}`);
295+
});
296+
});
249297
});

x-pack/platform/plugins/shared/actions/server/lib/mustache_lambdas.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,39 @@ function getLambdas(logger: Logger) {
4646
const numberString = render(text.trim()).trim();
4747
return formatNumber(logger, numberString);
4848
},
49+
EncodeURI: () =>
50+
function (text: string, render: RenderFn) {
51+
// specifically does not strip whitespace
52+
const string = render(text);
53+
return callEncodeURI(logger, string);
54+
},
55+
EncodeURIComponent: () =>
56+
function (text: string, render: RenderFn) {
57+
// specifically does not strip whitespace
58+
const string = render(text);
59+
return callEncodeURIComponent(logger, string);
60+
},
4961
};
5062
}
5163

64+
function callEncodeURI(logger: Logger, string: string): string {
65+
const s = `${string}`;
66+
try {
67+
return encodeURI(s);
68+
} catch (err) {
69+
return logAndReturnErr(logger, `error evaluating encodeURI("${s}"): ${err.message}`);
70+
}
71+
}
72+
73+
function callEncodeURIComponent(logger: Logger, string: string): string {
74+
const s = `${string}`;
75+
try {
76+
return encodeURIComponent(s);
77+
} catch (err) {
78+
return logAndReturnErr(logger, `error evaluating encodeURIComponent("${s}"): ${err.message}`);
79+
}
80+
}
81+
5282
function evalMath(vars: Variables, o: unknown, logger: Logger): string {
5383
const expr = `${o}`;
5484
try {

x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const DeepContextVariables = {
4848
nullJ: null,
4949
undefinedK: undefined,
5050
dateL: '2023-04-20T04:13:17.858Z',
51+
encodeableUrl: 'https://www.elastic.co?foo=bar&baz= qux',
5152
};
5253

5354
function getAlwaysFiringRuleType() {

x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
6666

6767
describe('escaping', () => {
6868
it('should handle escapes in webhook', async () => {
69-
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts,
69+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
7070
// const EscapableStrings
7171
const template = '{{context.escapableDoubleQuote}} -- {{context.escapableLineFeed}}';
7272
const rule = await createRule({
@@ -81,7 +81,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
8181
});
8282

8383
it('should handle escapes in slack', async () => {
84-
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts,
84+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
8585
// const EscapableStrings
8686
const template =
8787
'{{context.escapableBacktic}} -- {{context.escapableBold}} -- {{context.escapableBackticBold}} -- {{context.escapableHtml}} -- {{context.escapableLink}}';
@@ -101,7 +101,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
101101
});
102102

103103
it('should handle context variable object expansion', async () => {
104-
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts,
104+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
105105
// const DeepContextVariables
106106
const template = '{{context.deep}}';
107107
const rule = await createRule({
@@ -113,7 +113,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
113113
});
114114
const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id));
115115
expect(body).to.be(
116-
'{"objectA":{"stringB":"B","arrayC":[{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}],"objectF":{"stringG":"G","nullG":null}},"stringH":"H","arrayI":[44,45],"nullJ":null,"dateL":"2023-04-20T04:13:17.858Z"}'
116+
'{"objectA":{"stringB":"B","arrayC":[{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}],"objectF":{"stringG":"G","nullG":null}},"stringH":"H","arrayI":[44,45],"nullJ":null,"dateL":"2023-04-20T04:13:17.858Z","encodeableUrl":"https://www.elastic.co?foo=bar&baz= qux"}'
117117
);
118118
});
119119

@@ -165,7 +165,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
165165
});
166166

167167
it('should handle asJSON', async () => {
168-
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts,
168+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
169169
// const DeepContextVariables
170170
const template = `{{#context.deep.objectA}}
171171
{{{arrayC}}} {{{arrayC.asJSON}}}
@@ -185,7 +185,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
185185
});
186186

187187
it('should handle EvalMath', async () => {
188-
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts,
188+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
189189
// const DeepContextVariables
190190
const template = `{{#context.deep}}avg({{arrayI.0}}, {{arrayI.1}})/100 => {{#EvalMath}}
191191
round((arrayI[0] + arrayI[1]) / 2 / 100, 2)
@@ -202,7 +202,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
202202
});
203203

204204
it('should handle FormatDate', async () => {
205-
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts,
205+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
206206
// const DeepContextVariables
207207
const template = `{{#context.deep}}{{#FormatDate}}
208208
{{{dateL}}} ; America/New_York; dddd MMM Do YYYY HH:mm:ss
@@ -220,7 +220,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
220220
});
221221

222222
it('should handle FormatNumber', async () => {
223-
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts,
223+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
224224
// const DeepContextVariables
225225
const template = `{{#context.deep}}{{#FormatNumber}}
226226
{{{arrayI.1}}}; en-US; style: currency, currency: EUR
@@ -236,6 +236,36 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
236236
expect(body.trim()).to.be('€45.00');
237237
});
238238

239+
it('should handle EncodeURI', async () => {
240+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
241+
// const DeepContextVariables
242+
const template = `{{#context.deep}}{{#EncodeURI}}{{{encodeableUrl}}}{{/EncodeURI}}{{/context.deep}}`;
243+
const rule = await createRule({
244+
id: slackConnector.id,
245+
group: 'default',
246+
params: {
247+
message: `message {{rule.id}} - ${template}`,
248+
},
249+
});
250+
const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id));
251+
expect(body.trim()).to.be('https://www.elastic.co?foo=bar&baz=%20qux');
252+
});
253+
254+
it('should handle EncodeURIComponent', async () => {
255+
// from x-pack/test/alerting_api_integration/common/plugins/alerts/server/rule_types.ts
256+
// const DeepContextVariables
257+
const template = `{{#context.deep}}{{#EncodeURIComponent}}{{{encodeableUrl}}}{{/EncodeURIComponent}}{{/context.deep}}`;
258+
const rule = await createRule({
259+
id: slackConnector.id,
260+
group: 'default',
261+
params: {
262+
message: `message {{rule.id}} - ${template}`,
263+
},
264+
});
265+
const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id));
266+
expect(body.trim()).to.be('https%3A%2F%2Fwww.elastic.co%3Ffoo%3Dbar%26baz%3D%20qux');
267+
});
268+
239269
async function createRule(action: any) {
240270
const ruleResponse = await supertest
241271
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)

0 commit comments

Comments
 (0)