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@92406ac2a1
This commit is contained in:
uboness 2015-05-08 16:02:49 +02:00
parent 45f39ca2a2
commit 87d4f67b10
4 changed files with 168 additions and 85 deletions

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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<String, Attachment> 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<String, Attachment> attachments;
AttachementVerifyElementPolicy(Map<String, Attachment> attachments) {
this.attachments = attachments;
}
@Override
public String apply(@ParametersAreNonnullByDefault String elementName, @ParametersAreNonnullByDefault List<String> 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<String, Attachment> 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<String, Attachment> attachments;
AttachementVerifyElementPolicy(Map<String, Attachment> attachments) {
this.attachments = attachments;
public ParseException(String msg, Object... args) {
super(msg, args);
}
@Override
public String apply(@ParametersAreNonnullByDefault String elementName, @ParametersAreNonnullByDefault List<String> 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);
}
}

View File

@ -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)