Watcher: Support for inline attachments (elastic/elasticsearch#2601)
If an attachment is configured of disposition type INLINE, and is referred to in HTML body parts, then some email clients can display images inside of an HTML email and refer to those attachments. Watcher already had support for inlined attachments, however this could not be configured from a watch, but just via the Java API. Also it was not tested. This commit changes the attachment to decide on creation if it should be inline or a regular attachment and adds a test. Relates elastic/elasticsearch#2381 Relates elastic/elasticsearch#2464 Closes elastic/elasticsearch#2557 Original commit: elastic/x-pack-elasticsearch@84935ffb18
This commit is contained in:
parent
88d0d4ddf5
commit
32c9f86124
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.notification.email;
|
|||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.inject.Provider;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
@ -16,19 +17,26 @@ import javax.activation.DataHandler;
|
|||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Part;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.util.ByteArrayDataSource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static javax.mail.Part.ATTACHMENT;
|
||||
import static javax.mail.Part.INLINE;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class Attachment extends BodyPartSource {
|
||||
|
||||
protected Attachment(String id, String name, String contentType) {
|
||||
private final boolean inline;
|
||||
|
||||
protected Attachment(String id, String name, String contentType, boolean inline) {
|
||||
super(id, name, contentType);
|
||||
this.inline = inline;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,13 +44,17 @@ public abstract class Attachment extends BodyPartSource {
|
|||
MimeBodyPart part = new MimeBodyPart();
|
||||
part.setContentID(id);
|
||||
part.setFileName(name);
|
||||
part.setDisposition(Part.ATTACHMENT);
|
||||
part.setDisposition(inline ? INLINE : ATTACHMENT);
|
||||
writeTo(part);
|
||||
return part;
|
||||
}
|
||||
|
||||
public abstract String type();
|
||||
|
||||
public boolean isInline() {
|
||||
return inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* intentionally not emitting path as it may come as an information leak
|
||||
*/
|
||||
|
@ -65,22 +77,22 @@ public abstract class Attachment extends BodyPartSource {
|
|||
private final Path path;
|
||||
private final DataSource dataSource;
|
||||
|
||||
public File(String id, Path path) {
|
||||
this(id, path.getFileName().toString(), path);
|
||||
public File(String id, Path path, boolean inline) {
|
||||
this(id, path.getFileName().toString(), path, inline);
|
||||
}
|
||||
|
||||
public File(String id, Path path, String contentType) {
|
||||
this(id, path.getFileName().toString(), path, contentType);
|
||||
public File(String id, Path path, String contentType, boolean inline) {
|
||||
this(id, path.getFileName().toString(), path, contentType, inline);
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "uses toFile")
|
||||
public File(String id, String name, Path path) {
|
||||
this(id, name, path, fileTypeMap.getContentType(path.toFile()));
|
||||
public File(String id, String name, Path path, boolean inline) {
|
||||
this(id, name, path, fileTypeMap.getContentType(path.toFile()), inline);
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "uses toFile")
|
||||
public File(String id, String name, Path path, String contentType) {
|
||||
super(id, name, contentType);
|
||||
public File(String id, String name, Path path, String contentType, boolean inline) {
|
||||
super(id, name, contentType, inline);
|
||||
this.path = path;
|
||||
this.dataSource = new FileDataSource(path.toFile());
|
||||
}
|
||||
|
@ -105,16 +117,16 @@ public abstract class Attachment extends BodyPartSource {
|
|||
|
||||
private final byte[] bytes;
|
||||
|
||||
public Bytes(String id, byte[] bytes, String contentType) {
|
||||
this(id, id, bytes, contentType);
|
||||
public Bytes(String id, byte[] bytes, String contentType, boolean inline) {
|
||||
this(id, id, bytes, contentType, inline);
|
||||
}
|
||||
|
||||
public Bytes(String id, String name, byte[] bytes) {
|
||||
this(id, name, bytes, fileTypeMap.getContentType(name));
|
||||
public Bytes(String id, String name, byte[] bytes, boolean inline) {
|
||||
this(id, name, bytes, fileTypeMap.getContentType(name), inline);
|
||||
}
|
||||
|
||||
public Bytes(String id, String name, byte[] bytes, String contentType) {
|
||||
super(id, name, contentType);
|
||||
public Bytes(String id, String name, byte[] bytes, String contentType, boolean inline) {
|
||||
super(id, name, contentType, inline);
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
|
@ -134,6 +146,68 @@ public abstract class Attachment extends BodyPartSource {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Stream extends Attachment {
|
||||
|
||||
static final String TYPE = "stream";
|
||||
|
||||
private final Provider<InputStream> source;
|
||||
|
||||
public Stream(String id, String name, boolean inline, Provider<InputStream> source) {
|
||||
this(id, name, fileTypeMap.getContentType(name), inline, source);
|
||||
}
|
||||
|
||||
public Stream(String id, String name, String contentType, boolean inline, Provider<InputStream> source) {
|
||||
super(id, name, contentType, inline);
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeTo(MimeBodyPart part) throws MessagingException {
|
||||
DataSource ds = new StreamDataSource(name, contentType, source);
|
||||
DataHandler dh = new DataHandler(ds);
|
||||
part.setDataHandler(dh);
|
||||
}
|
||||
|
||||
static class StreamDataSource implements DataSource {
|
||||
|
||||
private final String name;
|
||||
private final String contentType;
|
||||
private final Provider<InputStream> source;
|
||||
|
||||
public StreamDataSource(String name, String contentType, Provider<InputStream> source) {
|
||||
this.name = name;
|
||||
this.contentType = contentType;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return source.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class XContent extends Bytes {
|
||||
|
||||
protected XContent(String id, ToXContent content, XContentType type) {
|
||||
|
@ -141,7 +215,7 @@ public abstract class Attachment extends BodyPartSource {
|
|||
}
|
||||
|
||||
protected XContent(String id, String name, ToXContent content, XContentType type) {
|
||||
super(id, name, bytes(name, content, type), mimeType(type));
|
||||
super(id, name, bytes(name, content, type), mimeType(type), false);
|
||||
}
|
||||
|
||||
static String mimeType(XContentType type) {
|
||||
|
|
|
@ -49,11 +49,10 @@ public class Email implements ToXContent {
|
|||
final String textBody;
|
||||
final String htmlBody;
|
||||
final Map<String, Attachment> attachments;
|
||||
final Map<String, Inline> inlines;
|
||||
|
||||
public Email(String id, Address from, AddressList replyTo, Priority priority, DateTime sentDate,
|
||||
AddressList to, AddressList cc, AddressList bcc, String subject, String textBody, String htmlBody,
|
||||
Map<String, Attachment> attachments, Map<String, Inline> inlines) {
|
||||
Map<String, Attachment> attachments) {
|
||||
|
||||
this.id = id;
|
||||
this.from = from;
|
||||
|
@ -67,7 +66,6 @@ public class Email implements ToXContent {
|
|||
this.textBody = textBody;
|
||||
this.htmlBody = htmlBody;
|
||||
this.attachments = attachments;
|
||||
this.inlines = inlines;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
|
@ -118,10 +116,6 @@ public class Email implements ToXContent {
|
|||
return attachments;
|
||||
}
|
||||
|
||||
public Map<String, Inline> inlines() {
|
||||
return inlines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
|
@ -249,7 +243,6 @@ public class Email implements ToXContent {
|
|||
private String textBody;
|
||||
private String htmlBody;
|
||||
private Map<String, Attachment> attachments = new HashMap<>();
|
||||
private Map<String, Inline> inlines = new HashMap<>();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
@ -267,7 +260,6 @@ public class Email implements ToXContent {
|
|||
textBody = email.textBody;
|
||||
htmlBody = email.htmlBody;
|
||||
attachments.putAll(email.attachments);
|
||||
inlines.putAll(email.inlines);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -358,14 +350,6 @@ public class Email implements ToXContent {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder inline(Inline inline) {
|
||||
if (inlines == null) {
|
||||
throw new IllegalStateException("Email has already been built!");
|
||||
}
|
||||
inlines.put(inline.id(), inline);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the email. Note that adding items to attachments or inlines
|
||||
* after this is called is incorrect.
|
||||
|
@ -373,9 +357,8 @@ public class Email implements ToXContent {
|
|||
public Email build() {
|
||||
assert id != null : "email id should not be null (should be set to the watch id";
|
||||
Email email = new Email(id, from, replyTo, priority, sentDate, to, cc, bcc, subject, textBody, htmlBody,
|
||||
unmodifiableMap(attachments), unmodifiableMap(inlines));
|
||||
unmodifiableMap(attachments));
|
||||
attachments = null;
|
||||
inlines = null;
|
||||
return email;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.elasticsearch.xpack.notification.email;
|
||||
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.inject.Provider;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xpack.notification.email.support.BodyPartSource;
|
||||
|
||||
import javax.activation.DataHandler;
|
||||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Part;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class Inline extends BodyPartSource {
|
||||
|
||||
protected Inline(String id, String name, String contentType) {
|
||||
super(id, name, contentType);
|
||||
}
|
||||
|
||||
public abstract String type();
|
||||
|
||||
@Override
|
||||
public final MimeBodyPart bodyPart() throws MessagingException {
|
||||
MimeBodyPart part = new MimeBodyPart();
|
||||
part.setDisposition(Part.INLINE);
|
||||
part.setContentID(id);
|
||||
part.setFileName(name);
|
||||
writeTo(part);
|
||||
return part;
|
||||
}
|
||||
|
||||
/**
|
||||
* intentionally not emitting path as it may come as an information leak
|
||||
*/
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject()
|
||||
.field("type", type())
|
||||
.field("id", id)
|
||||
.field("name", name)
|
||||
.field("content_type", contentType)
|
||||
.endObject();
|
||||
}
|
||||
|
||||
protected abstract void writeTo(MimeBodyPart part) throws MessagingException;
|
||||
|
||||
public static class File extends Inline {
|
||||
|
||||
static final String TYPE = "file";
|
||||
|
||||
private final Path path;
|
||||
private DataSource dataSource;
|
||||
|
||||
public File(String id, Path path) {
|
||||
this(id, path.getFileName().toString(), path);
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "uses toFile")
|
||||
public File(String id, String name, Path path) {
|
||||
this(id, name, path, fileTypeMap.getContentType(path.toFile()));
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "uses toFile")
|
||||
public File(String id, String name, Path path, String contentType) {
|
||||
super(id, name, contentType);
|
||||
this.path = path;
|
||||
this.dataSource = new FileDataSource(path.toFile());
|
||||
}
|
||||
|
||||
public Path path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(MimeBodyPart part) throws MessagingException {
|
||||
part.setDataHandler(new DataHandler(dataSource, contentType));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Stream extends Inline {
|
||||
|
||||
static final String TYPE = "stream";
|
||||
|
||||
private final Provider<InputStream> source;
|
||||
|
||||
public Stream(String id, String name, Provider<InputStream> source) {
|
||||
this(id, name, fileTypeMap.getContentType(name), source);
|
||||
}
|
||||
|
||||
public Stream(String id, String name, String contentType, Provider<InputStream> source) {
|
||||
super(id, name, contentType);
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeTo(MimeBodyPart part) throws MessagingException {
|
||||
DataSource ds = new StreamDataSource(name, contentType, source);
|
||||
DataHandler dh = new DataHandler(ds);
|
||||
part.setDataHandler(dh);
|
||||
}
|
||||
|
||||
static class StreamDataSource implements DataSource {
|
||||
|
||||
private final String name;
|
||||
private final String contentType;
|
||||
private final Provider<InputStream> source;
|
||||
|
||||
public StreamDataSource(String name, String contentType, Provider<InputStream> source) {
|
||||
this.name = name;
|
||||
this.contentType = contentType;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return source.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Bytes extends Stream {
|
||||
|
||||
public Bytes(String id, String name, String contentType, byte[] bytes) {
|
||||
super(id, name, contentType, new BytesStreamProvider(bytes));
|
||||
}
|
||||
|
||||
public Bytes(String id, String name, String contentType, BytesReference bytes) {
|
||||
super(id, name, contentType, new BytesStreamProvider(bytes));
|
||||
}
|
||||
|
||||
static class BytesStreamProvider implements Provider<InputStream> {
|
||||
|
||||
private final BytesReference bytes;
|
||||
|
||||
public BytesStreamProvider(byte[] bytes) {
|
||||
this(new BytesArray(bytes));
|
||||
}
|
||||
|
||||
public BytesStreamProvider(BytesReference bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream get() {
|
||||
return new ByteArrayInputStream(bytes.array(), bytes.arrayOffset(), bytes.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,15 +92,13 @@ public enum Profile implements ToXContent {
|
|||
alternative.addBodyPart(html);
|
||||
}
|
||||
|
||||
if (!email.inlines.isEmpty()) {
|
||||
for (Inline inline : email.inlines.values()) {
|
||||
related.addBodyPart(inline.bodyPart());
|
||||
}
|
||||
}
|
||||
|
||||
if (!email.attachments.isEmpty()) {
|
||||
for (Attachment attachment : email.attachments.values()) {
|
||||
mixed.addBodyPart(attachment.bodyPart());
|
||||
if (attachment.isInline()) {
|
||||
related.addBodyPart(attachment.bodyPart());
|
||||
} else {
|
||||
mixed.addBodyPart(attachment.bodyPart());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,10 +54,16 @@ public class DataAttachment implements EmailAttachmentParser.EmailAttachment {
|
|||
return Objects.hash(id, dataAttachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inline() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Builder builder(String id) {
|
||||
return new Builder(id);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,14 @@ public interface EmailAttachmentParser<T extends EmailAttachmentParser.EmailAtta
|
|||
* @return The id of this attachment
|
||||
*/
|
||||
String id();
|
||||
|
||||
/**
|
||||
* Allows the attachment to decide of it should be of disposition type attachment or inline, which is important
|
||||
* for being able to display inside of desktop email clients
|
||||
*
|
||||
* @return a boolean flagging this attachment as being inline
|
||||
*/
|
||||
boolean inline();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,15 +14,15 @@ import org.elasticsearch.common.inject.Inject;
|
|||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.xpack.watcher.support.Variables;
|
||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequest;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequestTemplate;
|
||||
import org.elasticsearch.xpack.common.http.HttpResponse;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.watcher.watch.Payload;
|
||||
import org.elasticsearch.xpack.notification.email.Attachment;
|
||||
import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.xpack.watcher.support.Variables;
|
||||
import org.elasticsearch.xpack.watcher.watch.Payload;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
@ -30,6 +30,7 @@ import java.util.Map;
|
|||
public class HttpEmailAttachementParser implements EmailAttachmentParser<HttpRequestAttachment> {
|
||||
|
||||
public interface Fields {
|
||||
ParseField INLINE = new ParseField("inline");
|
||||
ParseField REQUEST = new ParseField("request");
|
||||
ParseField CONTENT_TYPE = new ParseField("content_type");
|
||||
}
|
||||
|
@ -56,6 +57,7 @@ public class HttpEmailAttachementParser implements EmailAttachmentParser<HttpReq
|
|||
|
||||
@Override
|
||||
public HttpRequestAttachment parse(String id, XContentParser parser) throws IOException {
|
||||
boolean inline = false;
|
||||
String contentType = null;
|
||||
HttpRequestTemplate requestTemplate = null;
|
||||
|
||||
|
@ -66,6 +68,8 @@ public class HttpEmailAttachementParser implements EmailAttachmentParser<HttpReq
|
|||
currentFieldName = parser.currentName();
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.CONTENT_TYPE)) {
|
||||
contentType = parser.text();
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.INLINE)) {
|
||||
inline = parser.booleanValue();
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.REQUEST)) {
|
||||
requestTemplate = requestTemplateParser.parse(parser);
|
||||
} else {
|
||||
|
@ -75,7 +79,7 @@ public class HttpEmailAttachementParser implements EmailAttachmentParser<HttpReq
|
|||
}
|
||||
|
||||
if (requestTemplate != null) {
|
||||
return new HttpRequestAttachment(id, requestTemplate, contentType);
|
||||
return new HttpRequestAttachment(id, requestTemplate, inline, contentType);
|
||||
}
|
||||
|
||||
throw new ElasticsearchParseException("Could not parse http request attachment");
|
||||
|
@ -94,7 +98,7 @@ public class HttpEmailAttachementParser implements EmailAttachmentParser<HttpReq
|
|||
if (response.hasContent()) {
|
||||
String contentType = attachment.getContentType();
|
||||
String attachmentContentType = Strings.hasLength(contentType) ? contentType : response.contentType();
|
||||
return new Attachment.Bytes(attachment.id(), response.body().toBytes(), attachmentContentType);
|
||||
return new Attachment.Bytes(attachment.id(), response.body().toBytes(), attachmentContentType, attachment.inline());
|
||||
} else {
|
||||
logger.error("Empty response body: [host[{}], port[{}], method[{}], path[{}]: response status [{}]", httpRequest.host(),
|
||||
httpRequest.port(), httpRequest.method(), httpRequest.path(), response.status());
|
||||
|
|
|
@ -16,12 +16,14 @@ import java.util.Objects;
|
|||
public class HttpRequestAttachment implements EmailAttachmentParser.EmailAttachment {
|
||||
|
||||
private final HttpRequestTemplate requestTemplate;
|
||||
private boolean inline;
|
||||
private final String contentType;
|
||||
private final String id;
|
||||
|
||||
public HttpRequestAttachment(String id, HttpRequestTemplate requestTemplate, @Nullable String contentType) {
|
||||
public HttpRequestAttachment(String id, HttpRequestTemplate requestTemplate, boolean inline, @Nullable String contentType) {
|
||||
this.id = id;
|
||||
this.requestTemplate = requestTemplate;
|
||||
this.inline = inline;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
|
@ -38,6 +40,11 @@ public class HttpRequestAttachment implements EmailAttachmentParser.EmailAttachm
|
|||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inline() {
|
||||
return inline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject(id)
|
||||
|
@ -46,6 +53,9 @@ public class HttpRequestAttachment implements EmailAttachmentParser.EmailAttachm
|
|||
if (Strings.hasLength(contentType)) {
|
||||
builder.field(HttpEmailAttachementParser.Fields.CONTENT_TYPE.getPreferredName(), contentType);
|
||||
}
|
||||
if (inline) {
|
||||
builder.field(HttpEmailAttachementParser.Fields.INLINE.getPreferredName(), inline);
|
||||
}
|
||||
return builder.endObject().endObject();
|
||||
}
|
||||
|
||||
|
@ -65,12 +75,12 @@ public class HttpRequestAttachment implements EmailAttachmentParser.EmailAttachm
|
|||
|
||||
HttpRequestAttachment otherDataAttachment = (HttpRequestAttachment) o;
|
||||
return Objects.equals(id, otherDataAttachment.id) && Objects.equals(requestTemplate, otherDataAttachment.requestTemplate)
|
||||
&& Objects.equals(contentType, otherDataAttachment.contentType);
|
||||
&& Objects.equals(contentType, otherDataAttachment.contentType) && Objects.equals(inline, otherDataAttachment.inline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, requestTemplate, contentType);
|
||||
return Objects.hash(id, requestTemplate, contentType, inline);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -78,6 +88,7 @@ public class HttpRequestAttachment implements EmailAttachmentParser.EmailAttachm
|
|||
private String id;
|
||||
private HttpRequestTemplate httpRequestTemplate;
|
||||
private String contentType;
|
||||
private boolean inline = false;
|
||||
|
||||
private Builder(String id) {
|
||||
this.id = id;
|
||||
|
@ -93,8 +104,13 @@ public class HttpRequestAttachment implements EmailAttachmentParser.EmailAttachm
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder inline(boolean inline) {
|
||||
this.inline = inline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequestAttachment build() {
|
||||
return new HttpRequestAttachment(id, httpRequestTemplate, contentType);
|
||||
return new HttpRequestAttachment(id, httpRequestTemplate, inline, contentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,9 +41,8 @@ public class EmailTests extends ESTestCase {
|
|||
String textBody = randomFrom("Random Body", "", null);
|
||||
String htmlBody = randomFrom("<hr /><b>BODY</b><hr />", "", null);
|
||||
Map<String, Attachment> attachments = null;
|
||||
Map<String, Inline> inlines = null;
|
||||
|
||||
Email email = new Email(id, from, replyTo, priority, sentDate, to, cc, bcc, subject, textBody, htmlBody, attachments, inlines);
|
||||
Email email = new Email(id, from, replyTo, priority, sentDate, to, cc, bcc, subject, textBody, htmlBody, attachments);
|
||||
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
email.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
|
|
|
@ -109,7 +109,8 @@ public class ManualPublicSmtpServersTester {
|
|||
.textBody("_text_body")
|
||||
.htmlBody("<b>html body</b><p/><p/><img src=\"cid:logo.png\"/>")
|
||||
.attach(new Attachment.XContent.Yaml("test.yml", content))
|
||||
.inline(new Inline.Stream("logo.png", "logo.png", () -> InternalEmailServiceTests.class.getResourceAsStream(path)))
|
||||
.attach(new Attachment.Stream("logo.png", "logo.png", true,
|
||||
() -> InternalEmailServiceTests.class.getResourceAsStream(path)))
|
||||
.build();
|
||||
|
||||
EmailService.EmailSent sent = service.send(email, null, profile);
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.elasticsearch.xpack.notification.email;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.common.secret.SecretService;
|
||||
|
||||
import javax.mail.BodyPart;
|
||||
import javax.mail.Part;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class ProfileTests extends ESTestCase {
|
||||
|
||||
public void testThatInlineAttachmentsAreCreated() throws Exception {
|
||||
String path = "/org/elasticsearch/xpack/watcher/actions/email/service/logo.png";
|
||||
Attachment attachment = new Attachment.Stream("inline.png", "inline.png", true,
|
||||
() -> InternalEmailServiceTests.class.getResourceAsStream(path));
|
||||
|
||||
Email email = Email.builder()
|
||||
.id("foo")
|
||||
.from("foo@example.org")
|
||||
.to("bar@example.org")
|
||||
.subject(randomAsciiOfLength(10))
|
||||
.attach(attachment)
|
||||
.build();
|
||||
|
||||
Settings settings = Settings.builder()
|
||||
.put("default_account", "foo")
|
||||
.put("account.foo.smtp.host", "_host")
|
||||
.build();
|
||||
|
||||
Accounts accounts = new Accounts(settings, SecretService.Insecure.INSTANCE, logger);
|
||||
Session session = accounts.account("foo").getConfig().createSession();
|
||||
MimeMessage mimeMessage = Profile.STANDARD.toMimeMessage(email, session);
|
||||
|
||||
Object content = ((MimeMultipart) mimeMessage.getContent()).getBodyPart(0).getContent();
|
||||
assertThat(content, instanceOf(MimeMultipart.class));
|
||||
MimeMultipart multipart = (MimeMultipart) content;
|
||||
|
||||
assertThat(multipart.getCount(), is(2));
|
||||
boolean foundInlineAttachment = false;
|
||||
BodyPart bodyPart = null;
|
||||
for (int i = 0; i < multipart.getCount(); i++) {
|
||||
bodyPart = multipart.getBodyPart(i);
|
||||
if (Part.INLINE.equalsIgnoreCase(bodyPart.getDisposition())) {
|
||||
foundInlineAttachment = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertThat("Expected to find an inline attachment in mime message, but didnt", foundInlineAttachment, is(true));
|
||||
}
|
||||
}
|
|
@ -93,7 +93,8 @@ public class EmailAttachmentParsersTests extends ESTestCase {
|
|||
attachments.add(new DataAttachment("my-name.json", org.elasticsearch.xpack.notification.email.DataAttachment.JSON));
|
||||
|
||||
HttpRequestTemplate requestTemplate = HttpRequestTemplate.builder("localhost", 80).scheme(Scheme.HTTP).path("/").build();
|
||||
HttpRequestAttachment httpRequestAttachment = new HttpRequestAttachment("other-id", requestTemplate, null);
|
||||
boolean inline = randomBoolean();
|
||||
HttpRequestAttachment httpRequestAttachment = new HttpRequestAttachment("other-id", requestTemplate, inline, null);
|
||||
|
||||
attachments.add(httpRequestAttachment);
|
||||
EmailAttachments emailAttachments = new EmailAttachments(attachments);
|
||||
|
@ -105,6 +106,9 @@ public class EmailAttachmentParsersTests extends ESTestCase {
|
|||
assertThat(builder.string(), containsString("other-id"));
|
||||
assertThat(builder.string(), containsString("localhost"));
|
||||
assertThat(builder.string(), containsString("/"));
|
||||
if (inline) {
|
||||
assertThat(builder.string(), containsString("inline"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testThatTwoAttachmentsWithTheSameIdThrowError() throws Exception {
|
||||
|
@ -161,7 +165,7 @@ public class EmailAttachmentParsersTests extends ESTestCase {
|
|||
|
||||
@Override
|
||||
public Attachment toAttachment(WatchExecutionContext ctx, Payload payload, TestEmailAttachment attachment) {
|
||||
return new Attachment.Bytes(attachment.id(), attachment.getValue().getBytes(Charsets.UTF_8), "personalContentType");
|
||||
return new Attachment.Bytes(attachment.id(), attachment.getValue().getBytes(Charsets.UTF_8), "personalContentType", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,6 +197,11 @@ public class EmailAttachmentParsersTests extends ESTestCase {
|
|||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inline() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject(id)
|
||||
|
|
|
@ -37,15 +37,13 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
public class HttpEmailAttachementParserTests extends ESTestCase {
|
||||
|
||||
private SecretService.Insecure secretService;
|
||||
private HttpAuthRegistry authRegistry;
|
||||
private HttpRequestTemplate.Parser httpRequestTemplateParser;
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
secretService = SecretService.Insecure.INSTANCE;
|
||||
authRegistry = new HttpAuthRegistry(singletonMap(BasicAuth.TYPE, new BasicAuthFactory(secretService)));
|
||||
SecretService.Insecure secretService = SecretService.Insecure.INSTANCE;
|
||||
HttpAuthRegistry authRegistry = new HttpAuthRegistry(singletonMap(BasicAuth.TYPE, new BasicAuthFactory(secretService)));
|
||||
httpRequestTemplateParser = new HttpRequestTemplate.Parser(authRegistry);
|
||||
httpClient = mock(HttpClient.class);
|
||||
|
||||
|
@ -77,9 +75,12 @@ public class HttpEmailAttachementParserTests extends ESTestCase {
|
|||
if (configureContentType) {
|
||||
builder.field("content_type", "application/foo");
|
||||
}
|
||||
boolean isInline = randomBoolean();
|
||||
if (isInline) {
|
||||
builder.field("inline", true);
|
||||
}
|
||||
builder.endObject().endObject().endObject();
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
logger.info("JSON: {}", builder.string());
|
||||
|
||||
EmailAttachments emailAttachments = emailAttachmentsParser.parse(parser);
|
||||
assertThat(emailAttachments.getAttachments(), hasSize(1));
|
||||
|
@ -89,6 +90,7 @@ public class HttpEmailAttachementParserTests extends ESTestCase {
|
|||
attachments.get(0).toXContent(toXcontentBuilder, ToXContent.EMPTY_PARAMS);
|
||||
toXcontentBuilder.endObject();
|
||||
assertThat(toXcontentBuilder.string(), is(builder.string()));
|
||||
}
|
||||
|
||||
assertThat(attachments.get(0).inline(), is(isInline));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -610,7 +610,8 @@ public class EmailActionTests extends ESTestCase {
|
|||
when(httpClient.execute(any(HttpRequest.class))).thenReturn(mockResponse);
|
||||
|
||||
HttpRequestTemplate template = HttpRequestTemplate.builder("localhost", 1234).build();
|
||||
attachments.add(new HttpRequestAttachment(randomAsciiOfLength(10), template, randomFrom("my/custom-type", null)));
|
||||
attachments.add(new HttpRequestAttachment(randomAsciiOfLength(10), template,
|
||||
randomBoolean(), randomFrom("my/custom-type", null)));
|
||||
} else if ("data".equals(attachmentType)) {
|
||||
attachments.add(new org.elasticsearch.xpack.notification.email.attachment.DataAttachment(randomAsciiOfLength(10),
|
||||
randomFrom(DataAttachment.JSON, DataAttachment.YAML)));
|
||||
|
|
|
@ -207,7 +207,4 @@ public class EmailAttachmentTests extends AbstractWatcherIntegrationTestCase {
|
|||
fail("waited too long for email to be received");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue