Skip to content

Commit 4e36251

Browse files
authored
Watcher: Validate email adresses when storing a watch (#34042)
Right now, watches fail on runtime, when invalid email addresses are used. All those fields can be checked on parsing, if no mustache is used in any email address template. In that case we can return immediate feedback, that invalid email addresses should not be specified when trying to store a watch.
1 parent 7bcf496 commit 4e36251

File tree

11 files changed

+70
-13
lines changed

11 files changed

+70
-13
lines changed

x-pack/docs/en/watcher/actions/email.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ the watch payload in the email body:
2828
"actions" : {
2929
"send_email" : { <1>
3030
"email" : { <2>
31-
"to" : "<username>@<domainname>", <3>
31+
"to" : "username@example.org", <3>
3232
"subject" : "Watcher Notification", <4>
3333
"body" : "{{ctx.payload.hits.total}} error logs found" <5>
3434
}

x-pack/docs/en/watcher/customizing-watches.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ attaches the payload data to the message:
219219
"actions" : {
220220
"send_email" : { <1>
221221
"email" : { <2>
222-
"to" : "<username>@<domainname>",
222+
"to" : "email@example.org",
223223
"subject" : "Watcher Notification",
224224
"body" : "{{ctx.payload.hits.total}} error logs found",
225225
"attachments" : {

x-pack/docs/en/watcher/example-watches/example-watch-clusterstatus.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ PUT _xpack/watcher/watch/cluster_health_watch
213213
"actions" : {
214214
"send_email" : {
215215
"email" : {
216-
"to" : "<username>@<domainname>",
216+
"to" : "username@example.org",
217217
"subject" : "Cluster Status Warning",
218218
"body" : "Cluster status is RED"
219219
}

x-pack/docs/en/watcher/example-watches/example-watch-meetupdata.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,8 @@ PUT _xpack/watcher/watch/meetup
277277
"email_me": {
278278
"throttle_period": "10m",
279279
"email": {
280-
"from": "<username>@<domainname>", <2>
281-
"to": "<username@<domainname>", <3>
280+
"from": "username@example.org", <2>
281+
"to": "recipient@example.org", <3>
282282
"subject": "Open Source events",
283283
"body": {
284284
"html": "Found events matching Open Source: <ul>{{#ctx.payload.aggregations.group_by_city.buckets}}<li>{{key}} ({{doc_count}})<ul>{{#group_by_event.buckets}}<li><a href=\"{{key}}\">{{get_latest.buckets.0.group_by_event_name.buckets.0.key}}</a> ({{doc_count}})</li>{{/group_by_event.buckets}}</ul></li>{{/ctx.payload.aggregations.group_by_city.buckets}}</ul>"

x-pack/docs/en/watcher/example-watches/watching-time-series-data.asciidoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ you can then reference it by name in the watch condition.
133133
"actions" : {
134134
"send_email" : {
135135
"email" : {
136-
"to" : "<username>@<domainname>",
136+
"to" : "username@example.org",
137137
"subject" : "Somebody needs help with the Elastic Stack",
138138
"body" : "The attached Stack Overflow posts were tagged with Elasticsearch, Logstash, Beats or Kibana and mentioned an error or problem.",
139139
"attachments" : {
@@ -190,7 +190,7 @@ PUT _xpack/watcher/watch/rss_watch
190190
"actions" : {
191191
"send_email" : {
192192
"email" : {
193-
"to" : "<username>@<domainname>", <1>
193+
"to" : "username@example.org", <1>
194194
"subject" : "Somebody needs help with the Elastic Stack",
195195
"body" : "The attached Stack Overflow posts were tagged with Elasticsearch, Logstash, Beats or Kibana and mentioned an error or problem.",
196196
"attachments" : {
@@ -205,7 +205,7 @@ PUT _xpack/watcher/watch/rss_watch
205205
--------------------------------------------------
206206
// CONSOLE
207207
// TEST[s/"id" : "threshold_hits"/"source": "return ctx.payload.hits.total > params.threshold"/]
208-
<1> Replace `<username>@<domainname>` with your email address to receive
208+
<1> Replace `username@example.org` with your email address to receive
209209
notifications.
210210

211211
[TIP]

x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplate.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ public class TextTemplate implements ToXContent {
2929

3030
private final Script script;
3131
private final String inlineTemplate;
32+
private final boolean isUsingMustache;
3233

3334
public TextTemplate(String template) {
3435
this.script = null;
3536
this.inlineTemplate = template;
37+
this.isUsingMustache = template.contains("{{");
3638
}
3739

3840
public TextTemplate(String template, @Nullable XContentType contentType, ScriptType type,
@@ -48,12 +50,14 @@ public TextTemplate(String template, @Nullable XContentType contentType, ScriptT
4850
params = new HashMap<>();
4951
}
5052
this.script = new Script(type, type == ScriptType.STORED ? null : Script.DEFAULT_TEMPLATE_LANG, template, options, params);
53+
this.isUsingMustache = template.contains("{{");
5154
this.inlineTemplate = null;
5255
}
5356

5457
public TextTemplate(Script script) {
5558
this.script = script;
5659
this.inlineTemplate = null;
60+
this.isUsingMustache = script.getIdOrCode().contains("{{");
5761
}
5862

5963
public Script getScript() {
@@ -64,6 +68,10 @@ public String getTemplate() {
6468
return script != null ? script.getIdOrCode() : inlineTemplate;
6569
}
6670

71+
public boolean isUsingMustache() {
72+
return isUsingMustache;
73+
}
74+
6775
public XContentType getContentType() {
6876
if (script == null || script.getOptions() == null) {
6977
return null;

x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ public String render(TextTemplate textTemplate, Map<String, Object> model) {
3535
String mediaType = compileParams(detectContentType(template));
3636
template = trimContentType(textTemplate);
3737

38-
int indexStartMustacheExpression = template.indexOf("{{");
39-
if (indexStartMustacheExpression == -1) {
38+
if (textTemplate.isUsingMustache() == false) {
4039
return template;
4140
}
4241

x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/email/EmailTemplate.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ public static class Parser {
342342
public boolean handle(String fieldName, XContentParser parser) throws IOException {
343343
if (Email.Field.FROM.match(fieldName, parser.getDeprecationHandler())) {
344344
builder.from(TextTemplate.parse(parser));
345+
validateEmailAddresses(builder.from);
345346
} else if (Email.Field.REPLY_TO.match(fieldName, parser.getDeprecationHandler())) {
346347
if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
347348
List<TextTemplate> templates = new ArrayList<>();
@@ -352,6 +353,7 @@ public boolean handle(String fieldName, XContentParser parser) throws IOExceptio
352353
} else {
353354
builder.replyTo(TextTemplate.parse(parser));
354355
}
356+
validateEmailAddresses(builder.replyTo);
355357
} else if (Email.Field.TO.match(fieldName, parser.getDeprecationHandler())) {
356358
if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
357359
List<TextTemplate> templates = new ArrayList<>();
@@ -362,6 +364,7 @@ public boolean handle(String fieldName, XContentParser parser) throws IOExceptio
362364
} else {
363365
builder.to(TextTemplate.parse(parser));
364366
}
367+
validateEmailAddresses(builder.to);
365368
} else if (Email.Field.CC.match(fieldName, parser.getDeprecationHandler())) {
366369
if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
367370
List<TextTemplate> templates = new ArrayList<>();
@@ -372,6 +375,7 @@ public boolean handle(String fieldName, XContentParser parser) throws IOExceptio
372375
} else {
373376
builder.cc(TextTemplate.parse(parser));
374377
}
378+
validateEmailAddresses(builder.cc);
375379
} else if (Email.Field.BCC.match(fieldName, parser.getDeprecationHandler())) {
376380
if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
377381
List<TextTemplate> templates = new ArrayList<>();
@@ -382,6 +386,7 @@ public boolean handle(String fieldName, XContentParser parser) throws IOExceptio
382386
} else {
383387
builder.bcc(TextTemplate.parse(parser));
384388
}
389+
validateEmailAddresses(builder.bcc);
385390
} else if (Email.Field.PRIORITY.match(fieldName, parser.getDeprecationHandler())) {
386391
builder.priority(TextTemplate.parse(parser));
387392
} else if (Email.Field.SUBJECT.match(fieldName, parser.getDeprecationHandler())) {
@@ -413,6 +418,26 @@ public boolean handle(String fieldName, XContentParser parser) throws IOExceptio
413418
return true;
414419
}
415420

421+
/**
422+
* If this is a text template not using mustache
423+
* @param emails The list of email addresses to parse
424+
*/
425+
static void validateEmailAddresses(TextTemplate ... emails) {
426+
for (TextTemplate emailTemplate : emails) {
427+
// no mustache, do validation
428+
if (emailTemplate.isUsingMustache() == false) {
429+
String email = emailTemplate.getTemplate();
430+
try {
431+
for (Email.Address address : Email.AddressList.parse(email)) {
432+
address.validate();
433+
}
434+
} catch (AddressException e) {
435+
throw new ElasticsearchParseException("invalid email address [{}]", e, email);
436+
}
437+
}
438+
}
439+
}
440+
416441
public EmailTemplate parsedTemplate() {
417442
return builder.build();
418443
}

x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/email/EmailAttachmentTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public void testThatEmailAttachmentsAreSent() throws Exception {
170170
emailAttachments.toXContent(tmpBuilder, ToXContent.EMPTY_PARAMS);
171171
tmpBuilder.endObject();
172172

173-
EmailTemplate.Builder emailBuilder = EmailTemplate.builder().from("_from").to("_to").subject("Subject");
173+
EmailTemplate.Builder emailBuilder = EmailTemplate.builder().from("from@example.org").to("to@example.org").subject("Subject");
174174
WatchSourceBuilder watchSourceBuilder = watchBuilder()
175175
.trigger(schedule(interval(5, IntervalSchedule.Interval.Unit.SECONDS)))
176176
.input(noneInput())

x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/email/EmailSecretsIntegrationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ public void testEmail() throws Exception {
8888
.condition(InternalAlwaysCondition.INSTANCE)
8989
.addAction("_email", ActionBuilders.emailAction(
9090
EmailTemplate.builder()
91-
.from("_from")
92-
.to("_to")
91+
.from("from@example.org")
92+
.to("to@example.org")
9393
.subject("_subject"))
9494
.setAuthentication(EmailServer.USERNAME, EmailServer.PASSWORD.toCharArray())))
9595
.get();

0 commit comments

Comments
 (0)