Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLS and SES support #59

Merged
merged 3 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,27 @@
<url>https://github.com/killbill/killbill-email-notifications-plugin/issues</url>
</issueManagement>
<properties>
<aws-java-sdk-ses.version>1.12.618</aws-java-sdk-ses.version>
<check.fail-dependency>false</check.fail-dependency>
<check.fail-spotbugs>true</check.fail-spotbugs>
<check.spotbugs-exclude-filter-file>spotbugs-exclude.xml</check.spotbugs-exclude-filter-file>
<commons-logging.version>1.3.0</commons-logging.version>
<maven.javadoc.failOnError>false</maven.javadoc.failOnError>
<osgi.export>org.killbill.billing.plugin.notification.api,org.killbill.billing.plugin.notification.generator.*</osgi.export>
<osgi.private>org.killbill.billing.plugin.notification.*</osgi.private>
</properties>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-ses</artifactId>
<version>${aws-java-sdk-ses.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
Expand Down Expand Up @@ -79,6 +92,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging.version}</version>
</dependency>
<dependency>
<groupId>io.zonky.test</groupId>
<artifactId>embedded-postgres</artifactId>
Expand All @@ -101,7 +119,6 @@
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<scope>provided</scope>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was missing at runtime.
java.lang.NoClassDefFoundError: org/joda/time/tz/FixedDateTimeZone

</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

package org.killbill.billing.plugin.notification.email;

import java.io.IOException;
import java.util.List;

import javax.mail.internet.AddressException;
Expand All @@ -34,6 +33,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder;
import com.amazonaws.services.simpleemail.model.Body;
import com.amazonaws.services.simpleemail.model.Content;
import com.amazonaws.services.simpleemail.model.Destination;
import com.amazonaws.services.simpleemail.model.Message;
import com.amazonaws.services.simpleemail.model.SendEmailRequest;
import com.google.common.base.Joiner;

