[email] fixed bugs and added unit tests

Original commit: elastic/x-pack-elasticsearch@3b5406d4c8
This commit is contained in:
uboness 2015-02-23 04:53:53 +01:00
parent d916f99800
commit b292051a13
23 changed files with 1578 additions and 408 deletions

View File

@ -44,6 +44,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-gmail</artifactId>
<version>v1-rev23-1.19.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.alerts;
import org.elasticsearch.alerts.actions.email.service.InternalEmailService;
import org.elasticsearch.alerts.support.init.InitializingService;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.component.LifecycleComponent;
@ -47,7 +48,8 @@ public class AlertsPlugin extends AbstractPlugin {
// the initialization service must be first in the list
// as other services may depend on one of the initialized
// constructs
InitializingService.class);
InitializingService.class,
InternalEmailService.class);
}
@Override

View File

@ -22,6 +22,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Objects;
/**
*/
@ -29,23 +30,23 @@ public class EmailAction extends Action<EmailAction.Result> {
public static final String TYPE = "email";
private final Email.Builder email;
private final Authentication auth;
private final Profile profile;
private final String account;
private final Template subject;
private final Template textBody;
private final Template htmlBody;
private final boolean attachPayload;
final Email emailPrototype;
final Authentication auth;
final Profile profile;
final String account;
final Template subject;
final Template textBody;
final Template htmlBody;
final boolean attachPayload;
private final EmailService emailService;
final EmailService emailService;
public EmailAction(ESLogger logger, EmailService emailService, Email.Builder email, Authentication auth, Profile profile,
public EmailAction(ESLogger logger, EmailService emailService, Email emailPrototype, Authentication auth, Profile profile,
String account, Template subject, Template textBody, Template htmlBody, boolean attachPayload) {
super(logger);
this.emailService = emailService;
this.email = email;
this.emailPrototype = emailPrototype;
this.auth = auth;
this.profile = profile;
this.account = account;
@ -64,6 +65,10 @@ public class EmailAction extends Action<EmailAction.Result> {
public Result execute(ExecutionContext ctx, Payload payload) throws IOException {
ImmutableMap<String, Object> model = templateModel(ctx, payload);
Email.Builder email = Email.builder()
.id(ctx.id())
.copyFrom(emailPrototype);
email.id(ctx.id());
email.subject(subject.render(model));
email.textBody(textBody.render(model));
@ -73,7 +78,7 @@ public class EmailAction extends Action<EmailAction.Result> {
}
if (attachPayload) {
Attachment.Bytes attachment = new Attachment.XContent.Yaml("payload", "payload.yml", "alert execution output", payload);
Attachment.Bytes attachment = new Attachment.XContent.Yaml("payload", "payload.yml", payload);
email.attach(attachment);
}
@ -86,22 +91,71 @@ public class EmailAction extends Action<EmailAction.Result> {
}
}
@Override
public int hashCode() {
return Objects.hash(emailPrototype, auth, profile, account, subject, textBody, htmlBody, attachPayload);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final EmailAction other = (EmailAction) obj;
return Objects.equals(this.emailPrototype, other.emailPrototype)
&& Objects.equals(this.auth, other.auth)
&& Objects.equals(this.profile, other.profile)
&& Objects.equals(this.account, other.account)
&& Objects.equals(this.subject, other.subject)
&& Objects.equals(this.textBody, other.textBody)
&& Objects.equals(this.htmlBody, other.htmlBody)
&& Objects.equals(this.attachPayload, other.attachPayload);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (account != null) {
builder.field(EmailAction.Parser.ACCOUNT_FIELD.getPreferredName(), account);
builder.field(Parser.ACCOUNT_FIELD.getPreferredName(), account);
}
if (profile != null) {
builder.field(EmailAction.Parser.PROFILE_FIELD.getPreferredName(), profile);
builder.field(Parser.PROFILE_FIELD.getPreferredName(), profile);
}
if (auth != null) {
builder.field(Parser.USER_FIELD.getPreferredName(), auth.user());
builder.field(Parser.PASSWORD_FIELD.getPreferredName(), auth.password());
}
builder.field(Parser.ATTACH_PAYLOAD_FIELD.getPreferredName(), attachPayload);
if (emailPrototype.from() != null) {
builder.field(Email.FROM_FIELD.getPreferredName(), emailPrototype.from());
}
if (emailPrototype.to() != null && !emailPrototype.to().isEmpty()) {
builder.field(Email.TO_FIELD.getPreferredName(), (ToXContent) emailPrototype.to());
}
if (emailPrototype.cc() != null && !emailPrototype.cc().isEmpty()) {
builder.field(Email.CC_FIELD.getPreferredName(), (ToXContent) emailPrototype.cc());
}
if (emailPrototype.bcc() != null && !emailPrototype.bcc().isEmpty()) {
builder.field(Email.BCC_FIELD.getPreferredName(), (ToXContent) emailPrototype.bcc());
}
if (emailPrototype.replyTo() != null && !emailPrototype.replyTo().isEmpty()) {
builder.field(Email.REPLY_TO_FIELD.getPreferredName(), (ToXContent) emailPrototype.replyTo());
}
builder.field(Email.TO_FIELD.getPreferredName(), (ToXContent) email.to());
if (subject != null) {
builder.field(Email.SUBJECT_FIELD.getPreferredName(), subject);
}
if (textBody != null) {
builder.field(Email.TEXT_BODY_FIELD.getPreferredName(), textBody);
}
if (htmlBody != null) {
builder.field(Email.HTML_BODY_FIELD.getPreferredName(), htmlBody);
}
if (emailPrototype.priority() != null) {
builder.field(Email.PRIORITY_FIELD.getPreferredName(), emailPrototype.priority());
}
return builder.endObject();
}
@ -136,7 +190,7 @@ public class EmailAction extends Action<EmailAction.Result> {
String password = null;
String account = null;
Profile profile = null;
Email.Builder email = Email.builder();
Email.Builder email = Email.builder().id("prototype");
Template subject = null;
Template textBody = null;
Template htmlBody = null;
@ -202,12 +256,9 @@ public class EmailAction extends Action<EmailAction.Result> {
}
}
if (email.to() == null || email.to().isEmpty()) {
throw new ActionSettingsException("could not parse email action. [to] was not found or was empty");
}
Authentication auth = user != null ? new Authentication(user, password) : null;
Authentication auth = new Authentication(user, password);
return new EmailAction(logger, emailService, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
return new EmailAction(logger, emailService, email.build(), auth, profile, account, subject, textBody, htmlBody, attachPayload);
}
@Override
@ -263,7 +314,6 @@ public class EmailAction extends Action<EmailAction.Result> {
super(type, success);
}
public static class Success extends Result {
private final EmailService.EmailSent sent;
@ -297,6 +347,10 @@ public class EmailAction extends Action<EmailAction.Result> {
this.reason = reason;
}
public String reason() {
return reason;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field("reason", reason);

View File

@ -43,8 +43,12 @@ public class Account {
// applying the defaults on missing emails fields
email = config.defaults.apply(email);
if (email.to == null) {
throw new EmailException("email must have [to] recipient");
}
Transport transport = session.getTransport(SMTP_PROTOCOL);
String user = auth != null ? auth.username() : null;
String user = auth != null ? auth.user() : null;
if (user == null) {
user = config.smtp.user;
if (user == null) {
@ -85,10 +89,10 @@ public class Account {
static final String SMTP_SETTINGS_PREFIX = "mail.smtp.";
private final String name;
private final Profile profile;
private final Smtp smtp;
private final EmailDefaults defaults;
final String name;
final Profile profile;
final Smtp smtp;
final EmailDefaults defaults;
public Config(String name, Settings settings) {
this.name = name;
@ -106,16 +110,16 @@ public class Account {
static class Smtp {
private final String host;
private final int port;
private final String user;
private final String password;
private final Properties properties;
final String host;
final int port;
final String user;
final String password;
final Properties properties;
public Smtp(Settings settings) {
host = settings.get("host");
port = settings.getAsInt("port", settings.getAsInt("localport", 25));
user = settings.get("user", settings.get("from", settings.get("local_address", null)));
host = settings.get("host", settings.get("localaddress", settings.get("local_address")));
port = settings.getAsInt("port", settings.getAsInt("localport", settings.getAsInt("local_port", 25)));
user = settings.get("user", settings.get("from", null));
password = settings.get("password", null);
properties = loadSmtpProperties(settings);
}
@ -165,7 +169,7 @@ public class Account {
* needed on each alert (e.g. if all the emails are always sent to the same recipients
* one could set those here and leave them out on the alert definition).
*/
class EmailDefaults {
static class EmailDefaults {
final Email.Address from;
final Email.AddressList replyTo;
@ -186,16 +190,59 @@ public class Account {
}
Email apply(Email email) {
return Email.builder()
.from(from)
.replyTo(replyTo)
.priority(priority)
.to(to)
.cc(cc)
.bcc(bcc)
.subject(subject)
.copyFrom(email)
.build();
Email.Builder builder = Email.builder().copyFrom(email);
if (email.from == null) {
builder.from(from);
}
if (email.replyTo == null) {
builder.replyTo(replyTo);
}
if (email.priority == null) {
builder.priority(priority);
}
if (email.to == null) {
builder.to(to);
}
if (email.cc == null) {
builder.cc(cc);
}
if (email.bcc == null) {
builder.bcc(bcc);
}
if (email.subject == null) {
builder.subject(subject);
}
return builder.build();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmailDefaults that = (EmailDefaults) o;
if (bcc != null ? !bcc.equals(that.bcc) : that.bcc != null) return false;
if (cc != null ? !cc.equals(that.cc) : that.cc != null) return false;
if (from != null ? !from.equals(that.from) : that.from != null) return false;
if (priority != that.priority) return false;
if (replyTo != null ? !replyTo.equals(that.replyTo) : that.replyTo != null) return false;
if (subject != null ? !subject.equals(that.subject) : that.subject != null) return false;
if (to != null ? !to.equals(that.to) : that.to != null) return false;
return true;
}
@Override
public int hashCode() {
int result = from != null ? from.hashCode() : 0;
result = 31 * result + (replyTo != null ? replyTo.hashCode() : 0);
result = 31 * result + (priority != null ? priority.hashCode() : 0);
result = 31 * result + (to != null ? to.hashCode() : 0);
result = 31 * result + (cc != null ? cc.hashCode() : 0);
result = 31 * result + (bcc != null ? bcc.hashCode() : 0);
result = 31 * result + (subject != null ? subject.hashCode() : 0);
return result;
}
}
}

View File

@ -8,7 +8,6 @@ package org.elasticsearch.alerts.actions.email.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -21,31 +20,27 @@ public class Accounts {
private final Map<String, Account> accounts;
public Accounts(Settings settings, ESLogger logger) {
settings = settings.getAsSettings("account");
Map<String, Account> accounts = new HashMap<>();
for (String name : settings.names()) {
Account.Config config = new Account.Config(name, settings.getAsSettings(name));
Settings accountsSettings = settings.getAsSettings("account");
accounts = new HashMap<>();
for (String name : accountsSettings.names()) {
Account.Config config = new Account.Config(name, accountsSettings.getAsSettings(name));
Account account = new Account(config, logger);
accounts.put(name, account);
}
if (accounts.isEmpty()) {
this.accounts = Collections.emptyMap();
this.defaultAccountName = null;
} else {
this.accounts = accounts;
String defaultAccountName = settings.get("default_account");
if (defaultAccountName == null) {
Account account = accounts.values().iterator().next();
logger.error("default account set to [{}]", account.name());
this.defaultAccountName = account.name();
} else if (!accounts.containsKey(defaultAccountName)) {
Account account = accounts.values().iterator().next();
this.defaultAccountName = account.name();
logger.error("could not find configured default account [{}]. falling back on account [{}]", defaultAccountName, account.name());
String defaultAccountName = settings.get("default_account");
if (defaultAccountName == null) {
if (accounts.isEmpty()) {
this.defaultAccountName = null;
} else {
this.defaultAccountName = defaultAccountName;
Account account = accounts.values().iterator().next();
logger.info("default account set to [{}]", account.name());
this.defaultAccountName = account.name();
}
} else if (!accounts.containsKey(defaultAccountName)) {
throw new EmailSettingsException("could not fine default account [" + defaultAccountName + "]");
} else {
this.defaultAccountName = defaultAccountName;
}
}

View File

@ -6,7 +6,6 @@
package org.elasticsearch.alerts.actions.email.service;
import org.elasticsearch.alerts.actions.email.service.support.BodyPartSource;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
@ -26,16 +25,8 @@ import java.nio.file.Path;
*/
public abstract class Attachment extends BodyPartSource {
public Attachment(String id) {
super(id);
}
public Attachment(String id, String name) {
super(id, name);
}
public Attachment(String id, String name, String description) {
super(id, name, description);
protected Attachment(String id, String name, String contentType) {
super(id, name, contentType);
}
@Override
@ -43,12 +34,26 @@ public abstract class Attachment extends BodyPartSource {
MimeBodyPart part = new MimeBodyPart();
part.setContentID(id);
part.setFileName(name);
part.setDescription(description, Charsets.UTF_8.name());
part.setDisposition(Part.ATTACHMENT);
writeTo(part);
return part;
}
public abstract String type();
/**
* 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 Attachment {
@ -57,7 +62,6 @@ public abstract class Attachment extends BodyPartSource {
private final Path path;
private final DataSource dataSource;
private final String contentType;
public File(String id, Path path) {
this(id, path.getFileName().toString(), path);
@ -68,21 +72,13 @@ public abstract class Attachment extends BodyPartSource {
}
public File(String id, String name, Path path) {
this(id, name, name, path);
this(id, name, path, fileTypeMap.getContentType(path.toFile()));
}
public File(String id, String name, Path path, String contentType) {
this(id, name, name, path, contentType);
}
public File(String id, String name, String description, Path path) {
this(id, name, description, path, null);
}
public File(String id, String name, String description, Path path, String contentType) {
super(id, name, description);
super(id, name, contentType);
this.path = path;
this.dataSource = new FileDataSource(path.toFile());
this.contentType = contentType;
}
public Path path() {
@ -93,31 +89,9 @@ public abstract class Attachment extends BodyPartSource {
return TYPE;
}
String contentType() {
return contentType != null ? contentType : dataSource.getContentType();
}
@Override
public void writeTo(MimeBodyPart part) throws MessagingException {
DataSource dataSource = new FileDataSource(path.toFile());
DataHandler handler = contentType != null ?
new DataHandler(dataSource, contentType) :
new DataHandler(dataSource);
part.setDataHandler(handler);
}
/**
* 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("description", description)
.field("content_type", contentType())
.endObject();
part.setDataHandler(new DataHandler(dataSource));
}
}
@ -126,20 +100,18 @@ public abstract class Attachment extends BodyPartSource {
static final String TYPE = "bytes";
private final byte[] bytes;
private final String contentType;
public Bytes(String id, byte[] bytes, String contentType) {
this(id, id, bytes, contentType);
}
public Bytes(String id, String name, byte[] bytes, String contentType) {
this(id, name, name, bytes, contentType);
public Bytes(String id, String name, byte[] bytes) {
this(id, name, bytes, fileTypeMap.getContentType(name));
}
public Bytes(String id, String name, String description, byte[] bytes, String contentType) {
super(id, name, description);
public Bytes(String id, String name, byte[] bytes, String contentType) {
super(id, name, contentType);
this.bytes = bytes;
this.contentType = contentType;
}
public String type() {
@ -150,27 +122,12 @@ public abstract class Attachment extends BodyPartSource {
return bytes;
}
public String contentType() {
return contentType;
}
@Override
public void writeTo(MimeBodyPart part) throws MessagingException {
DataSource dataSource = new ByteArrayDataSource(bytes, contentType);
DataHandler handler = new DataHandler(dataSource);
part.setDataHandler(handler);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field("type", type())
.field("id", id)
.field("name", name)
.field("description", description)
.field("content_type", contentType)
.endObject();
}
}
public static class XContent extends Bytes {
@ -180,11 +137,7 @@ public abstract class Attachment extends BodyPartSource {
}
protected XContent(String id, String name, ToXContent content, XContentType type) {
this(id, name, name, content, type);
}
protected XContent(String id, String name, String description, ToXContent content, XContentType type) {
super(id, name, description, bytes(name, content, type), mimeType(type));
super(id, name, bytes(name, content, type), mimeType(type));
}
static String mimeType(XContentType type) {
@ -202,7 +155,7 @@ public abstract class Attachment extends BodyPartSource {
try {
XContentBuilder builder = XContentBuilder.builder(type.xContent());
content.toXContent(builder, ToXContent.EMPTY_PARAMS);
return builder.bytes().array();
return builder.bytes().toBytes();
} catch (IOException ioe) {
throw new EmailException("could not create an xcontent attachment [" + name + "]", ioe);
}
@ -218,10 +171,6 @@ public abstract class Attachment extends BodyPartSource {
super(id, name, content, XContentType.YAML);
}
public Yaml(String id, String name, String description, ToXContent content) {
super(id, name, description, content, XContentType.YAML);
}
@Override
public String type() {
return "yaml";
@ -238,10 +187,6 @@ public abstract class Attachment extends BodyPartSource {
super(id, name, content, XContentType.JSON);
}
public Json(String id, String name, String description, ToXContent content) {
super(id, name, description, content, XContentType.JSON);
}
@Override
public String type() {
return "json";

View File

@ -10,19 +10,39 @@ package org.elasticsearch.alerts.actions.email.service;
*/
public class Authentication {
private final String username;
private final String user;
private final String password;
public Authentication(String username, String password) {
this.username = username;
public Authentication(String user, String password) {
this.user = user;
this.password = password;
}
public String username() {
return username;
public String user() {
return user;
}
public String password() {
return password;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Authentication that = (Authentication) o;
if (password != null ? !password.equals(that.password) : that.password != null) return false;
if (user != null ? !user.equals(that.user) : that.user != null) return false;
return true;
}
@Override
public int hashCode() {
int result = user != null ? user.hashCode() : 0;
result = 31 * result + (password != null ? password.hashCode() : 0);
return result;
}
}

View File

@ -20,16 +20,14 @@ import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.*;
/**
*
*/
public class Email implements ToXContent {
public static final ParseField ID_FIELD = new ParseField("id");
public static final ParseField FROM_FIELD = new ParseField("from");
public static final ParseField REPLY_TO_FIELD = new ParseField("reply_to");
public static final ParseField PRIORITY_FIELD = new ParseField("priority");
@ -40,8 +38,6 @@ public class Email implements ToXContent {
public static final ParseField SUBJECT_FIELD = new ParseField("subject");
public static final ParseField TEXT_BODY_FIELD = new ParseField("text_body");
public static final ParseField HTML_BODY_FIELD = new ParseField("html_body");
public static final ParseField ATTACHMENTS_FIELD = new ParseField("attachments");
public static final ParseField INLINES_FIELD = new ParseField("inlines");
final String id;
final Address from;
@ -65,7 +61,7 @@ public class Email implements ToXContent {
this.from = from;
this.replyTo = replyTo;
this.priority = priority;
this.sentDate = sentDate;
this.sentDate = sentDate != null ? sentDate : new DateTime();
this.to = to;
this.cc = cc;
this.bcc = bcc;
@ -76,6 +72,10 @@ public class Email implements ToXContent {
this.inlines = inlines;
}
public String id() {
return id;
}
public Address from() {
return from;
}
@ -126,20 +126,46 @@ public class Email implements ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(FROM_FIELD.getPreferredName(), from)
.field(REPLY_TO_FIELD.getPreferredName(), (ToXContent) replyTo)
.field(PRIORITY_FIELD.getPreferredName(), priority)
.field(SENT_DATE_FIELD.getPreferredName(), sentDate)
.field(TO_FIELD.getPreferredName(), (ToXContent) to)
.field(CC_FIELD.getPreferredName(), (ToXContent) cc)
.field(BCC_FIELD.getPreferredName(), (ToXContent) bcc)
.field(SUBJECT_FIELD.getPreferredName(), subject)
.field(TEXT_BODY_FIELD.getPreferredName(), textBody)
.field(HTML_BODY_FIELD.getPreferredName(), htmlBody)
.field(ATTACHMENTS_FIELD.getPreferredName(), attachments)
.field(INLINES_FIELD.getPreferredName(), inlines)
.endObject();
builder.startObject();
builder.field(ID_FIELD.getPreferredName(), id);
builder.field(FROM_FIELD.getPreferredName(), from);
if (replyTo != null) {
builder.field(REPLY_TO_FIELD.getPreferredName(), (ToXContent) replyTo);
}
if (priority != null) {
builder.field(PRIORITY_FIELD.getPreferredName(), priority);
}
builder.field(SENT_DATE_FIELD.getPreferredName(), sentDate);
builder.field(TO_FIELD.getPreferredName(), (ToXContent) to);
if (cc != null) {
builder.field(CC_FIELD.getPreferredName(), (ToXContent) cc);
}
if (bcc != null) {
builder.field(BCC_FIELD.getPreferredName(), (ToXContent) bcc);
}
builder.field(SUBJECT_FIELD.getPreferredName(), subject);
builder.field(TEXT_BODY_FIELD.getPreferredName(), textBody);
if (htmlBody != null) {
builder.field(HTML_BODY_FIELD.getPreferredName(), htmlBody);
}
return builder.endObject();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Email email = (Email) o;
if (!id.equals(email.id)) return false;
return true;
}
@Override
public int hashCode() {
return id.hashCode();
}
public static Builder builder() {
@ -154,7 +180,9 @@ public class Email implements ToXContent {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if ((token.isValue() || token == XContentParser.Token.START_OBJECT || token == XContentParser.Token.START_ARRAY) && currentFieldName != null) {
if (FROM_FIELD.match(currentFieldName)) {
if (ID_FIELD.match(currentFieldName)) {
email.id(parser.text());
} else if (FROM_FIELD.match(currentFieldName)) {
email.from(Address.parse(currentFieldName, token, parser));
} else if (REPLY_TO_FIELD.match(currentFieldName)) {
email.replyTo(AddressList.parse(currentFieldName, token, parser));
@ -174,10 +202,6 @@ public class Email implements ToXContent {
email.textBody(parser.text());
} else if (HTML_BODY_FIELD.match(currentFieldName)) {
email.htmlBody(parser.text());
} else if (ATTACHMENTS_FIELD.match(currentFieldName)) {
//@TODO handle this
} else if (INLINES_FIELD.match(currentFieldName)) {
//@TODO handle this
} else {
throw new EmailException("could not parse email. unrecognized field [" + currentFieldName + "]");
}
@ -293,7 +317,6 @@ public class Email implements ToXContent {
public Email build() {
assert id != null : "email id should not be null (should be set to the alert id";
assert to != null && !to.isEmpty() : "email must have a [to] recipient";
return new Email(id, from, replyTo, priority, sentDate, to, cc, bcc, subject, textBody, htmlBody, attachments.build(), inlines.build());
}
@ -427,6 +450,8 @@ public class Email implements ToXContent {
public static class AddressList implements Iterable<Address>, ToXContent {
public static final AddressList EMPTY = new AddressList(Collections.<Address>emptyList());
private final List<Address> addresses;
public AddressList(List<Address> addresses) {
@ -446,6 +471,10 @@ public class Email implements ToXContent {
return addresses.toArray(new Address[addresses.size()]);
}
public int size() {
return addresses.size();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startArray();
@ -499,6 +528,23 @@ public class Email implements ToXContent {
throw new EmailException("could not parse [" + field + "] as address list. field must either be a string " +
"(comma-separated list of RFC822 encoded addresses) or an array of objects representing addresses");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AddressList addresses1 = (AddressList) o;
if (!addresses.equals(addresses1.addresses)) return false;
return true;
}
@Override
public int hashCode() {
return addresses.hashCode();
}
}
}

View File

@ -5,17 +5,11 @@
*/
package org.elasticsearch.alerts.actions.email.service;
import org.elasticsearch.cluster.ClusterState;
/**
*
*/
public interface EmailService {
void start(ClusterState state);
void stop();
EmailSent send(Email email, Authentication auth, Profile profile);
EmailSent send(Email email, Authentication auth, Profile profile, String accountName);

View File

@ -6,6 +6,9 @@
package org.elasticsearch.alerts.actions.email.service;
import org.elasticsearch.alerts.actions.email.service.support.BodyPartSource;
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 javax.activation.DataHandler;
@ -14,7 +17,10 @@ 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;
/**
@ -22,26 +28,35 @@ import java.nio.file.Path;
*/
public abstract class Inline extends BodyPartSource {
public Inline(String id) {
super(id);
protected Inline(String id, String name, String contentType) {
super(id, name, contentType);
}
public Inline(String id, String name) {
super(id, name);
}
public Inline(String id, String name, String description) {
super(id, name, description);
}
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 {
@ -50,32 +65,19 @@ public abstract class Inline extends BodyPartSource {
private final Path path;
private DataSource dataSource;
private final String contentType;
public File(String id, Path path) {
this(id, path.getFileName().toString(), path);
}
public File(String id, Path path, String contentType) {
this(id, path.getFileName().toString(), path, contentType);
}
public File(String id, String name, Path path) {
this(id, name, name, path);
this(id, name, path, fileTypeMap.getContentType(path.toFile()));
}
public File(String id, String name, Path path, String contentType) {
this(id, name, name, path, contentType);
}
public File(String id, String name, String description, Path path) {
this(id, name, description, path, null);
}
public File(String id, String name, String description, Path path, String contentType) {
super(id, name, description);
super(id, name, contentType);
this.path = path;
this.dataSource = new FileDataSource(path.toFile());
this.contentType = contentType;
}
public Path path() {
@ -86,30 +88,99 @@ public abstract class Inline extends BodyPartSource {
return TYPE;
}
String contentType() {
return contentType != null ? contentType : dataSource.getContentType();
}
@Override
public void writeTo(MimeBodyPart part) throws MessagingException {
DataHandler handler = contentType != null ?
new DataHandler(dataSource, contentType) :
new DataHandler(dataSource);
part.setDataHandler(handler);
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;
}
/**
* 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("description", description)
.field("content_type", contentType())
.endObject();
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());
}
}
}
}

View File

@ -5,25 +5,23 @@
*/
package org.elasticsearch.alerts.actions.email.service;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.settings.NodeSettingsService;
import javax.mail.MessagingException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
*
*/
public class InternalEmailService extends AbstractComponent implements EmailService {
public class InternalEmailService extends AbstractLifecycleComponent<InternalEmailService> implements EmailService {
private volatile Accounts accounts;
private final AtomicBoolean started = new AtomicBoolean(false);
@Inject
public InternalEmailService(Settings settings, NodeSettingsService nodeSettingsService) {
super(settings);
@ -35,6 +33,19 @@ public class InternalEmailService extends AbstractComponent implements EmailServ
});
}
@Override
protected void doStart() throws ElasticsearchException {
reset(settings);
}
@Override
protected void doStop() throws ElasticsearchException {
}
@Override
protected void doClose() throws ElasticsearchException {
}
@Override
public EmailSent send(Email email, Authentication auth, Profile profile) {
return send(email, auth, profile, (String) null);
@ -59,29 +70,16 @@ public class InternalEmailService extends AbstractComponent implements EmailServ
return new EmailSent(account.name(), email);
}
@Override
public synchronized void start(ClusterState state) {
if (started.get()) {
return;
}
reset(state.metaData().settings());
started.set(true);
}
@Override
public synchronized void stop() {
started.set(false);
}
synchronized void reset(Settings nodeSettings) {
if (!started.get()) {
return;
}
void reset(Settings nodeSettings) {
Settings settings = ImmutableSettings.builder()
.put(componentSettings)
.put(nodeSettings.getComponentSettings(InternalEmailService.class))
.build();
accounts = new Accounts(settings, logger);
accounts = createAccounts(settings, logger);
}
protected Accounts createAccounts(Settings settings, ESLogger logger) {
return new Accounts(settings, logger);
}
}

View File

@ -16,7 +16,6 @@ import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
/**
@ -26,11 +25,51 @@ import java.util.Locale;
public enum Profile implements ToXContent {
STANDARD() {
@Override
public String textBody(MimeMessage msg) throws IOException, MessagingException {
MimeMultipart mixed = (MimeMultipart) msg.getContent();
MimeMultipart related = null;
for (int i = 0; i < mixed.getCount(); i++) {
MimeBodyPart part = (MimeBodyPart) mixed.getBodyPart(i);
if (part.getContentType().startsWith("multipart/related")) {
related = (MimeMultipart) part.getContent();
break;
}
}
if (related == null) {
throw new EmailException("could not extract body text from mime message");
}
MimeMultipart alternative = null;
for (int i = 0; i < related.getCount(); i++) {
MimeBodyPart part = (MimeBodyPart) related.getBodyPart(i);
if (part.getContentType().startsWith("multipart/alternative")) {
alternative = (MimeMultipart) part.getContent();
break;
}
}
if (alternative == null) {
throw new EmailException("could not extract body text from mime message");
}
for (int i = 0; i < alternative.getCount(); i++) {
MimeBodyPart part = (MimeBodyPart) alternative.getBodyPart(i);
if (part.getContentType().startsWith("text/plain")) {
return (String) part.getContent();
}
}
throw new EmailException("could not extract body text from mime message");
}
@Override
public MimeMessage toMimeMessage(Email email, Session session) throws MessagingException {
MimeMessage message = createCommon(email, session);
MimeMultipart mixed = new MimeMultipart("mixed");
message.setContent(mixed);
MimeMultipart related = new MimeMultipart("related");
mixed.addBodyPart(wrap(related, null));
@ -39,12 +78,16 @@ public enum Profile implements ToXContent {
related.addBodyPart(wrap(alternative, "text/alternative"));
MimeBodyPart text = new MimeBodyPart();
text.setText(email.textBody, Charsets.UTF_8.name());
if (email.textBody != null) {
text.setText(email.textBody, Charsets.UTF_8.name());
} else {
text.setText("", Charsets.UTF_8.name());
}
alternative.addBodyPart(text);
if (email.htmlBody != null) {
MimeBodyPart html = new MimeBodyPart();
text.setText(email.textBody, Charsets.UTF_8.name(), "html");
html.setText(email.htmlBody, Charsets.UTF_8.name(), "html");
alternative.addBodyPart(html);
}
@ -65,18 +108,36 @@ public enum Profile implements ToXContent {
},
OUTLOOK() {
@Override
public String textBody(MimeMessage msg) throws IOException, MessagingException {
return STANDARD.textBody(msg);
}
@Override
public MimeMessage toMimeMessage(Email email, Session session) throws MessagingException {
return STANDARD.toMimeMessage(email, session);
}
},
GMAIL() {
@Override
public String textBody(MimeMessage msg) throws IOException, MessagingException {
return STANDARD.textBody(msg);
}
@Override
public MimeMessage toMimeMessage(Email email, Session session) throws MessagingException {
return STANDARD.toMimeMessage(email, session);
}
},
MAC() {
@Override
public String textBody(MimeMessage msg) throws IOException, MessagingException {
return STANDARD.textBody(msg);
}
@Override
public MimeMessage toMimeMessage(Email email, Session session) throws MessagingException {
return STANDARD.toMimeMessage(email, session);
@ -87,6 +148,8 @@ public enum Profile implements ToXContent {
public abstract MimeMessage toMimeMessage(Email email, Session session) throws MessagingException ;
public abstract String textBody(MimeMessage msg) throws IOException, MessagingException;
public static Profile resolve(String name) {
Profile profile = resolve(name, null);
if (profile == null) {
@ -104,6 +167,7 @@ public enum Profile implements ToXContent {
case "standard": return STANDARD;
case "outlook": return OUTLOOK;
case "gmail": return GMAIL;
case "mac": return MAC;
default:
return defaultProfile;
}
@ -126,14 +190,19 @@ public enum Profile implements ToXContent {
if (email.priority != null) {
email.priority.applyTo(message);
}
Date sentDate = email.sentDate != null ? email.sentDate.toDate() : new Date();
message.setSentDate(sentDate);
message.setSentDate(email.sentDate.toDate());
message.setRecipients(Message.RecipientType.TO, email.to.toArray());
message.setRecipients(Message.RecipientType.CC, email.cc.toArray());
message.setRecipients(Message.RecipientType.BCC, email.bcc.toArray());
message.setSubject(email.subject, Charsets.UTF_8.name());
if (email.cc != null) {
message.setRecipients(Message.RecipientType.CC, email.cc.toArray());
}
if (email.bcc != null) {
message.setRecipients(Message.RecipientType.BCC, email.bcc.toArray());
}
if (email.subject != null) {
message.setSubject(email.subject, Charsets.UTF_8.name());
} else {
message.setSubject("", Charsets.UTF_8.name());
}
return message;
}

View File

@ -7,6 +7,7 @@ package org.elasticsearch.alerts.actions.email.service.support;
import org.elasticsearch.common.xcontent.ToXContent;
import javax.activation.FileTypeMap;
import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
@ -15,22 +16,20 @@ import javax.mail.internet.MimeBodyPart;
*/
public abstract class BodyPartSource implements ToXContent {
protected static FileTypeMap fileTypeMap = FileTypeMap.getDefaultFileTypeMap();
protected final String id;
protected final String name;
protected final String description;
protected final String contentType;
public BodyPartSource(String id) {
this(id, id);
public BodyPartSource(String id, String contentType) {
this(id, id, contentType);
}
public BodyPartSource(String id, String name) {
this(id, name, name);
}
public BodyPartSource(String id, String name, String description) {
public BodyPartSource(String id, String name, String contentType) {
this.id = id;
this.name = name;
this.description = description;
this.contentType = contentType;
}
public String id() {
@ -41,8 +40,8 @@ public abstract class BodyPartSource implements ToXContent {
return name;
}
public String description() {
return description;
public String contentType() {
return contentType;
}
public abstract MimeBodyPart bodyPart() throws MessagingException;

View File

@ -50,6 +50,10 @@ public class ScriptTemplate implements ToXContent, Template {
this(service, text, DEFAULT_LANG, ScriptService.ScriptType.INLINE, Collections.<String, Object>emptyMap());
}
public ScriptTemplate(ScriptServiceProxy service, String text, String lang, ScriptService.ScriptType type) {
this(service, text, lang, type, Collections.<String, Object>emptyMap());
}
public ScriptTemplate(ScriptServiceProxy service, String text, String lang, ScriptService.ScriptType type, Map<String, Object> params) {
this.service = service;
this.text = text;
@ -135,8 +139,12 @@ public class ScriptTemplate implements ToXContent, Template {
@Override
public ScriptTemplate parse(XContentParser parser) throws IOException {
assert parser.currentToken() == XContentParser.Token.START_OBJECT : "Expected START_OBJECT, but was " + parser.currentToken();
if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
return new ScriptTemplate(scriptService, parser.text());
}
if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
throw new ParseException("expected either a string or an object, but found [" + parser.currentToken() + "] instead");
}
String text = null;
ScriptService.ScriptType type = ScriptService.ScriptType.INLINE;
String lang = DEFAULT_LANG;

View File

@ -55,7 +55,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.*;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.hamcrest.Matchers.*;
@ -174,12 +174,12 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
Email.AddressList to = new Email.AddressList(emailAddressList);
Email.Builder emailBuilder = Email.builder();
Email.Builder emailBuilder = Email.builder().id("prototype");
emailBuilder.from(from);
emailBuilder.to(to);
EmailAction emailAction = new EmailAction(logger, noopEmailService(), emailBuilder,
EmailAction emailAction = new EmailAction(logger, noopEmailService(), emailBuilder.build(),
new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", body, body, null, true);
actions.add(emailAction);
@ -471,19 +471,10 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
}
private static class NoopEmailService implements EmailService {
@Override
public void start(ClusterState state) {
}
@Override
public void stop() {
}
@Override
public EmailSent send(Email email, Authentication auth, Profile profile) {
return new EmailSent(auth.username(), email);
return new EmailSent(auth.user(), email);
}
@Override

View File

@ -1,116 +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.alerts.actions.email;
import org.elasticsearch.alerts.Alert;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.actions.email.service.Authentication;
import org.elasticsearch.alerts.actions.email.service.Email;
import org.elasticsearch.alerts.actions.email.service.EmailService;
import org.elasticsearch.alerts.actions.email.service.Profile;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.alerts.support.template.ScriptTemplate;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.mustache.MustacheScriptEngineService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import javax.mail.MessagingException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*/
public class EmailActionTest extends ElasticsearchTestCase {
public void testEmailTemplateRender() throws IOException, MessagingException {
Settings settings = ImmutableSettings.settingsBuilder().build();
MustacheScriptEngineService mustacheScriptEngineService = new MustacheScriptEngineService(settings);
ThreadPool threadPool = new ThreadPool(ThreadPool.Names.SAME);
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
engineServiceSet.add(mustacheScriptEngineService);
ScriptServiceProxy scriptService = ScriptServiceProxy.of(new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, threadPool)));
ScriptTemplate template = new ScriptTemplate(scriptService, "{{alert_name}} executed with {{response.hits.total}} hits");
EmailService emailService = new EmailServiceMock();
Email.Address from = new Email.Address("from@test.com");
List<Email.Address> emailAddressList = new ArrayList<>();
emailAddressList.add(new Email.Address("to@test.com"));
Email.AddressList to = new Email.AddressList(emailAddressList);
Email.Builder emailBuilder = Email.builder();
emailBuilder.from(from);
emailBuilder.to(to);
EmailAction emailAction = new EmailAction(logger, emailService, emailBuilder,
new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", template, template, null, true);
//This is ok since the execution of the action only relies on the alert name
Alert alert = new Alert(
"test-serialization",
new CronSchedule("0/5 * * * * ? *"),
null,
null,
new TimeValue(0),
null,
null,
new Alert.Status()
);
ExecutionContext ctx = new ExecutionContext("test-serialization#1", alert, new DateTime(), new DateTime());
EmailAction.Result result = emailAction.execute(ctx, new Payload.Simple());
threadPool.shutdownNow();
assertTrue(result.success());
EmailAction.Result.Success success = (EmailAction.Result.Success) result;
assertEquals(success.account(), "testaccount");
assertArrayEquals(success.email().to().toArray(), to.toArray() );
assertEquals(success.email().from(), from);
//@TODO add more here
}
static class EmailServiceMock implements EmailService {
@Override
public void start(ClusterState state) {
}
@Override
public void stop() {
}
@Override
public EmailSent send(Email email, Authentication auth, Profile profile) {
return new EmailSent(auth.username(), email);
}
@Override
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) {
return new EmailSent(accountName, email);
}
}
}

View File

@ -0,0 +1,365 @@
/*
* 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.alerts.actions.email;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.alerts.Alert;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.actions.ActionSettingsException;
import org.elasticsearch.alerts.actions.email.service.*;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.alerts.support.template.ScriptTemplate;
import org.elasticsearch.alerts.support.template.Template;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class EmailActionTests extends ElasticsearchTestCase {
@Test @Repeat(iterations = 20)
public void testExecute() throws Exception {
final String account = "account1";
EmailService service = new EmailService() {
@Override
public EmailSent send(Email email, Authentication auth, Profile profile) {
return new EmailSent(account, email);
}
@Override
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) {
return new EmailSent(account, email);
}
};
Email email = Email.builder().id("prototype").build();
Authentication auth = new Authentication("user", "passwd");
Profile profile = randomFrom(Profile.values());
Template subject = mock(Template.class);
Template textBody = mock(Template.class);
Template htmlBody = randomBoolean() ? null : mock(Template.class);
boolean attachPayload = randomBoolean();
EmailAction action = new EmailAction(logger, service, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
final Map<String, Object> data = new HashMap<>();
Payload payload = new Payload() {
@Override
public Map<String, Object> data() {
return data;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.map(data);
}
};
String ctxId = randomAsciiOfLength(5);
ExecutionContext ctx = mock(ExecutionContext.class);
when(ctx.id()).thenReturn(ctxId);
Alert alert = mock(Alert.class);
when(alert.name()).thenReturn("alert1");
when(ctx.alert()).thenReturn(alert);
Map<String, Object> expectedModel = ImmutableMap.<String, Object>builder()
.put("alert_name", "alert1")
.put("payload", data)
.build();
when(subject.render(expectedModel)).thenReturn("_subject");
when(textBody.render(expectedModel)).thenReturn("_text_body");
if (htmlBody != null) {
when (htmlBody.render(expectedModel)).thenReturn("_html_body");
}
EmailAction.Result result = action.execute(ctx, payload);
assertThat(result, notNullValue());
assertThat(result, instanceOf(EmailAction.Result.Success.class));
assertThat(((EmailAction.Result.Success) result).account(), equalTo(account));
Email actualEmail = ((EmailAction.Result.Success) result).email();
assertThat(actualEmail.id(), is(ctxId));
assertThat(actualEmail, notNullValue());
assertThat(actualEmail.subject(), is("_subject"));
assertThat(actualEmail.textBody(), is("_text_body"));
if (htmlBody != null) {
assertThat(actualEmail.htmlBody(), is("_html_body"));
}
if (attachPayload) {
assertThat(actualEmail.attachments(), hasKey("payload"));
}
}
@Test @Repeat(iterations = 20)
public void testParser() throws Exception {
ScriptServiceProxy scriptService = mock(ScriptServiceProxy.class);
EmailService emailService = mock(EmailService.class);
Profile profile = randomFrom(Profile.values());
Email.Priority priority = randomFrom(Email.Priority.values());
Email.Address[] to = rarely() ? null : Email.AddressList.parse(randomBoolean() ? "to@domain" : "to1@domain,to2@domain").toArray();
Email.Address[] cc = rarely() ? null : Email.AddressList.parse(randomBoolean() ? "cc@domain" : "cc1@domain,cc2@domain").toArray();
Email.Address[] bcc = rarely() ? null : Email.AddressList.parse(randomBoolean() ? "bcc@domain" : "bcc1@domain,bcc2@domain").toArray();
Email.Address[] replyTo = rarely() ? null : Email.AddressList.parse(randomBoolean() ? "reply@domain" : "reply1@domain,reply2@domain").toArray();
ScriptTemplate subject = randomBoolean() ? new ScriptTemplate(scriptService, "_subject") : null;
ScriptTemplate textBody = randomBoolean() ? new ScriptTemplate(scriptService, "_text_body") : null;
ScriptTemplate htmlBody = randomBoolean() ? new ScriptTemplate(scriptService, "_text_html") : null;
boolean attachPayload = randomBoolean();
XContentBuilder builder = jsonBuilder().startObject()
.field("account", "_account")
.field("profile", profile.name())
.field("user", "_user")
.field("password", "_passwd")
.field("attach_payload", attachPayload)
.field("from", "from@domain")
.field("priority", priority.name());
if (to != null) {
if (to.length == 1) {
builder.field("to", to[0]);
} else {
builder.array("to", (Object[]) to);
}
}
if (cc != null) {
if (cc.length == 1) {
builder.field("cc", cc[0]);
} else {
builder.array("cc", (Object[]) cc);
}
}
if (bcc != null) {
if (bcc.length == 1) {
builder.field("bcc", bcc[0]);
} else {
builder.array("bcc", (Object[]) bcc);
}
}
if (replyTo != null) {
if (replyTo.length == 1) {
builder.field("reply_to", replyTo[0]);
} else {
builder.array("reply_to", (Object[]) replyTo);
}
}
if (subject != null) {
if (randomBoolean()) {
builder.field("subject", subject.text());
} else {
builder.field("subject", subject);
}
}
if (textBody != null) {
if (randomBoolean()) {
builder.field("text_body", textBody.text());
} else {
builder.field("text_body", textBody);
}
}
if (htmlBody != null) {
if (randomBoolean()) {
builder.field("html_body", htmlBody.text());
} else {
builder.field("html_body", htmlBody);
}
}
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
EmailAction action = new EmailAction.Parser(ImmutableSettings.EMPTY, emailService,
new ScriptTemplate.Parser(ImmutableSettings.EMPTY, scriptService)).parse(parser);
assertThat(action, notNullValue());
assertThat(action.account, is("_account"));
assertThat(action.attachPayload, is(attachPayload));
assertThat(action.auth, notNullValue());
assertThat(action.auth.user(), is("_user"));
assertThat(action.auth.password(), is("_passwd"));
assertThat(action.subject, is((Template) subject));
assertThat(action.textBody, is((Template) textBody));
assertThat(action.htmlBody, is((Template) htmlBody));
assertThat(action.emailPrototype.priority(), is(priority));
if (to != null) {
assertThat(action.emailPrototype.to().toArray(), arrayContainingInAnyOrder(to));
} else {
assertThat(action.emailPrototype.to(), nullValue());
}
if (cc != null) {
assertThat(action.emailPrototype.cc().toArray(), arrayContainingInAnyOrder(cc));
} else {
assertThat(action.emailPrototype.cc(), nullValue());
}
if (bcc != null) {
assertThat(action.emailPrototype.bcc().toArray(), arrayContainingInAnyOrder(bcc));
} else {
assertThat(action.emailPrototype.bcc(), nullValue());
}
if (replyTo != null) {
assertThat(action.emailPrototype.replyTo().toArray(), arrayContainingInAnyOrder(replyTo));
} else {
assertThat(action.emailPrototype.replyTo(), nullValue());
}
}
@Test @Repeat(iterations = 20)
public void testParser_SelfGenerated() throws Exception {
EmailService service = mock(EmailService.class);
Email.Builder emailPrototypeBuilder = Email.builder().id("prototype");
if (randomBoolean()) {
emailPrototypeBuilder.from(new Email.Address("from@domain"));
}
if (randomBoolean()) {
emailPrototypeBuilder.to(Email.AddressList.parse(randomBoolean() ? "to@domain" : "to1@domain,to2@damain"));
}
if (randomBoolean()) {
emailPrototypeBuilder.cc(Email.AddressList.parse(randomBoolean() ? "cc@domain" : "cc1@domain,cc2@damain"));
}
if (randomBoolean()) {
emailPrototypeBuilder.bcc(Email.AddressList.parse(randomBoolean() ? "bcc@domain" : "bcc1@domain,bcc2@damain"));
}
if (randomBoolean()) {
emailPrototypeBuilder.replyTo(Email.AddressList.parse(randomBoolean() ? "reply@domain" : "reply1@domain,reply2@damain"));
}
Email email = emailPrototypeBuilder.build();
Authentication auth = randomBoolean() ? null : new Authentication("_user", "_passwd");
Profile profile = randomFrom(Profile.values());
String account = randomAsciiOfLength(6);
Template subject = new TemplateMock("_subject");
Template textBody = new TemplateMock("_text_body");
Template htmlBody = randomBoolean() ? null : new TemplateMock("_html_body");
boolean attachPayload = randomBoolean();
EmailAction action = new EmailAction(logger, service, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
XContentBuilder builder = jsonBuilder();
action.toXContent(builder, Attachment.XContent.EMPTY_PARAMS);
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
EmailAction parsed = new EmailAction.Parser(ImmutableSettings.EMPTY, service, new TemplateMock.Parser()).parse(parser);
assertThat(parsed, equalTo(action));
}
@Test(expected = ActionSettingsException.class) @Repeat(iterations = 100)
public void testParser_Invalid() throws Exception {
ScriptServiceProxy scriptService = mock(ScriptServiceProxy.class);
EmailService emailService = mock(EmailService.class);
XContentBuilder builder = jsonBuilder().startObject()
.field("unknown_field", "value");
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
new EmailAction.Parser(ImmutableSettings.EMPTY, emailService,
new ScriptTemplate.Parser(ImmutableSettings.EMPTY, scriptService)).parse(parser);
}
@Test @Repeat(iterations = 20)
public void testParser_Result() throws Exception {
boolean success = randomBoolean();
Email email = Email.builder().id("_id")
.from(new Email.Address("from@domain"))
.to(Email.AddressList.parse("to@domain"))
.sentDate(new DateTime())
.subject("_subject")
.textBody("_text_body")
.build();
XContentBuilder builder = jsonBuilder().startObject()
.field("success", success);
if (success) {
builder.field("email", email);
builder.field("account", "_account");
} else {
builder.field("reason", "_reason");
}
builder.endObject();
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
EmailAction.Result result = new EmailAction.Parser(ImmutableSettings.EMPTY, mock(EmailService.class), new TemplateMock.Parser())
.parseResult(parser);
assertThat(result.success(), is(success));
if (success) {
assertThat(result, instanceOf(EmailAction.Result.Success.class));
assertThat(((EmailAction.Result.Success) result).email(), equalTo(email));
assertThat(((EmailAction.Result.Success) result).account(), is("_account"));
} else {
assertThat(result, instanceOf(EmailAction.Result.Failure.class));
assertThat(((EmailAction.Result.Failure) result).reason(), is("_reason"));
}
}
@Test(expected = EmailException.class)
public void testParser_Result_Invalid() throws Exception {
XContentBuilder builder = jsonBuilder().startObject()
.field("unknown_field", "value")
.endObject();
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
new EmailAction.Parser(ImmutableSettings.EMPTY, mock(EmailService.class), new TemplateMock.Parser())
.parseResult(parser);
}
static class TemplateMock implements Template {
private final String name;
public TemplateMock(String name) {
this.name = name;
}
@Override
public String render(Map<String, Object> model) {
return "";
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.value(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TemplateMock that = (TemplateMock) o;
if (!name.equals(that.name)) return false;
return true;
}
@Override
public int hashCode() {
return name.hashCode();
}
public static class Parser implements Template.Parser<TemplateMock> {
@Override
public TemplateMock parse(XContentParser parser) throws IOException, ParseException {
return new TemplateMock(parser.text());
}
}
}
}

View File

@ -0,0 +1,269 @@
/*
* 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.alerts.actions.email.service;
import org.elasticsearch.alerts.actions.email.service.support.EmailServer;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.*;
/**
*
*/
public class AccountTests extends ElasticsearchTestCase {
static final String USERNAME = "_user";
static final String PASSWORD = "_passwd";
private EmailServer server;
@Before
public void init() throws Exception {
server = new EmailServer("localhost", 2500, USERNAME, PASSWORD);
server.start();
}
@After
public void cleanup() throws Exception {
server.stop();
}
@Test
public void testConfig() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder();
Profile profile = rarely() ? Profile.STANDARD : randomFrom(Profile.values());
if (profile != Profile.STANDARD) {
builder.put("profile", profile.name());
}
Account.Config.EmailDefaults emailDefaults;
if (randomBoolean()) {
ImmutableSettings.Builder sb = ImmutableSettings.builder();
if (randomBoolean()) {
sb.put(Email.FROM_FIELD.getPreferredName(), "from@domain");
}
if (randomBoolean()) {
sb.put(Email.REPLY_TO_FIELD.getPreferredName(), "replyto@domain");
}
if (randomBoolean()) {
sb.put(Email.PRIORITY_FIELD.getPreferredName(), randomFrom(Email.Priority.values()));
}
if (randomBoolean()) {
sb.put(Email.TO_FIELD.getPreferredName(), "to@domain");
}
if (randomBoolean()) {
sb.put(Email.CC_FIELD.getPreferredName(), "cc@domain");
}
if (randomBoolean()) {
sb.put(Email.BCC_FIELD.getPreferredName(), "bcc@domain");
}
if (randomBoolean()) {
sb.put(Email.SUBJECT_FIELD.getPreferredName(), "_subject");
}
Settings settings = sb.build();
emailDefaults = new Account.Config.EmailDefaults(settings);
for (String name : settings.names()) {
builder.put("email_defaults." + name, settings.get(name));
}
} else {
emailDefaults = new Account.Config.EmailDefaults(ImmutableSettings.EMPTY);
}
Properties smtpProps = new Properties();
ImmutableSettings.Builder smtpBuilder = ImmutableSettings.builder();
String host = "somehost";
String setting = randomFrom("host", "localaddress", "local_address");
smtpBuilder.put(setting, host);
if (setting.equals("local_address")) {
// we need to remove the `_`... we only added support for `_` for readability
// the actual properties (java mail properties) don't contain underscores
setting = "localaddress";
}
smtpProps.put("mail.smtp." + setting, host);
String user = null;
if (randomBoolean()) {
user = randomAsciiOfLength(5);
setting = randomFrom("user", "from");
smtpBuilder.put(setting, user);
smtpProps.put("mail.smtp." + setting, user);
}
int port = 25;
if (randomBoolean()) {
port = randomIntBetween(2000, 2500);
setting = randomFrom("port", "localport", "local_port");
smtpBuilder.put(setting, port);
if (setting.equals("local_port")) {
setting = "localport";
}
smtpProps.setProperty("mail.smtp." + setting, String.valueOf(port));
}
String password = null;
if (randomBoolean()) {
password = randomAsciiOfLength(8);
smtpBuilder.put("password", password);
smtpProps.put("mail.smtp.password", password);
}
for (int i = 0; i < 5; i++) {
String name = randomAsciiOfLength(5);
String value = randomAsciiOfLength(6);
smtpProps.put("mail.smtp." + name, value);
smtpBuilder.put(name, value);
}
Settings smtpSettings = smtpBuilder.build();
for (String name : smtpSettings.names()) {
builder.put("smtp." + name, smtpSettings.get(name));
}
Settings settings = builder.build();
Account.Config config = new Account.Config("_name", settings);
if (profile != null) {
assertThat(config.profile, is(profile));
}
assertThat(config.defaults, equalTo(emailDefaults));
assertThat(config.smtp, notNullValue());
assertThat(config.smtp.port, is(port));
assertThat(config.smtp.host, is(host));
assertThat(config.smtp.user, is(user));
assertThat(config.smtp.password, is(password));
assertThat(config.smtp.properties, equalTo(smtpProps));
}
@Test
public void testSend() throws Exception {
Account account = new Account(new Account.Config("default", ImmutableSettings.builder()
.put("smtp.host", "localhost")
.put("smtp.port", 2500)
.put("smtp.user", USERNAME)
.put("smtp.password", PASSWORD)
.build()), logger);
Email email = Email.builder()
.id("_id")
.from(new Email.Address("from@domain.com"))
.to(Email.AddressList.parse("To<to@domain.com>"))
.subject("_subject")
.textBody("_text_body")
.build();
final CountDownLatch latch = new CountDownLatch(1);
EmailServer.Listener.Handle handle = server.addListener(new EmailServer.Listener() {
@Override
public void on(MimeMessage message) throws Exception {
assertThat(message.getFrom().length, is(1));
assertThat((InternetAddress) message.getFrom()[0], equalTo(new InternetAddress("from@domain.com")));
assertThat(message.getRecipients(Message.RecipientType.TO).length, is(1));
assertThat((InternetAddress) message.getRecipients(Message.RecipientType.TO)[0], equalTo(new InternetAddress("to@domain.com", "To")));
assertThat(message.getSubject(), equalTo("_subject"));
assertThat(Profile.STANDARD.textBody(message), equalTo("_text_body"));
latch.countDown();
}
});
account.send(email, null, Profile.STANDARD);
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("waiting for email too long");
}
handle.remove();
}
@Test
public void testSend_CC_BCC() throws Exception {
Account account = new Account(new Account.Config("default", ImmutableSettings.builder()
.put("smtp.host", "localhost")
.put("smtp.port", 2500)
.put("smtp.user", USERNAME)
.put("smtp.password", PASSWORD)
.build()), logger);
Email email = Email.builder()
.id("_id")
.from(new Email.Address("from@domain.com"))
.to(Email.AddressList.parse("TO<to@domain.com>"))
.cc(Email.AddressList.parse("CC1<cc1@domain.com>,cc2@domain.com"))
.bcc(Email.AddressList.parse("BCC1<bcc1@domain.com>,bcc2@domain.com"))
.replyTo(Email.AddressList.parse("noreply@domain.com"))
.build();
final CountDownLatch latch = new CountDownLatch(5);
EmailServer.Listener.Handle handle = server.addListener(new EmailServer.Listener() {
@Override
public void on(MimeMessage message) throws Exception {
assertThat(message.getFrom().length, is(1));
assertThat((InternetAddress) message.getFrom()[0], equalTo(new InternetAddress("from@domain.com")));
assertThat(message.getRecipients(Message.RecipientType.TO).length, is(1));
assertThat((InternetAddress) message.getRecipients(Message.RecipientType.TO)[0], equalTo(new InternetAddress("to@domain.com", "TO")));
assertThat(message.getRecipients(Message.RecipientType.CC).length, is(2));
assertThat(message.getRecipients(Message.RecipientType.CC), hasItemInArray((Address) new InternetAddress("cc1@domain.com", "CC1")));
assertThat(message.getRecipients(Message.RecipientType.CC), hasItemInArray((Address) new InternetAddress("cc2@domain.com")));
assertThat(message.getReplyTo(), arrayWithSize(1));
assertThat(message.getReplyTo(), hasItemInArray((Address) new InternetAddress("noreply@domain.com")));
// bcc should not be there... (it's bcc after all)
latch.countDown();
}
});
account.send(email, null, Profile.STANDARD);
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("waiting for email too long");
}
handle.remove();
}
@Test
public void testSend_Authentication() throws Exception {
Account account = new Account(new Account.Config("default", ImmutableSettings.builder()
.put("smtp.host", "localhost")
.put("smtp.port", 2500)
.build()), logger);
Email email = Email.builder()
.id("_id")
.from(new Email.Address("from@domain.com"))
.to(Email.AddressList.parse("To<to@domain.com>"))
.subject("_subject")
.textBody("_text_body")
.build();
final CountDownLatch latch = new CountDownLatch(1);
EmailServer.Listener.Handle handle = server.addListener(new EmailServer.Listener() {
@Override
public void on(MimeMessage message) throws Exception {
latch.countDown();
}
});
account.send(email, new Authentication(USERNAME, PASSWORD), Profile.STANDARD);
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("waiting for email too long");
}
handle.remove();
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.alerts.actions.email.service;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.isOneOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
/**
*
*/
public class AccountsTests extends ElasticsearchTestCase {
@Test
public void testSingleAccount() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
Accounts accounts = new Accounts(builder.build(), logger);
Account account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account1"));
account = accounts.account(null); // falling back on the default
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account1"));
}
@Test
public void testSingleAccount_NoExplicitDefault() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder();
addAccountSettings("account1", builder);
Accounts accounts = new Accounts(builder.build(), logger);
Account account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account1"));
account = accounts.account(null); // falling back on the default
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account1"));
}
@Test
public void testMultipleAccounts() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
Accounts accounts = new Accounts(builder.build(), logger);
Account account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account1"));
account = accounts.account("account2");
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account2"));
account = accounts.account(null); // falling back on the default
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account1"));
}
@Test
public void testMultipleAccounts_NoExplicitDefault() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
Accounts accounts = new Accounts(builder.build(), logger);
Account account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account1"));
account = accounts.account("account2");
assertThat(account, notNullValue());
assertThat(account.name(), equalTo("account2"));
account = accounts.account(null);
assertThat(account, notNullValue());
assertThat(account.name(), isOneOf("account1", "account2"));
}
@Test(expected = EmailSettingsException.class)
public void testMultipleAccounts_UnknownDefault() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("default_account", "unknown");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
new Accounts(builder.build(), logger);
}
@Test
public void testNoAccount() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder();
Accounts accounts = new Accounts(builder.build(), logger);
Account account = accounts.account(null);
assertThat(account, nullValue());
}
@Test(expected = EmailSettingsException.class)
public void testNoAccount_WithDefaultAccount() throws Exception {
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("default_account", "unknown");
new Accounts(builder.build(), logger);
}
private void addAccountSettings(String name, ImmutableSettings.Builder builder) {
builder.put("account." + name + ".smtp.host", "_host");
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.alerts.actions.email.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
/**
*
*/
public class InternalEmailServiceTests extends ElasticsearchTestCase {
private InternalEmailService service;
private Accounts accounts;
@Before
public void init() throws Exception {
accounts = mock(Accounts.class);
service = new InternalEmailService(ImmutableSettings.EMPTY, new NodeSettingsService(ImmutableSettings.EMPTY)) {
@Override
protected Accounts createAccounts(Settings settings, ESLogger logger) {
return accounts;
}
};
service.start();
}
@After
public void cleanup() throws Exception {
service.stop();
}
@Test
public void testSend() throws Exception {
Account account = mock(Account.class);
when(account.name()).thenReturn("account1");
when(accounts.account("account1")).thenReturn(account);
Email email = mock(Email.class);
Authentication auth = new Authentication("user", "passwd");
Profile profile = randomFrom(Profile.values());
EmailService.EmailSent sent = service.send(email, auth, profile, "account1");
verify(account).send(email, auth, profile);
assertThat(sent, notNullValue());
assertThat(sent.email(), sameInstance(email));
assertThat(sent.account(), is("account1"));
}
}

View File

@ -0,0 +1,120 @@
/*
* 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.alerts.actions.email.service;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.inject.Provider;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.junit.Ignore;
import java.io.IOException;
import java.io.InputStream;
/**
*
*/
@Ignore
public class ManualPublicSmtpServersTests {
private static final Terminal terminal = Terminal.DEFAULT;
public static class Gmail {
public static void main(String[] args) throws Exception {
test(Profile.GMAIL, ImmutableSettings.builder()
.put("alerts.actions.email.service.account.gmail.smtp.auth", true)
.put("alerts.actions.email.service.account.gmail.smtp.starttls.enable", true)
.put("alerts.actions.email.service.account.gmail.smtp.host", "smtp.gmail.com")
.put("alerts.actions.email.service.account.gmail.smtp.port", 587)
.put("alerts.actions.email.service.account.gmail.smtp.user", terminal.readText("username: "))
.put("alerts.actions.email.service.account.gmail.smtp.password", new String(terminal.readSecret("password: ")))
.put("alerts.actions.email.service.account.gmail.email_defaults.to", terminal.readText("to: "))
);
}
}
public static class OutlookDotCom {
public static void main(String[] args) throws Exception {
test(Profile.STANDARD, ImmutableSettings.builder()
.put("alerts.actions.email.service.account.outlook.smtp.auth", true)
.put("alerts.actions.email.service.account.outlook.smtp.starttls.enable", true)
.put("alerts.actions.email.service.account.outlook.smtp.host", "smtp-mail.outlook.com")
.put("alerts.actions.email.service.account.outlook.smtp.port", 587)
.put("alerts.actions.email.service.account.outlook.smtp.user", "elastic.user@outlook.com")
.put("alerts.actions.email.service.account.outlook.smtp.password", "fantastic42")
.put("alerts.actions.email.service.account.outlook.email_defaults.to", "elastic.user@outlook.com")
.put()
);
}
}
public static class YahooMail {
public static void main(String[] args) throws Exception {
test(Profile.STANDARD, ImmutableSettings.builder()
.put("alerts.actions.email.service.account.yahoo.smtp.starttls.enable", true)
.put("alerts.actions.email.service.account.yahoo.smtp.auth", true)
.put("alerts.actions.email.service.account.yahoo.smtp.host", "smtp.mail.yahoo.com")
.put("alerts.actions.email.service.account.yahoo.smtp.port", 587)
.put("alerts.actions.email.service.account.yahoo.smtp.user", "elastic.user@yahoo.com")
.put("alerts.actions.email.service.account.yahoo.smtp.password", "fantastic42")
// note: from must be set to the same authenticated user account
.put("alerts.actions.email.service.account.yahoo.email_defaults.from", "elastic.user@yahoo.com")
.put("alerts.actions.email.service.account.yahoo.email_defaults.to", "elastic.user@yahoo.com")
);
}
}
static void test(Profile profile, Settings.Builder builder) throws Exception {
InternalEmailService service = startEmailService(builder);
try {
ToXContent content = new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field("key1", "value1")
.field("key2", "value2")
.field("key3", "value3")
.endObject();
}
};
Email email = Email.builder()
.id("_id")
.subject("_subject")
.textBody("_text_body")
.htmlBody("<b>html body</b><p/><p/><img src=\"cid:logo\"/>")
.attach(new Attachment.XContent.Yaml("test.yml", content))
.inline(new Inline.Stream("logo", "logo.jpg", new Provider<InputStream>() {
@Override
public InputStream get() {
return InternalEmailServiceTests.class.getResourceAsStream("logo.jpg");
}
}))
.build();
EmailService.EmailSent sent = service.send(email, null, profile);
terminal.println("email sent via account [%s]", sent.account());
} finally {
service.stop();
}
}
static InternalEmailService startEmailService(Settings.Builder builder) {
Settings settings = builder.build();
InternalEmailService service = new InternalEmailService(settings, new NodeSettingsService(settings));
service.start();
return service;
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.alerts.actions.email.service.support;
import org.subethamail.smtp.TooMuchDataException;
import org.subethamail.smtp.auth.EasyAuthenticationHandlerFactory;
import org.subethamail.smtp.auth.LoginFailedException;
import org.subethamail.smtp.auth.UsernamePasswordValidator;
import org.subethamail.smtp.helper.SimpleMessageListener;
import org.subethamail.smtp.helper.SimpleMessageListenerAdapter;
import org.subethamail.smtp.server.SMTPServer;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.fail;
/**
* An mini email smtp server that can be used for unit testing
*
*
*/
public class EmailServer {
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
private final SMTPServer server;
public EmailServer(String host, int port, final String username, final String password) {
server = new SMTPServer(new SimpleMessageListenerAdapter(new SimpleMessageListener() {
@Override
public boolean accept(String from, String recipient) {
return true;
}
@Override
public void deliver(String from, String recipient, InputStream data) throws TooMuchDataException, IOException {
try {
Session session = Session.getDefaultInstance(new Properties());
MimeMessage msg = new MimeMessage(session, data);
for (Listener listener : listeners) {
try {
listener.on(msg);
} catch (Exception e) {
fail(e.getMessage());
e.printStackTrace();
}
}
} catch (MessagingException me) {
throw new RuntimeException("could not create mime message", me);
}
}
}), new EasyAuthenticationHandlerFactory(new UsernamePasswordValidator() {
@Override
public void login(String user, String passwd) throws LoginFailedException {
assertThat(user, is(username));
assertThat(passwd, is(password));
}
}));
server.setHostName(host);
server.setPort(port);
}
public void start() {
server.start();
}
public void stop() {
server.stop();
listeners.clear();
}
public Listener.Handle addListener(Listener listener) {
listeners.add(listener);
return new Listener.Handle(listeners, listener);
}
public static interface Listener {
void on(MimeMessage message) throws Exception;
public static class Handle {
private final List<Listener> listeners;
private final Listener listener;
Handle(List<Listener> listeners, Listener listener) {
this.listeners = listeners;
this.listener = listener;
}
public void remove() {
listeners.remove(listener);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB