From 87d4f67b10e586a2fb14dc9de4036fa127867fec Mon Sep 17 00:00:00 2001 From: uboness Date: Fri, 8 May 2015 16:02:49 +0200 Subject: [PATCH] Changed the `body` settings of the email moved from `text_body` and `html_body` to a more structured `body` object as follows: ``` { "body" : { "text" : "the text body", "html" : "the html body" } } ``` `body` can also accpet a string, in which case it will default to the text body of the email: ``` { "body" : "the text body of the email" } ``` the above is a syntactic sugar for the following: ``` { "body" : { "text" : "the text body of the email" } } ``` Original commit: elastic/x-pack-elasticsearch@92406ac2a124dcabf41d30fce8d399dee8f37c38 --- .../watcher/actions/email/service/Email.java | 50 ++++-- .../actions/email/service/EmailException.java | 11 +- .../actions/email/service/EmailTemplate.java | 148 +++++++++++------- .../actions/email/EmailActionTests.java | 44 +++++- 4 files changed, 168 insertions(+), 85 deletions(-) diff --git a/src/main/java/org/elasticsearch/watcher/actions/email/service/Email.java b/src/main/java/org/elasticsearch/watcher/actions/email/service/Email.java index 713df4a6c9f..06b784d00bd 100644 --- a/src/main/java/org/elasticsearch/watcher/actions/email/service/Email.java +++ b/src/main/java/org/elasticsearch/watcher/actions/email/service/Email.java @@ -137,9 +137,15 @@ public class Email implements ToXContent { builder.field(Field.BCC.getPreferredName(), bcc, params); } builder.field(Field.SUBJECT.getPreferredName(), subject); - builder.field(Field.TEXT_BODY.getPreferredName(), textBody); - if (htmlBody != null) { - builder.field(Field.HTML_BODY.getPreferredName(), htmlBody); + if (textBody != null || htmlBody != null) { + builder.startObject(Field.BODY.getPreferredName()); + if (textBody != null) { + builder.field(Field.BODY_TEXT.getPreferredName(), textBody); + } + if (htmlBody != null) { + builder.field(Field.BODY_HTML.getPreferredName(), htmlBody); + } + builder.endObject(); } return builder.endObject(); } @@ -191,12 +197,27 @@ public class Email implements ToXContent { email.sentDate(new DateTime(parser.text())); } else if (Field.SUBJECT.match(currentFieldName)) { email.subject(parser.text()); - } else if (Field.TEXT_BODY.match(currentFieldName)) { - email.textBody(parser.text()); - } else if (Field.HTML_BODY.match(currentFieldName)) { - email.htmlBody(parser.text()); + } else if (Field.BODY.match(currentFieldName)) { + String bodyField = currentFieldName; + if (parser.currentToken() == XContentParser.Token.VALUE_STRING) { + email.textBody(parser.text()); + } else if (parser.currentToken() == XContentParser.Token.START_OBJECT) { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (currentFieldName == null) { + throw new ParseException("could not parse email. empty [{}] field", bodyField); + } else if (Email.Field.BODY_TEXT.match(currentFieldName)) { + email.textBody(parser.text()); + } else if (Email.Field.BODY_HTML.match(currentFieldName)) { + email.htmlBody(parser.text()); + } else { + throw new ParseException("could not parse email. unexpected field [{}.{}] field", bodyField, currentFieldName); + } + } + } } else { - throw new ParseException("could not parse email. unrecognized field [" + currentFieldName + "]"); + throw new ParseException("could not parse email. unexpected field [{}]", currentFieldName); } } } @@ -563,12 +584,12 @@ public class Email implements ToXContent { public static class ParseException extends EmailException { - public ParseException(String msg) { - super(msg); + public ParseException(String msg, Object... args) { + super(msg, args); } - public ParseException(String msg, Throwable cause) { - super(msg, cause); + public ParseException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); } } @@ -582,8 +603,9 @@ public class Email implements ToXContent { ParseField CC = new ParseField("cc"); ParseField BCC = new ParseField("bcc"); ParseField SUBJECT = new ParseField("subject"); - ParseField TEXT_BODY = new ParseField("text_body"); - ParseField HTML_BODY = new ParseField("html_body"); + ParseField BODY = new ParseField("body"); + ParseField BODY_TEXT = new ParseField("text"); + ParseField BODY_HTML = new ParseField("html"); } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailException.java b/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailException.java index 379f8f53d05..d2a319ff3d1 100644 --- a/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailException.java +++ b/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailException.java @@ -5,18 +5,19 @@ */ package org.elasticsearch.watcher.actions.email.service; +import org.elasticsearch.watcher.WatcherException; import org.elasticsearch.watcher.actions.ActionException; /** * */ -public class EmailException extends ActionException { +public class EmailException extends WatcherException { - public EmailException(String msg) { - super(msg); + public EmailException(String msg, Object... args) { + super(msg, args); } - public EmailException(String msg, Throwable cause) { - super(msg, cause); + public EmailException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); } } diff --git a/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailTemplate.java b/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailTemplate.java index 41f5d441ae1..728141f0f66 100644 --- a/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailTemplate.java +++ b/src/main/java/org/elasticsearch/watcher/actions/email/service/EmailTemplate.java @@ -8,6 +8,7 @@ package org.elasticsearch.watcher.actions.email.service; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.watcher.WatcherException; import org.elasticsearch.watcher.support.template.Template; import org.elasticsearch.watcher.support.template.TemplateEngine; import org.owasp.html.*; @@ -207,11 +208,15 @@ public class EmailTemplate implements ToXContent { if (subject != null) { builder.field(Email.Field.SUBJECT.getPreferredName(), subject, params); } - if (textBody != null) { - builder.field(Email.Field.TEXT_BODY.getPreferredName(), textBody, params); - } - if (htmlBody != null) { - builder.field(Email.Field.HTML_BODY.getPreferredName(), htmlBody, params); + if (textBody != null || htmlBody != null) { + builder.startObject(Email.Field.BODY.getPreferredName()); + if (textBody != null) { + builder.field(Email.Field.BODY_TEXT.getPreferredName(), textBody, params); + } + if (htmlBody != null) { + builder.field(Email.Field.BODY_HTML.getPreferredName(), htmlBody, params); + } + builder.endObject(); } return builder; } @@ -252,7 +257,7 @@ public class EmailTemplate implements ToXContent { public Builder replyTo(String... replyTo) { Template[] templates = new Template[replyTo.length]; for (int i = 0; i < templates.length; i++) { - templates[i] = Template.inline(replyTo[i]).build(); + templates[i] = Template.defaultType(replyTo[i]).build(); } return replyTo(templates); } @@ -286,7 +291,7 @@ public class EmailTemplate implements ToXContent { public Builder to(String... to) { Template[] templates = new Template[to.length]; for (int i = 0; i < templates.length; i++) { - templates[i] = Template.inline(to[i]).build(); + templates[i] = Template.defaultType(to[i]).build(); } return to(templates); } @@ -307,7 +312,7 @@ public class EmailTemplate implements ToXContent { public Builder cc(String... cc) { Template[] templates = new Template[cc.length]; for (int i = 0; i < templates.length; i++) { - templates[i] = Template.inline(cc[i]).build(); + templates[i] = Template.defaultType(cc[i]).build(); } return cc(templates); } @@ -328,7 +333,7 @@ public class EmailTemplate implements ToXContent { public Builder bcc(String... bcc) { Template[] templates = new Template[bcc.length]; for (int i = 0; i < templates.length; i++) { - templates[i] = Template.inline(bcc[i]).build(); + templates[i] = Template.defaultType(bcc[i]).build(); } return bcc(templates); } @@ -347,7 +352,7 @@ public class EmailTemplate implements ToXContent { } public Builder subject(String subject) { - return subject(Template.inline(subject)); + return subject(Template.defaultType(subject)); } public Builder subject(Template.Builder subject) { @@ -360,7 +365,7 @@ public class EmailTemplate implements ToXContent { } public Builder textBody(String text) { - return textBody(Template.inline(text)); + return textBody(Template.defaultType(text)); } public Builder textBody(Template.Builder text) { @@ -373,7 +378,7 @@ public class EmailTemplate implements ToXContent { } public Builder htmlBody(String html, boolean sanitizeHtmlBody) { - return htmlBody(Template.inline(html), sanitizeHtmlBody); + return htmlBody(Template.defaultType(html), sanitizeHtmlBody); } public Builder htmlBody(Template.Builder html, boolean sanitizeHtmlBody) { @@ -391,6 +396,54 @@ public class EmailTemplate implements ToXContent { } } + static String sanitizeHtml(String html, final Map attachments){ + ElementPolicy onlyCIDImgPolicy = new AttachementVerifyElementPolicy(attachments); + PolicyFactory policy = Sanitizers.FORMATTING + .and(new HtmlPolicyBuilder() + .allowElements("img", "table", "tr", "td", "style", "body", "head", "hr") + .allowAttributes("src").onElements("img") + .allowAttributes("class").onElements("style") + .allowUrlProtocols("cid") + .allowCommonInlineFormattingElements() + .allowElements(onlyCIDImgPolicy, "img") + .allowStyling(CssSchema.DEFAULT) + .toFactory()) + .and(Sanitizers.LINKS) + .and(Sanitizers.BLOCKS); + return policy.sanitize(html); + } + + private static class AttachementVerifyElementPolicy implements ElementPolicy { + + private final Map attachments; + + AttachementVerifyElementPolicy(Map attachments) { + this.attachments = attachments; + } + + @Override + public String apply(@ParametersAreNonnullByDefault String elementName, @ParametersAreNonnullByDefault List attrs) { + if (attrs.size() == 0) { + return elementName; + } + for (int i = 0; i < attrs.size(); ++i) { + if(attrs.get(i).equals("src") && i < attrs.size() - 1) { + String srcValue = attrs.get(i+1); + if (!srcValue.startsWith("cid:")) { + return null; //Disallow anything other than content ids + } + String contentId = srcValue.substring(4); + if (attachments.containsKey(contentId)) { + return elementName; + } else { + return null; //This cid wasn't found + } + } + } + return elementName; + } + } + public static class Parser { private final EmailTemplate.Builder builder = builder(); @@ -447,10 +500,26 @@ public class EmailTemplate implements ToXContent { builder.priority(Template.parse(parser)); } else if (Email.Field.SUBJECT.match(fieldName)) { builder.subject(Template.parse(parser)); - } else if (Email.Field.TEXT_BODY.match(fieldName)) { - builder.textBody(Template.parse(parser)); - } else if (Email.Field.HTML_BODY.match(fieldName)) { - builder.htmlBody(Template.parse(parser), sanitizeHtmlBody); + } else if (Email.Field.BODY.match(fieldName)) { + if (parser.currentToken() == XContentParser.Token.VALUE_STRING) { + builder.textBody(Template.parse(parser)); + } else if (parser.currentToken() == XContentParser.Token.START_OBJECT) { + XContentParser.Token token; + String currentFieldName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (currentFieldName == null) { + throw new ParseException("could not parse email template. empty [{}] field", fieldName); + } else if (Email.Field.BODY_TEXT.match(currentFieldName)) { + builder.textBody(Template.parse(parser)); + } else if (Email.Field.BODY_HTML.match(currentFieldName)) { + builder.htmlBody(Template.parse(parser), sanitizeHtmlBody); + } else { + throw new ParseException("could not parse email template. unknown field [{}.{}] field", fieldName, currentFieldName); + } + } + } } else { return false; } @@ -462,51 +531,14 @@ public class EmailTemplate implements ToXContent { } } - static String sanitizeHtml(String html, final Map attachments){ - ElementPolicy onlyCIDImgPolicy = new AttachementVerifyElementPolicy(attachments); - PolicyFactory policy = Sanitizers.FORMATTING - .and(new HtmlPolicyBuilder() - .allowElements("img", "table", "tr", "td", "style", "body", "head", "hr") - .allowAttributes("src").onElements("img") - .allowAttributes("class").onElements("style") - .allowUrlProtocols("cid") - .allowCommonInlineFormattingElements() - .allowElements(onlyCIDImgPolicy, "img") - .allowStyling(CssSchema.DEFAULT) - .toFactory()) - .and(Sanitizers.LINKS) - .and(Sanitizers.BLOCKS); - return policy.sanitize(html); - } + public static class ParseException extends WatcherException { - private static class AttachementVerifyElementPolicy implements ElementPolicy { - - private final Map attachments; - - AttachementVerifyElementPolicy(Map attachments) { - this.attachments = attachments; + public ParseException(String msg, Object... args) { + super(msg, args); } - @Override - public String apply(@ParametersAreNonnullByDefault String elementName, @ParametersAreNonnullByDefault List attrs) { - if (attrs.size() == 0) { - return elementName; - } - for (int i = 0; i < attrs.size(); ++i) { - if(attrs.get(i).equals("src") && i < attrs.size() - 1) { - String srcValue = attrs.get(i+1); - if (!srcValue.startsWith("cid:")) { - return null; //Disallow anything other than content ids - } - String contentId = srcValue.substring(4); - if (attachments.containsKey(contentId)) { - return elementName; - } else { - return null; //This cid wasn't found - } - } - } - return elementName; + public ParseException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); } } diff --git a/src/test/java/org/elasticsearch/watcher/actions/email/EmailActionTests.java b/src/test/java/org/elasticsearch/watcher/actions/email/EmailActionTests.java index e16e92316e9..89cdeb41938 100644 --- a/src/test/java/org/elasticsearch/watcher/actions/email/EmailActionTests.java +++ b/src/test/java/org/elasticsearch/watcher/actions/email/EmailActionTests.java @@ -203,19 +203,37 @@ public class EmailActionTests extends ElasticsearchTestCase { builder.field("subject", subject); } } - if (textBody != null) { + if (textBody != null && htmlBody == null) { if (randomBoolean()) { - builder.field("text_body", textBody.getTemplate()); + builder.field("body", textBody.getTemplate()); } else { - builder.field("text_body", textBody); + builder.startObject("body"); + if (randomBoolean()) { + builder.field("text", textBody.getTemplate()); + } else { + builder.field("text", textBody); + } + builder.endObject(); } } - if (htmlBody != null) { - if (randomBoolean()) { - builder.field("html_body", htmlBody.getTemplate()); - } else { - builder.field("html_body", htmlBody); + + if (textBody != null || htmlBody != null) { + builder.startObject("body"); + if (textBody != null) { + if (randomBoolean()) { + builder.field("text", textBody.getTemplate()); + } else { + builder.field("text", textBody); + } } + if (htmlBody != null) { + if (randomBoolean()) { + builder.field("html", htmlBody.getTemplate()); + } else { + builder.field("html", htmlBody); + } + } + builder.endObject(); } BytesReference bytes = builder.bytes(); logger.info("email action json [{}]", bytes.toUtf8()); @@ -286,6 +304,15 @@ public class EmailActionTests extends ElasticsearchTestCase { if (randomBoolean()) { emailTemplate.replyTo(randomBoolean() ? "reply@domain" : "reply1@domain,reply2@domain"); } + if (randomBoolean()) { + emailTemplate.subject("_subject"); + } + if (randomBoolean()) { + emailTemplate.textBody("_text_body"); + } + if (randomBoolean()) { + emailTemplate.htmlBody("_html_body", randomBoolean()); + } EmailTemplate email = emailTemplate.build(); Authentication auth = randomBoolean() ? null : new Authentication("_user", new Secret("_passwd".toCharArray())); Profile profile = randomFrom(Profile.values()); @@ -301,6 +328,7 @@ public class EmailActionTests extends ElasticsearchTestCase { XContentBuilder builder = jsonBuilder(); executable.toXContent(builder, params); BytesReference bytes = builder.bytes(); + logger.info(bytes.toUtf8()); XContentParser parser = JsonXContent.jsonXContent.createParser(bytes); parser.nextToken(); ExecutableEmailAction parsed = new EmailActionFactory(ImmutableSettings.EMPTY, service, engine)