import static org.killbill.billing.plugin.notification.exception.EmailNotificationErrorCode.EMAIL_ADDRESS_INVALID;
Expand All @@ -57,9 +64,13 @@ public class EmailSender {
private static final String SMTP_PWD_PROP = "org.killbill.mail.smtp.password";
private static final String IS_USE_SSL_PROP = "org.killbill.mail.useSSL";
private static final String SMTP_FROM_PROP = "org.killbill.mail.from";

private static final String DEBUG_LOG_ONLY = "org.killbill.billing.plugin.notification.email.logOnly";

private static final String EMAIL_NOTIFICATION_VIA_SES = "org.killbill.email.notification.via.ses";
private static final String AWS_REGION = "org.killbill.aws.region";

private static final String DEFAULT_AWS_REGION = "US_EAST_1";

private final boolean useSmtpAuth;
private final int useSmtpPort;
private final String smtpUserName;
Expand All @@ -70,18 +81,27 @@ public class EmailSender {

private final boolean logOnly;

private final String awsRegion;

private final boolean sendEmailsViaSES;

public EmailSender(final OSGIConfigPropertiesService configProperties) {
this(configProperties.getString(SERVER_NAME_PROP),
(configProperties.getString(SERVER_PORT_PROP) != null ? Integer.parseInt(configProperties.getString(SERVER_PORT_PROP)) : 25),
configProperties.getString(SMTP_USER_PROP),
configProperties.getString(SMTP_PWD_PROP),
configProperties.getString(SMTP_FROM_PROP),
(configProperties.getString(IS_SMTP_AUTH_PROP) != null ? Boolean.valueOf(configProperties.getString(IS_SMTP_AUTH_PROP)) : false),
(configProperties.getString(IS_USE_SSL_PROP) != null ? Boolean.valueOf(configProperties.getString(IS_USE_SSL_PROP)) : false),
(configProperties.getString(DEBUG_LOG_ONLY) != null ? Boolean.valueOf(configProperties.getString(DEBUG_LOG_ONLY)) : false));
(configProperties.getString(IS_SMTP_AUTH_PROP) != null && Boolean.parseBoolean(configProperties.getString(IS_SMTP_AUTH_PROP))),
(configProperties.getString(IS_USE_SSL_PROP) != null && Boolean.parseBoolean(configProperties.getString(IS_USE_SSL_PROP))),
(configProperties.getString(DEBUG_LOG_ONLY) != null && Boolean.parseBoolean(configProperties.getString(DEBUG_LOG_ONLY))),
(configProperties.getString(AWS_REGION) != null ? configProperties.getString(AWS_REGION) : DEFAULT_AWS_REGION),
(configProperties.getString(EMAIL_NOTIFICATION_VIA_SES) != null && Boolean.parseBoolean(configProperties.getString(EMAIL_NOTIFICATION_VIA_SES))));
}

public EmailSender(final String smtpServerName, final int useSmtpPort, final String smtpUserName, final String smtpUserPassword, final String from, final boolean useSmtpAuth, final boolean useSSL, final boolean logOnly) {
public EmailSender(final String smtpServerName, final int useSmtpPort, final String smtpUserName,
final String smtpUserPassword, final String from, final boolean useSmtpAuth,
final boolean useSSL, final boolean logOnly, final String awsRegion,
final boolean sendEmailsViaSES) {
this.useSmtpAuth = useSmtpAuth;
this.useSmtpPort = useSmtpPort;
this.smtpUserName = smtpUserName;
Expand All @@ -90,12 +110,14 @@ public EmailSender(final String smtpServerName, final int useSmtpPort, final Str
this.from = from;
this.useSSL = useSSL;
this.logOnly = logOnly;
this.awsRegion = awsRegion;
this.sendEmailsViaSES = sendEmailsViaSES;
}

// Backward compatibility. If no configuration exists, then reuse Kill Bill email properties
public SmtpProperties precheckSmtp(SmtpProperties smtp) {
public SmtpProperties precheckSmtp(final SmtpProperties smtp) {

if (smtp.getHost() == null && smtpServerName != null){
if (smtp.getHost() == null && smtpServerName != null) {
smtp.setHost(smtpServerName);
smtp.setDefaultSender(this.from);
smtp.setPort(this.useSmtpPort);
Expand All @@ -111,19 +133,28 @@ public SmtpProperties precheckSmtp(SmtpProperties smtp) {
return smtp;
}

public void sendHTMLEmail(final List<String> to, final List<String> cc, final String subject, final String htmlBody, final SmtpProperties smtp) throws EmailException, EmailNotificationException {
public void sendHTMLEmail(final List<String> to, final List<String> cc, final String subject,
final String htmlBody, final SmtpProperties smtp)
throws EmailException, EmailNotificationException {
logger.debug("Sending email to={}, cc={}, subject={}, body=[{}]",
to,
JOINER_ON_COMMA.join(cc),
subject,
htmlBody);
to,
JOINER_ON_COMMA.join(cc),
subject,
htmlBody);
final HtmlEmail email = new HtmlEmail();
email.setHtmlMsg(htmlBody);
email.setCharset("utf-8");
sendEmail(to, cc, subject, email, precheckSmtp(smtp));

if (sendEmailsViaSES) {
sendEmailViaSES(to, cc, subject, htmlBody, smtp);
} else {
sendEmailViaSMTP(to, cc, subject, email, precheckSmtp(smtp));
}
}

public void sendPlainTextEmail(final List<String> to, final List<String> cc, final String subject, final String body, final SmtpProperties smtp) throws IOException, EmailException, EmailNotificationException {
public void sendPlainTextEmail(final List<String> to, final List<String> cc, final String subject,
final String body, final SmtpProperties smtp)
throws EmailException, EmailNotificationException {
logger.debug("Sending email to={}, cc={}, subject={}, body=[{}]",
to,
JOINER_ON_COMMA.join(cc),
Expand All @@ -133,10 +164,17 @@ public void sendPlainTextEmail(final List<String> to, final List<String> cc, fin
final SimpleEmail email = new SimpleEmail();
email.setCharset("utf-8");
email.setMsg(body);
sendEmail(to, cc, subject, email, precheckSmtp(smtp));

if (sendEmailsViaSES) {
sendEmailViaSES(to, cc, subject, body, smtp);
} else {
sendEmailViaSMTP(to, cc, subject, email, precheckSmtp(smtp));
}
}

private void sendEmail(final List<String> to, final List<String> cc, final String subject, final Email email, final SmtpProperties smtp) throws EmailException, EmailNotificationException {
private void sendEmailViaSMTP(final List<String> to, final List<String> cc, final String subject,
final Email email, final SmtpProperties smtp)
throws EmailException, EmailNotificationException {

if (logOnly) {
return;
Expand Down Expand Up @@ -166,49 +204,80 @@ private void sendEmail(final List<String> to, final List<String> cc, final Strin
}

email.setSSLOnConnect(smtp.isUseSSL());
email.setStartTLSEnabled(smtp.isUseSSL());
email.setStartTLSRequired(smtp.isUseSSL());

logger.info("Sending email to={}, cc={}, subject={}", to, cc, subject);
email.send();
}

private void validateEmailFields(final List<String> to, final List<String> cc, final String subject, final SmtpProperties smtp) throws EmailNotificationException {
private void sendEmailViaSES(final List<String> to, final List<String> cc, final String subject,
final String body, final SmtpProperties smtp) {

if (logOnly) {
return;
}

final Regions region = Regions.valueOf(awsRegion.replace("-", "_").toUpperCase());

final AmazonSimpleEmailService client = AmazonSimpleEmailServiceClientBuilder.standard()
.withRegion(region)
.build();
final SendEmailRequest request = new SendEmailRequest()
.withDestination(new Destination().withToAddresses(to).withCcAddresses(cc))
.withMessage(new Message()
.withBody(new Body()
.withHtml(new Content().withCharset("UTF-8").withData(body))
.withText(new Content().withCharset("UTF-8").withData(body)))
.withSubject(new Content()
.withCharset("UTF-8").withData(subject)))
.withSource(smtp.getFrom());

logger.info("Sending email to={}, cc={}, subject={}", to, cc, subject);

client.sendEmail(request);
}

private void validateEmailFields(final List<String> to, final List<String> cc, final String subject,
final SmtpProperties smtp) throws EmailNotificationException {

if (to == null || to.size() == 0 || to.get(0).trim().isEmpty()){
if (to == null || to.isEmpty() || to.get(0).trim().isEmpty()) {
throw new EmailNotificationException(RECIPIENT_EMAIL_ADDRESS_REQUIRED);
}

if (smtp.getFrom() == null || smtp.getFrom().trim().isEmpty()){
if (smtp.getFrom() == null || smtp.getFrom().trim().isEmpty()) {
throw new EmailNotificationException(SENDER_EMAIL_ADDRESS_REQUIRED);
}

if (smtp.isUseAuthentication() && ( (smtp.getUserName() == null || smtp.getUserName().trim().isEmpty()) || (smtp.getPassword() == null || smtp.getPassword().trim().isEmpty()) )){
if (smtp.isUseAuthentication() && ((smtp.getUserName() == null || smtp.getUserName().trim().isEmpty())
|| (smtp.getPassword() == null || smtp.getPassword().trim().isEmpty()))) {
throw new EmailNotificationException(SMTP_AUTHENTICATION_REQUIRED);
}

if (subject == null || subject.trim().isEmpty()){
if (subject == null || subject.trim().isEmpty()) {
throw new EmailNotificationException(SUBJECT_REQUIRED);
}

if (smtp.getHost() == null || smtp.getHost().trim().isEmpty()){
if (smtp.getHost() == null || smtp.getHost().trim().isEmpty()) {
throw new EmailNotificationException(SMTP_HOSTNAME_REQUIRED);
}

validateEmailAddress(smtp.getFrom());

for(String recipient: to){
for (final String recipient : to) {
validateEmailAddress(recipient);
}

for(String recipient: cc){
for (final String recipient : cc) {
validateEmailAddress(recipient);
}
}

private void validateEmailAddress(String email) throws EmailNotificationException {
private void validateEmailAddress(final String email) throws EmailNotificationException {
try {
InternetAddress emailAddr = new InternetAddress(email);
final InternetAddress emailAddr = new InternetAddress(email);
emailAddr.validate();
} catch (AddressException ex) {
} catch (final AddressException ex) {
throw new EmailNotificationException(ex, EMAIL_ADDRESS_INVALID, email);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ public class TestEmailSender {
private static final String TEST_SMTP_PWD = "bar";
private static final String TEST_SMTP_FROM = "[email protected]";

private static final String AWS_REGION = "us-east-1";


@Test(enabled = false)
public void foo() throws IOException, EmailException, EmailNotificationException {
EmailSender sender = new EmailSender(TEST_SMTP_SERVER_NAME, TEST_SMPT_SERVER_PORT, TEST_SMTP_USER, TEST_SMTP_PWD, TEST_SMTP_FROM, true, false, false);
final EmailSender sender = new EmailSender(TEST_SMTP_SERVER_NAME, TEST_SMPT_SERVER_PORT, TEST_SMTP_USER,
TEST_SMTP_PWD, TEST_SMTP_FROM, true, false,
false, AWS_REGION, false);
final String to = "[email protected]";
sender.sendPlainTextEmail(ImmutableList.of(to), ImmutableList.<String>of(), "coucou", "body", Mockito.mock(SmtpProperties.class));
}
Expand Down