Email.java
/*
* Copyright (c) 2007-2018 MetaSolutions AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.entrystore.rest.util;
import com.google.common.base.Charsets;
import com.google.common.html.HtmlEscapers;
import org.apache.commons.io.IOUtils;
import org.entrystore.Entry;
import org.entrystore.User;
import org.entrystore.config.Config;
import org.entrystore.repository.config.ConfigurationManager;
import org.entrystore.repository.config.Settings;
import org.entrystore.repository.util.EntryUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.util.Calendar;
import java.util.Properties;
/**
* Helper class for sending emails.
*
* @author Hannes Ebner
*/
public class Email {
private static Logger log = LoggerFactory.getLogger(Email.class);
private static String messageBodySignup;
private static String messageBodyPasswordReset;
private static String messageBodyPasswordChanged;
public static boolean sendMessage(Config config, String msgTo, String msgSubject, String msgBody) {
return sendMessage(config, msgTo, msgSubject, msgBody, null, null);
}
public static boolean sendMessage(Config config, String msgTo, String msgSubject, String msgBody, String msgFrom, String msgReplyTo) {
if (msgFrom == null || msgFrom.isEmpty()) {
msgFrom = config.getString(Settings.SMTP_EMAIL_FROM);
if (msgFrom == null) {
msgFrom = config.getString(Settings.AUTH_FROM_EMAIL_DEPRECATED); // fallback to deprecated setting
}
}
String msgBcc = config.getString(Settings.SMTP_EMAIL_BCC);
if (msgBcc == null) {
msgBcc = config.getString(Settings.AUTH_BCC_EMAIL_DEPRECATED); // fallback to deprecated setting
}
if (msgReplyTo == null || msgReplyTo.isEmpty()) {
msgReplyTo = config.getString(Settings.SMTP_EMAIL_REPLYTO);
}
String host = config.getString(Settings.SMTP_HOST);
int port = config.getInt(Settings.SMTP_PORT, 25);
boolean ssl = "ssl".equalsIgnoreCase(config.getString(Settings.SMTP_SECURITY));
boolean starttls = "starttls".equalsIgnoreCase(config.getString(Settings.SMTP_SECURITY));
final String username = config.getString(Settings.SMTP_USERNAME);
final String password = config.getString(Settings.SMTP_PASSWORD);
Properties props = new Properties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", port);
// SSL/TLS-related settings
if (ssl) {
log.debug("SSL enabled");
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.socketFactory.port", port);
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");
}
if (starttls) {
log.debug("StartTLS enabled");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.starttls.required", "true");
}
// other options, to be made configurable at some later point
props.put("mail.smtp.ssl.checkserveridentity", "true"); // default false
props.put("mail.smtp.connectiontimeout", "5000"); // default infinite
props.put("mail.smtp.timeout", "5000"); // default infinite
props.put("mail.smtp.writetimeout", "5000"); // default infinite
//props.put("mail.debug", "true");
Session session = null;
// Authentication
if (username != null && password != null) {
props.put("mail.smtp.auth", "true");
session = Session.getInstance(props, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
} else {
session = Session.getInstance(props);
}
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(msgFrom));
if (msgReplyTo != null) {
message.setReplyTo(InternetAddress.parse(msgReplyTo));
}
if (msgBcc != null) {
message.addRecipients(Message.RecipientType.BCC, InternetAddress.parse(msgBcc));
}
message.addRecipients(Message.RecipientType.TO, InternetAddress.parse(msgTo));
if (msgSubject.toLowerCase().startsWith("=?utf-8?")) {
message.setHeader("Subject", MimeUtility.fold(9, msgSubject));
} else {
message.setSubject(msgSubject, "UTF-8");
}
message.setText(msgBody, "UTF-8", "html");
int failure = 0;
while (failure < 3) { // we try three times
try {
session.getTransport().send(message);
return true;
} catch (MessagingException me) {
log.error(me.getMessage());
failure++;
}
}
} catch (MessagingException e) {
log.error(e.getMessage());
return false;
}
return false;
}
public static boolean sendSignupConfirmation(Config config, String recipientName, String recipientEmail, String confirmationLink) {
String subject = config.getString(Settings.SIGNUP_SUBJECT, "User sign-up request");
String templatePath = config.getString(Settings.SIGNUP_CONFIRMATION_MESSAGE_TEMPLATE_PATH);
if (messageBodySignup == null) {
if (templatePath == null) {
templatePath = new File(ConfigurationManager.getConfigurationURI("email_signup.html")).getAbsolutePath();
}
if (templatePath != null) {
messageBodySignup = loadTemplate(templatePath);
}
}
if (messageBodySignup == null) {
log.error("Unable to load email template for sign-up confirmation");
return false;
}
String messageText = messageBodySignup.replaceAll("__YEAR__", Integer.toString(Calendar.getInstance().get(Calendar.YEAR)));
messageText = messageText.replaceAll("__DOMAIN__", URI.create(config.getString(Settings.BASE_URL)).getHost());
if (confirmationLink != null) {
messageText = messageText.replaceAll("__CONFIRMATION_LINK__", confirmationLink);
}
if (recipientName != null) {
// we escape the name because it could contain content (e.g. HTML) that cause trouble if displayed in e-mails
messageText = messageText.replaceAll("__NAME__", HtmlEscapers.htmlEscaper().escape(recipientName));
}
if (recipientEmail != null) {
// we escape the name because it could contain content (e.g. HTML) that cause trouble if displayed in e-mails
messageText = messageText.replaceAll("__EMAIL__", HtmlEscapers.htmlEscaper().escape(recipientEmail));
}
return sendMessage(config, recipientEmail, subject, messageText);
}
public static boolean sendPasswordResetConfirmation(Config config, String recipientEmail, String confirmationLink) {
String subject = config.getString(Settings.AUTH_PASSWORD_RESET_SUBJECT, "Password reset request");
if (messageBodyPasswordReset == null) {
String templatePath = config.getString(Settings.AUTH_PASSWORD_RESET_CONFIRMATION_MESSAGE_TEMPLATE_PATH);
if (templatePath == null) {
templatePath = new File(ConfigurationManager.getConfigurationURI("email_pwreset.html")).getAbsolutePath();
}
if (templatePath != null) {
messageBodyPasswordReset = loadTemplate(templatePath);
}
}
if (messageBodyPasswordReset == null) {
log.error("Unable to load email template for sign-up confirmation");
return false;
}
String messageText = messageBodyPasswordReset.replaceAll("__YEAR__", Integer.toString(Calendar.getInstance().get(Calendar.YEAR)));
messageText = messageText.replaceAll("__DOMAIN__", URI.create(config.getString(Settings.BASE_URL)).getHost());
if (confirmationLink != null) {
messageText = messageText.replaceAll("__CONFIRMATION_LINK__", confirmationLink);
}
if (recipientEmail != null) {
messageText = messageText.replaceAll("__EMAIL__", HtmlEscapers.htmlEscaper().escape(recipientEmail));
}
return sendMessage(config, recipientEmail, subject, messageText);
}
public static boolean sendPasswordChangeConfirmation(Config config, Entry userEntry) {
String msgTo = ((User) userEntry.getResource()).getName();
if (!msgTo.contains("@")) {
msgTo = EntryUtil.getEmail(userEntry);
}
if (msgTo == null || !msgTo.contains("@")) {
log.warn("Unable to send email, invalid email address of recipient: " + msgTo);
return false;
}
if (messageBodyPasswordChanged == null) {
String templatePath = config.getString(Settings.AUTH_PASSWORD_CHANGE_CONFIRMATION_MESSAGE_TEMPLATE_PATH);
if (templatePath == null) {
templatePath = new File(ConfigurationManager.getConfigurationURI("email_pwchange.html")).getAbsolutePath();
}
if (templatePath != null) {
messageBodyPasswordChanged = loadTemplate(templatePath);
}
}
if (messageBodyPasswordChanged == null) {
log.error("Unable to load email template for password change confirmation");
return false;
}
String messageText = messageBodyPasswordChanged.replaceAll("__YEAR__", Integer.toString(Calendar.getInstance().get(Calendar.YEAR)));
messageText = messageText.replaceAll("__DOMAIN__", URI.create(config.getString(Settings.BASE_URL)).getHost());
String msgSubject = config.getString(Settings.AUTH_PASSWORD_CHANGE_SUBJECT, "Your password has been changed");
String recipientName = EntryUtil.getName(userEntry);
if (recipientName == null) {
recipientName = "";
}
messageText = messageText.replaceAll("__NAME__", HtmlEscapers.htmlEscaper().escape(recipientName));
return sendMessage(config, msgTo, msgSubject, messageText);
}
private static String loadTemplate(String url) {
if (url == null) {
return null;
}
log.debug("Loading template from " + url);
InputStream is = null;
try {
if (url.startsWith("http://") || url.startsWith("https://")) {
is = new URL(url).openStream();
} else {
is = Files.newInputStream(new File(url).toPath());
}
return IOUtils.toString(is, Charsets.UTF_8);
} catch (IOException e) {
log.error(e.getMessage());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
log.warn(e.getMessage());
}
}
}
return null;
}
}