From e962f0f27181a367eb8c9f54f64b995f935a5085 Mon Sep 17 00:00:00 2001 From: Daniel Augusto Veronezi Salvador <38945620+GutoVeronezi@users.noreply.github.com> Date: Tue, 15 Jun 2021 18:18:26 -0300 Subject: [PATCH] Unify SMTP mail sending (#4954) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add mail dependencies * Create util to send SMTP mail * Add unit tests to SMTP mail sender * Use SMTP mail util on quota alert * Use SMTP mail util on alert * Use SMTP mail util on project * Use SMTP mail util on usage alert * Remove copyright line in license header Co-authored-by: Gabriel Beims Bräscher * Remove copyright line in license header Co-authored-by: Gabriel Beims Bräscher * Remove copyright line in license header Co-authored-by: Gabriel Beims Bräscher * Remove copyright line in license header Co-authored-by: Gabriel Beims Bräscher * Remove copyright line in license header Co-authored-by: Gabriel Beims Bräscher Co-authored-by: Daniel Augusto Veronezi Salvador Co-authored-by: Gabriel Beims Bräscher --- .../quota/QuotaAlertManagerImpl.java | 147 +---- .../quota/QuotaAlertManagerImplTest.java | 14 +- .../com/cloud/alert/AlertManagerImpl.java | 275 +++----- .../cloud/projects/ProjectManagerImpl.java | 152 +---- .../cloud/usage/UsageAlertManagerImpl.java | 234 ++----- utils/pom.xml | 9 + .../cloudstack/utils/mailing/MailAddress.java | 63 ++ .../utils/mailing/SMTPMailProperties.java | 89 +++ .../utils/mailing/SMTPMailSender.java | 231 +++++++ .../utils/mailing/SMTPSessionProperties.java | 113 ++++ .../utils/mailing/SMTPMailSenderTest.java | 617 ++++++++++++++++++ 11 files changed, 1338 insertions(+), 606 deletions(-) create mode 100644 utils/src/main/java/org/apache/cloudstack/utils/mailing/MailAddress.java create mode 100644 utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailProperties.java create mode 100644 utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailSender.java create mode 100644 utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPSessionProperties.java create mode 100644 utils/src/test/java/org/apache/cloudstack/utils/mailing/SMTPMailSenderTest.java diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index 019420c8a09..924d9c4f7d6 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -16,24 +16,15 @@ //under the License. package org.apache.cloudstack.quota; -import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import javax.mail.Authenticator; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.URLName; -import javax.mail.internet.InternetAddress; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -55,15 +46,14 @@ import com.cloud.user.AccountVO; import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; -import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.TransactionLegacy; -import com.google.common.base.Strings; -import com.sun.mail.smtp.SMTPMessage; -import com.sun.mail.smtp.SMTPSSLTransport; -import com.sun.mail.smtp.SMTPTransport; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang.BooleanUtils; +import java.util.HashSet; +import java.util.Set; +import org.apache.cloudstack.utils.mailing.MailAddress; +import org.apache.cloudstack.utils.mailing.SMTPMailProperties; +import org.apache.cloudstack.utils.mailing.SMTPMailSender; +import org.apache.commons.lang3.BooleanUtils; @Component public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertManager { @@ -84,8 +74,9 @@ public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertMana @Inject private QuotaManager _quotaManager; - private EmailQuotaAlert _emailQuotaAlert; private boolean _lockAccountEnforcement = false; + private String senderAddress; + protected SMTPMailSender mailSender; boolean _smtpDebug = false; @@ -109,19 +100,16 @@ public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertMana mergeConfigs(configs, params); } - final String smtpHost = configs.get(QuotaConfig.QuotaSmtpHost.key()); - int smtpPort = NumbersUtil.parseInt(configs.get(QuotaConfig.QuotaSmtpPort.key()), 25); - String useAuthStr = configs.get(QuotaConfig.QuotaSmtpAuthType.key()); - boolean useAuth = ((useAuthStr != null) && Boolean.parseBoolean(useAuthStr)); + senderAddress = configs.get(QuotaConfig.QuotaSmtpSender.key()); + _lockAccountEnforcement = BooleanUtils.toBoolean(configs.get(QuotaConfig.QuotaEnableEnforcement.key())); String smtpUsername = configs.get(QuotaConfig.QuotaSmtpUser.key()); - String smtpPassword = configs.get(QuotaConfig.QuotaSmtpPassword.key()); - String emailSender = configs.get(QuotaConfig.QuotaSmtpSender.key()); - String smtpEnabledSecurityProtocols = configs.get(QuotaConfig.QuotaSmtpEnabledSecurityProtocols.key()); - String useStartTLSStr = configs.get(QuotaConfig.QuotaSmtpUseStartTLS.key()); - boolean useStartTLS = BooleanUtils.toBoolean(useStartTLSStr); - _lockAccountEnforcement = "true".equalsIgnoreCase(configs.get(QuotaConfig.QuotaEnableEnforcement.key())); - _emailQuotaAlert = new EmailQuotaAlert(smtpHost, smtpPort, useAuth, smtpUsername, smtpPassword, emailSender, smtpEnabledSecurityProtocols, useStartTLS, _smtpDebug); + String namespace = "quota.usage.smtp"; + configs.put(String.format("%s.debug", namespace), String.valueOf(_smtpDebug)); + configs.put(String.format("%s.username", namespace), smtpUsername); + + mailSender = new SMTPMailSender(configs, namespace); + return true; } @@ -234,7 +222,7 @@ public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertMana final String subject = templateEngine.replace(emailTemplate.getTemplateSubject()); final String body = templateEngine.replace(emailTemplate.getTemplateBody()); try { - _emailQuotaAlert.sendQuotaAlert(emailRecipients, subject, body); + sendQuotaAlert(emailRecipients, subject, body); emailToBeSent.sentSuccessfully(_quotaAcc); } catch (Exception e) { s_logger.error(String.format("Unable to send quota alert email (subject=%s; body=%s) to account %s (%s) recipients (%s) due to error (%s)", subject, body, account.getAccountName(), @@ -337,99 +325,22 @@ public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertMana } }; - static class EmailQuotaAlert { - private final Session _smtpSession; - private final String _smtpHost; - private final int _smtpPort; - private final boolean _smtpUseAuth; - private final String _smtpUsername; - private final String _smtpPassword; - private final String _emailSender; - private final boolean smtpUseStartTLS; + protected void sendQuotaAlert(List emails, String subject, String body) { + SMTPMailProperties mailProperties = new SMTPMailProperties(); - public EmailQuotaAlert(String smtpHost, int smtpPort, boolean smtpUseAuth, final String smtpUsername, final String smtpPassword, String emailSender, String smtpEnabledSecurityProtocols, boolean smtpUseStartTLS, boolean smtpDebug) { - _smtpHost = smtpHost; - _smtpPort = smtpPort; - _smtpUseAuth = smtpUseAuth; - _smtpUsername = smtpUsername; - _smtpPassword = smtpPassword; - _emailSender = emailSender; - this.smtpUseStartTLS = smtpUseStartTLS; + mailProperties.setSender(new MailAddress(senderAddress)); + mailProperties.setSubject(subject); + mailProperties.setContent(body); + mailProperties.setContentType("text/html; charset=utf-8"); - if (!Strings.isNullOrEmpty(_smtpHost)) { - Properties smtpProps = new Properties(); - smtpProps.put("mail.smtp.host", smtpHost); - smtpProps.put("mail.smtp.port", smtpPort); - smtpProps.put("mail.smtp.auth", "" + smtpUseAuth); - if (smtpUsername != null) { - smtpProps.put("mail.smtp.user", smtpUsername); - } - - smtpProps.put("mail.smtps.host", smtpHost); - smtpProps.put("mail.smtps.port", smtpPort); - smtpProps.put("mail.smtps.auth", "" + smtpUseAuth); - if (!Strings.isNullOrEmpty(smtpUsername)) { - smtpProps.put("mail.smtps.user", smtpUsername); - } - - if (StringUtils.isNotBlank(smtpEnabledSecurityProtocols)) { - smtpProps.put("mail.smtp.ssl.protocols", smtpEnabledSecurityProtocols); - } - - if (smtpUseAuth) { - smtpProps.put("mail.smtp.starttls.enable", smtpUseStartTLS); - } - - if (!Strings.isNullOrEmpty(smtpUsername) && !Strings.isNullOrEmpty(smtpPassword)) { - _smtpSession = Session.getInstance(smtpProps, new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(smtpUsername, smtpPassword); - } - }); - } else { - _smtpSession = Session.getInstance(smtpProps); - } - _smtpSession.setDebug(smtpDebug); - } else { - _smtpSession = null; - } + Set addresses = new HashSet<>(); + for (String email : emails) { + addresses.add(new MailAddress(email)); } - public void sendQuotaAlert(List emails, String subject, String body) throws MessagingException, UnsupportedEncodingException { - if (_smtpSession == null) { - s_logger.error("Unable to create smtp session."); - return; - } - SMTPMessage msg = new SMTPMessage(_smtpSession); - msg.setSender(new InternetAddress(_emailSender, _emailSender)); - msg.setFrom(new InternetAddress(_emailSender, _emailSender)); + mailProperties.setRecipients(addresses); - for (String email : emails) { - if (email != null && !email.isEmpty()) { - try { - InternetAddress address = new InternetAddress(email, email); - msg.addRecipient(Message.RecipientType.TO, address); - } catch (Exception pokemon) { - s_logger.error("Exception in creating address for:" + email, pokemon); - } - } - } - - msg.setSubject(subject); - msg.setSentDate(new Date()); - msg.setContent(body, "text/html; charset=utf-8"); - msg.saveChanges(); - - SMTPTransport smtpTrans = null; - if (_smtpUseAuth && !this.smtpUseStartTLS) { - smtpTrans = new SMTPSSLTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } else { - smtpTrans = new SMTPTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } - smtpTrans.connect(); - smtpTrans.sendMessage(msg, msg.getAllRecipients()); - smtpTrans.close(); - } + mailSender.sendMail(mailProperties); } + } diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java index 88485b90145..95033ccf234 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java @@ -52,6 +52,8 @@ import com.cloud.user.dao.UserDao; import com.cloud.utils.db.TransactionLegacy; import junit.framework.TestCase; +import org.apache.cloudstack.utils.mailing.SMTPMailProperties; +import org.apache.cloudstack.utils.mailing.SMTPMailSender; @RunWith(MockitoJUnitRunner.class) public class QuotaAlertManagerImplTest extends TestCase { @@ -68,8 +70,6 @@ public class QuotaAlertManagerImplTest extends TestCase { private QuotaEmailTemplatesDao quotaEmailTemplateDao; @Mock private ConfigurationDao configDao; - @Mock - private QuotaAlertManagerImpl.EmailQuotaAlert emailQuotaAlert; @Spy @InjectMocks @@ -77,7 +77,6 @@ public class QuotaAlertManagerImplTest extends TestCase { @Before public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException { - // Dummy transaction stack setup TransactionLegacy.open("QuotaAlertManagerImplTest"); } @@ -135,7 +134,8 @@ public class QuotaAlertManagerImplTest extends TestCase { quotaAccount.setQuotaAlertDate(null); quotaAccount.setQuotaEnforce(0); - QuotaAlertManagerImpl.DeferredQuotaEmail email = new QuotaAlertManagerImpl.DeferredQuotaEmail(account, quotaAccount, new BigDecimal(100), QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW); + QuotaAlertManagerImpl.DeferredQuotaEmail email = new QuotaAlertManagerImpl.DeferredQuotaEmail(account, quotaAccount, new BigDecimal(100), + QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW); QuotaEmailTemplatesVO quotaEmailTemplatesVO = new QuotaEmailTemplatesVO(); quotaEmailTemplatesVO.setTemplateSubject("Low quota"); @@ -156,9 +156,13 @@ public class QuotaAlertManagerImplTest extends TestCase { users.add(user); Mockito.when(userDao.listByAccount(Mockito.anyLong())).thenReturn(users); + quotaAlertManager.mailSender = Mockito.mock(SMTPMailSender.class); + Mockito.when(quotaAlertManager.mailSender.sendMail(Mockito.anyObject())).thenReturn(Boolean.TRUE); + quotaAlertManager.sendQuotaAlert(email); assertTrue(email.getSendDate() != null); - Mockito.verify(emailQuotaAlert, Mockito.times(1)).sendQuotaAlert(Mockito.anyListOf(String.class), Mockito.anyString(), Mockito.anyString()); + Mockito.verify(quotaAlertManager, Mockito.times(1)).sendQuotaAlert(Mockito.anyListOf(String.class), Mockito.anyString(), Mockito.anyString()); + Mockito.verify(quotaAlertManager.mailSender, Mockito.times(1)).sendMail(Mockito.any(SMTPMailProperties.class)); } @Test diff --git a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java index d48f2a362a4..99200f41c2a 100644 --- a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java +++ b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java @@ -23,20 +23,12 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Timer; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.inject.Inject; -import javax.mail.Authenticator; -import javax.mail.Message.RecipientType; import javax.mail.MessagingException; -import javax.mail.PasswordAuthentication; -import javax.mail.SendFailedException; -import javax.mail.Session; -import javax.mail.URLName; -import javax.mail.internet.InternetAddress; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.ConfigDepot; @@ -78,13 +70,15 @@ import com.cloud.resource.ResourceManager; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.StorageManager; -import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.SearchCriteria; -import com.sun.mail.smtp.SMTPMessage; -import com.sun.mail.smtp.SMTPSSLTransport; -import com.sun.mail.smtp.SMTPTransport; +import java.util.HashSet; +import java.util.Set; +import org.apache.cloudstack.utils.mailing.MailAddress; +import org.apache.cloudstack.utils.mailing.SMTPMailProperties; +import org.apache.cloudstack.utils.mailing.SMTPMailSender; +import org.apache.commons.lang3.math.NumberUtils; public class AlertManagerImpl extends ManagerBase implements AlertManager, Configurable { private static final Logger s_logger = Logger.getLogger(AlertManagerImpl.class.getName()); @@ -94,7 +88,6 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi private static final DecimalFormat DfPct = new DecimalFormat("###.##"); private static final DecimalFormat DfWhole = new DecimalFormat("########"); - private EmailAlert _emailAlert; @Inject private AlertDao _alertDao; @Inject @@ -138,6 +131,10 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi private final ExecutorService _executor; + protected SMTPMailSender mailSender; + protected String[] recipients = null; + protected String senderAddress = null; + public AlertManagerImpl() { _executor = Executors.newCachedThreadPool(new NamedThreadFactory("Email-Alerts-Sender")); } @@ -148,27 +145,23 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi // set up the email system for alerts String emailAddressList = configs.get("alert.email.addresses"); - String[] emailAddresses = null; if (emailAddressList != null) { - emailAddresses = emailAddressList.split(","); + recipients = emailAddressList.split(","); } - String smtpHost = configs.get("alert.smtp.host"); - int smtpPort = NumbersUtil.parseInt(configs.get("alert.smtp.port"), 25); - String useAuthStr = configs.get("alert.smtp.useAuth"); - boolean useAuth = ((useAuthStr == null) ? false : Boolean.parseBoolean(useAuthStr)); - String smtpUsername = configs.get("alert.smtp.username"); - String smtpPassword = configs.get("alert.smtp.password"); - String emailSender = configs.get("alert.email.sender"); - String smtpDebugStr = configs.get("alert.smtp.debug"); - int smtpTimeout = NumbersUtil.parseInt(configs.get("alert.smtp.timeout"), 30000); - int smtpConnectionTimeout = NumbersUtil.parseInt(configs.get("alert.smtp.connectiontimeout"), 30000); - boolean smtpDebug = false; - if (smtpDebugStr != null) { - smtpDebug = Boolean.parseBoolean(smtpDebugStr); - } + senderAddress = configs.get("alert.email.sender"); - _emailAlert = new EmailAlert(emailAddresses, smtpHost, smtpPort, smtpConnectionTimeout, smtpTimeout, useAuth, smtpUsername, smtpPassword, emailSender, smtpDebug); + String namespace = "alert.smtp"; + String timeoutConfig = String.format("%s.timeout", namespace); + String connectionTimeoutConfig = String.format("%s.connectiontimeout", namespace); + + int smtpTimeout = NumberUtils.toInt(configs.get(timeoutConfig), 30000); + int smtpConnectionTimeout = NumberUtils.toInt(configs.get(connectionTimeoutConfig), 30000); + + configs.put(timeoutConfig, String.valueOf(smtpTimeout)); + configs.put(connectionTimeoutConfig, String.valueOf(smtpConnectionTimeout)); + + mailSender = new SMTPMailSender(configs, namespace); String publicIPCapacityThreshold = _configDao.getValue(Config.PublicIpCapacityThreshold.key()); String privateIPCapacityThreshold = _configDao.getValue(Config.PrivateIpCapacityThreshold.key()); @@ -231,9 +224,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi @Override public void clearAlert(AlertType alertType, long dataCenterId, long podId) { try { - if (_emailAlert != null) { - _emailAlert.clearAlert(alertType.getType(), dataCenterId, podId); - } + clearAlert(alertType.getType(), dataCenterId, podId); } catch (Exception ex) { s_logger.error("Problem clearing email alert", ex); } @@ -248,8 +239,8 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi // TODO: queue up these messages and send them as one set of issues once a certain number of issues is reached? If that's the case, // shouldn't we have a type/severity as part of the API so that severe errors get sent right away? try { - if (_emailAlert != null) { - _emailAlert.sendAlert(alertType, dataCenterId, podId, null, subject, body); + if (mailSender != null) { + sendAlert(alertType, dataCenterId, podId, null, subject, body); } else { s_logger.warn("AlertType:: " + alertType + " | dataCenterId:: " + dataCenterId + " | podId:: " + podId + " | message:: " + subject + " | body:: " + body); @@ -441,8 +432,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi recalculateCapacity(); - // abort if we can't possibly send an alert... - if (_emailAlert == null) { + if (mailSender == null) { return; } @@ -642,7 +632,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi s_logger.debug(msgSubject); s_logger.debug(msgContent); } - _emailAlert.sendAlert(alertType, dc.getId(), podId, clusterId, msgSubject, msgContent); + sendAlert(alertType, dc.getId(), podId, clusterId, msgSubject, msgContent); } catch (Exception ex) { s_logger.error("Exception in CapacityChecker", ex); } @@ -679,170 +669,73 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi } - class EmailAlert { - private Session _smtpSession; - private InternetAddress[] _recipientList; - private final String _smtpHost; - private int _smtpPort = -1; - private boolean _smtpUseAuth = false; - private final String _smtpUsername; - private final String _smtpPassword; - private final String _emailSender; - private int _smtpTimeout; - private int _smtpConnectionTimeout; - - public EmailAlert(String[] recipientList, String smtpHost, int smtpPort, int smtpConnectionTimeout, int smtpTimeout, boolean smtpUseAuth, - final String smtpUsername, - final String smtpPassword, String emailSender, boolean smtpDebug) { - if (recipientList != null) { - _recipientList = new InternetAddress[recipientList.length]; - for (int i = 0; i < recipientList.length; i++) { - try { - _recipientList[i] = new InternetAddress(recipientList[i], recipientList[i]); - } catch (Exception ex) { - s_logger.error("Exception creating address for: " + recipientList[i], ex); - } - } - } - - _smtpHost = smtpHost; - _smtpPort = smtpPort; - _smtpUseAuth = smtpUseAuth; - _smtpUsername = smtpUsername; - _smtpPassword = smtpPassword; - _emailSender = emailSender; - _smtpTimeout = smtpTimeout; - _smtpConnectionTimeout = smtpConnectionTimeout; - - if (_smtpHost != null) { - Properties smtpProps = new Properties(); - smtpProps.put("mail.smtp.host", smtpHost); - smtpProps.put("mail.smtp.port", smtpPort); - smtpProps.put("mail.smtp.auth", "" + smtpUseAuth); - smtpProps.put("mail.smtp.timeout", _smtpTimeout); - smtpProps.put("mail.smtp.connectiontimeout", _smtpConnectionTimeout); - - if (smtpUsername != null) { - smtpProps.put("mail.smtp.user", smtpUsername); - } - - smtpProps.put("mail.smtps.host", smtpHost); - smtpProps.put("mail.smtps.port", smtpPort); - smtpProps.put("mail.smtps.auth", "" + smtpUseAuth); - smtpProps.put("mail.smtps.timeout", _smtpTimeout); - smtpProps.put("mail.smtps.connectiontimeout", _smtpConnectionTimeout); - - if (smtpUsername != null) { - smtpProps.put("mail.smtps.user", smtpUsername); - } - - if ((smtpUsername != null) && (smtpPassword != null)) { - _smtpSession = Session.getInstance(smtpProps, new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(smtpUsername, smtpPassword); - } - }); - } else { - _smtpSession = Session.getInstance(smtpProps); - } - _smtpSession.setDebug(smtpDebug); - } else { - _smtpSession = null; + public void clearAlert(short alertType, long dataCenterId, Long podId) { + if (alertType != -1) { + AlertVO alert = _alertDao.getLastAlert(alertType, dataCenterId, podId, null); + if (alert != null) { + AlertVO updatedAlert = _alertDao.createForUpdate(); + updatedAlert.setResolved(new Date()); + _alertDao.update(alert.getId(), updatedAlert); } } + } - // TODO: make sure this handles SSL transport (useAuth is true) and regular - public void sendAlert(AlertType alertType, long dataCenterId, Long podId, Long clusterId, String subject, String content) throws MessagingException, - UnsupportedEncodingException { - s_logger.warn("AlertType:: " + alertType + " | dataCenterId:: " + dataCenterId + " | podId:: " + - podId + " | clusterId:: " + clusterId + " | message:: " + subject); - AlertVO alert = null; - if ((alertType != AlertManager.AlertType.ALERT_TYPE_HOST) && - (alertType != AlertManager.AlertType.ALERT_TYPE_USERVM) && - (alertType != AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER) && - (alertType != AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY) && - (alertType != AlertManager.AlertType.ALERT_TYPE_SSVM) && - (alertType != AlertManager.AlertType.ALERT_TYPE_STORAGE_MISC) && - (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE) && - (alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED) && - (alertType != AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED) && - (alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR) && - (alertType != AlertManager.AlertType.ALERT_TYPE_HA_ACTION) && - (alertType != AlertManager.AlertType.ALERT_TYPE_CA_CERT) && - (alertType != AlertManager.AlertType.ALERT_TYPE_VM_SNAPSHOT)) { - alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId); - } + public void sendAlert(AlertType alertType, long dataCenterId, Long podId, Long clusterId, String subject, String content) + throws MessagingException, UnsupportedEncodingException { + s_logger.warn(String.format("alertType=[%s] dataCenterId=[%s] podId=[%s] clusterId=[%s] message=[%s].", alertType, dataCenterId, podId, clusterId, subject)); + AlertVO alert = null; + if ((alertType != AlertManager.AlertType.ALERT_TYPE_HOST) && (alertType != AlertManager.AlertType.ALERT_TYPE_USERVM) + && (alertType != AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER) && (alertType != AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY) + && (alertType != AlertManager.AlertType.ALERT_TYPE_SSVM) && (alertType != AlertManager.AlertType.ALERT_TYPE_STORAGE_MISC) + && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE) && (alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED) + && (alertType != AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED) && (alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR) + && (alertType != AlertManager.AlertType.ALERT_TYPE_HA_ACTION) && (alertType != AlertManager.AlertType.ALERT_TYPE_CA_CERT)) { + alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId); + } - if (alert == null) { - // set up a new alert - AlertVO newAlert = new AlertVO(); - newAlert.setType(alertType.getType()); - newAlert.setSubject(subject); - newAlert.setContent(content); - newAlert.setClusterId(clusterId); - newAlert.setPodId(podId); - newAlert.setDataCenterId(dataCenterId); - newAlert.setSentCount(1); // Initialize sent count to 1 since we are now sending an alert. - newAlert.setLastSent(new Date()); - newAlert.setName(alertType.getName()); - _alertDao.persist(newAlert); - } else { - if (s_logger.isDebugEnabled()) { + if (alert == null) { + AlertVO newAlert = new AlertVO(); + newAlert.setType(alertType.getType()); + newAlert.setSubject(subject); + newAlert.setContent(content); + newAlert.setClusterId(clusterId); + newAlert.setPodId(podId); + newAlert.setDataCenterId(dataCenterId); + newAlert.setSentCount(1); + newAlert.setLastSent(new Date()); + newAlert.setName(alertType.getName()); + _alertDao.persist(newAlert); + } else { + if (s_logger.isDebugEnabled()) { s_logger.debug("Have already sent: " + alert.getSentCount() + " emails for alert type '" + alertType + "' -- skipping send email"); } - return; - } - - if (_smtpSession != null) { - SMTPMessage msg = new SMTPMessage(_smtpSession); - msg.setSender(new InternetAddress(_emailSender, _emailSender)); - msg.setFrom(new InternetAddress(_emailSender, _emailSender)); - for (InternetAddress address : _recipientList) { - msg.addRecipient(RecipientType.TO, address); - } - msg.setSubject(subject); - msg.setSentDate(new Date()); - msg.setContent(content, "text/plain"); - msg.saveChanges(); - - SMTPTransport smtpTrans = null; - if (_smtpUseAuth) { - smtpTrans = new SMTPSSLTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } else { - smtpTrans = new SMTPTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } - sendMessage(smtpTrans, msg); - } + return; } - private void sendMessage(final SMTPTransport smtpTrans, final SMTPMessage msg) { - _executor.execute(new Runnable() { - @Override - public void run() { - try { - smtpTrans.connect(); - smtpTrans.sendMessage(msg, msg.getAllRecipients()); - smtpTrans.close(); - } catch (SendFailedException e) { - s_logger.error(" Failed to send email alert " + e); - } catch (MessagingException e) { - s_logger.error(" Failed to send email alert " + e); - } - } - }); + SMTPMailProperties mailProps = new SMTPMailProperties(); + mailProps.setSender(new MailAddress(senderAddress)); + mailProps.setSubject(subject); + mailProps.setContent(content); + mailProps.setContentType("text/plain"); + + Set addresses = new HashSet<>(); + for (String recipient : recipients) { + addresses.add(new MailAddress(recipient)); } - public void clearAlert(short alertType, long dataCenterId, Long podId) { - if (alertType != -1) { - AlertVO alert = _alertDao.getLastAlert(alertType, dataCenterId, podId, null); - if (alert != null) { - AlertVO updatedAlert = _alertDao.createForUpdate(); - updatedAlert.setResolved(new Date()); - _alertDao.update(alert.getId(), updatedAlert); - } + mailProps.setRecipients(addresses); + + sendMessage(mailProps); + + } + + private void sendMessage(SMTPMailProperties mailProps) { + _executor.execute(new Runnable() { + @Override + public void run() { + mailSender.sendMail(mailProps); } - } + }); } private static String formatPercent(double percentage) { diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 88ad0c2ffc9..7cb4674f7a3 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -17,11 +17,9 @@ package com.cloud.projects; import java.io.UnsupportedEncodingException; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Properties; import java.util.Random; import java.util.TimeZone; import java.util.UUID; @@ -31,13 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.inject.Inject; -import javax.mail.Authenticator; -import javax.mail.Message.RecipientType; import javax.mail.MessagingException; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.URLName; -import javax.mail.internet.InternetAddress; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ProjectRole; @@ -79,8 +71,6 @@ import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; -import com.cloud.utils.DateUtil; -import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DB; @@ -90,14 +80,16 @@ import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; -import com.sun.mail.smtp.SMTPMessage; -import com.sun.mail.smtp.SMTPSSLTransport; -import com.sun.mail.smtp.SMTPTransport; +import java.util.HashSet; +import java.util.Set; +import org.apache.cloudstack.utils.mailing.MailAddress; +import org.apache.cloudstack.utils.mailing.SMTPMailProperties; +import org.apache.cloudstack.utils.mailing.SMTPMailSender; +import org.apache.commons.lang3.BooleanUtils; @Component public class ProjectManagerImpl extends ManagerBase implements ProjectManager { public static final Logger s_logger = Logger.getLogger(ProjectManagerImpl.class); - private EmailInvite _emailInvite; @Inject private DomainDao _domainDao; @@ -137,33 +129,23 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager { protected boolean _allowUserToCreateProject = true; protected ScheduledExecutorService _executor; protected int _projectCleanupExpInvInterval = 60; //Interval defining how often project invitation cleanup thread is running + private String senderAddress; + protected SMTPMailSender mailSender; @Override public boolean configure(final String name, final Map params) throws ConfigurationException { Map configs = _configDao.getConfiguration(params); - _invitationRequired = Boolean.valueOf(configs.get(Config.ProjectInviteRequired.key())); + _invitationRequired = BooleanUtils.toBoolean(configs.get(Config.ProjectInviteRequired.key())); String value = configs.get(Config.ProjectInvitationExpirationTime.key()); _invitationTimeOut = Long.parseLong(value != null ? value : "86400") * 1000; - _allowUserToCreateProject = Boolean.valueOf(configs.get(Config.AllowUserToCreateProject.key())); + _allowUserToCreateProject = BooleanUtils.toBoolean(configs.get(Config.AllowUserToCreateProject.key())); + senderAddress = configs.get("project.email.sender"); - // set up the email system for project invitations + String namespace = "project.smtp"; - String smtpHost = configs.get("project.smtp.host"); - int smtpPort = NumbersUtil.parseInt(configs.get("project.smtp.port"), 25); - String useAuthStr = configs.get("project.smtp.useAuth"); - boolean useAuth = ((useAuthStr == null) ? false : Boolean.parseBoolean(useAuthStr)); - String smtpUsername = configs.get("project.smtp.username"); - String smtpPassword = configs.get("project.smtp.password"); - String emailSender = configs.get("project.email.sender"); - String smtpDebugStr = configs.get("project.smtp.debug"); - boolean smtpDebug = false; - if (smtpDebugStr != null) { - smtpDebug = Boolean.parseBoolean(smtpDebugStr); - } - - _emailInvite = new EmailInvite(smtpHost, smtpPort, useAuth, smtpUsername, smtpPassword, emailSender, smtpDebug); + mailSender = new SMTPMailSender(configs, namespace); _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Project-ExpireInvitations")); return true; @@ -1081,7 +1063,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager { ProjectInvitation projectInvitation = _projectInvitationDao.persist(projectInvitationVO); try { - _emailInvite.sendInvite(token, email, project.getId()); + sendInvite(token, email, project.getId()); } catch (Exception ex) { s_logger.warn("Failed to send project id=" + project + " invitation to the email " + email + "; removing the invitation record from the db", ex); _projectInvitationDao.remove(projectInvitation.getId()); @@ -1091,6 +1073,27 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager { return projectInvitation; } + protected void sendInvite(String token, String email, long projectId) throws MessagingException, UnsupportedEncodingException { + String subject = String.format("You are invited to join the cloud stack project id=[%s].", projectId); + String content = String.format("You've been invited to join the CloudStack project id=[%s]. Please use token [%s] to complete registration", projectId, token); + + SMTPMailProperties mailProperties = new SMTPMailProperties(); + + mailProperties.setSender(new MailAddress(senderAddress)); + mailProperties.setSubject(subject); + mailProperties.setContent(content); + mailProperties.setContentType("text/plain"); + + Set addresses = new HashSet<>(); + + addresses.add(new MailAddress(email)); + + mailProperties.setRecipients(addresses); + + mailSender.sendMail(mailProperties); + + } + private boolean expireInvitation(ProjectInvitationVO invite) { s_logger.debug("Expiring invitation id=" + invite.getId()); invite.setState(ProjectInvitation.State.Expired); @@ -1305,91 +1308,6 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager { } return sb.toString(); } - class EmailInvite { - private Session _smtpSession; - private final String _smtpHost; - private int _smtpPort = -1; - private boolean _smtpUseAuth = false; - private final String _smtpUsername; - private final String _smtpPassword; - private final String _emailSender; - - public EmailInvite(String smtpHost, int smtpPort, boolean smtpUseAuth, final String smtpUsername, final String smtpPassword, String emailSender, boolean smtpDebug) { - _smtpHost = smtpHost; - _smtpPort = smtpPort; - _smtpUseAuth = smtpUseAuth; - _smtpUsername = smtpUsername; - _smtpPassword = smtpPassword; - _emailSender = emailSender; - - if (_smtpHost != null) { - Properties smtpProps = new Properties(); - smtpProps.put("mail.smtp.host", smtpHost); - smtpProps.put("mail.smtp.port", smtpPort); - smtpProps.put("mail.smtp.auth", "" + smtpUseAuth); - if (smtpUsername != null) { - smtpProps.put("mail.smtp.user", smtpUsername); - } - - smtpProps.put("mail.smtps.host", smtpHost); - smtpProps.put("mail.smtps.port", smtpPort); - smtpProps.put("mail.smtps.auth", "" + smtpUseAuth); - if (smtpUsername != null) { - smtpProps.put("mail.smtps.user", smtpUsername); - } - - if ((smtpUsername != null) && (smtpPassword != null)) { - _smtpSession = Session.getInstance(smtpProps, new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(smtpUsername, smtpPassword); - } - }); - } else { - _smtpSession = Session.getInstance(smtpProps); - } - _smtpSession.setDebug(smtpDebug); - } else { - _smtpSession = null; - } - } - - public void sendInvite(String token, String email, long projectId) throws MessagingException, UnsupportedEncodingException { - if (_smtpSession != null) { - InternetAddress address = null; - if (email != null) { - try { - address = new InternetAddress(email, email); - } catch (Exception ex) { - s_logger.error("Exception creating address for: " + email, ex); - } - } - - String content = "You've been invited to join the CloudStack project id=" + projectId + ". Please use token " + token + " to complete registration"; - - SMTPMessage msg = new SMTPMessage(_smtpSession); - msg.setSender(new InternetAddress(_emailSender, _emailSender)); - msg.setFrom(new InternetAddress(_emailSender, _emailSender)); - msg.addRecipient(RecipientType.TO, address); - msg.setSubject("You are invited to join the cloud stack project id=" + projectId); - msg.setSentDate(new Date(DateUtil.currentGMTTime().getTime() >> 10)); - msg.setContent(content, "text/plain"); - msg.saveChanges(); - - SMTPTransport smtpTrans = null; - if (_smtpUseAuth) { - smtpTrans = new SMTPSSLTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } else { - smtpTrans = new SMTPTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } - smtpTrans.connect(); - smtpTrans.sendMessage(msg, msg.getAllRecipients()); - smtpTrans.close(); - } else { - throw new CloudRuntimeException("Unable to send email invitation; smtp ses"); - } - } - } @Override @DB diff --git a/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java index fd8c7c0c142..3ee64e60b21 100644 --- a/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java +++ b/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java @@ -16,19 +16,10 @@ // under the License. package com.cloud.usage; -import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.Map; -import java.util.Properties; import javax.inject.Inject; -import javax.mail.Authenticator; -import javax.mail.Message.RecipientType; -import javax.mail.MessagingException; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.URLName; -import javax.mail.internet.InternetAddress; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -38,18 +29,21 @@ import org.springframework.stereotype.Component; import com.cloud.alert.AlertManager; import com.cloud.alert.AlertVO; import com.cloud.alert.dao.AlertDao; -import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; -import com.sun.mail.smtp.SMTPMessage; -import com.sun.mail.smtp.SMTPSSLTransport; -import com.sun.mail.smtp.SMTPTransport; +import java.util.HashSet; +import java.util.Set; +import org.apache.cloudstack.utils.mailing.MailAddress; +import org.apache.cloudstack.utils.mailing.SMTPMailProperties; +import org.apache.cloudstack.utils.mailing.SMTPMailSender; @Component public class UsageAlertManagerImpl extends ManagerBase implements AlertManager { private static final Logger s_logger = Logger.getLogger(UsageAlertManagerImpl.class.getName()); - private static final Logger s_alertsLogger = Logger.getLogger("org.apache.cloudstack.alerts"); - private EmailAlert _emailAlert; + private String senderAddress; + protected SMTPMailSender mailSender; + protected String[] recipients; + @Inject private AlertDao _alertDao; @Inject @@ -59,185 +53,75 @@ public class UsageAlertManagerImpl extends ManagerBase implements AlertManager { public boolean configure(String name, Map params) throws ConfigurationException { Map configs = _configDao.getConfiguration("management-server", params); - // set up the email system for alerts + senderAddress = configs.get("alert.email.sender"); String emailAddressList = configs.get("alert.email.addresses"); - String[] emailAddresses = null; + recipients = null; if (emailAddressList != null) { - emailAddresses = emailAddressList.split(","); + recipients = emailAddressList.split(","); } - String smtpHost = configs.get("alert.smtp.host"); - int smtpPort = NumbersUtil.parseInt(configs.get("alert.smtp.port"), 25); - String useAuthStr = configs.get("alert.smtp.useAuth"); - boolean useAuth = ((useAuthStr == null) ? false : Boolean.parseBoolean(useAuthStr)); - String smtpUsername = configs.get("alert.smtp.username"); - String smtpPassword = configs.get("alert.smtp.password"); - String emailSender = configs.get("alert.email.sender"); - String smtpDebugStr = configs.get("alert.smtp.debug"); - boolean smtpDebug = false; - if (smtpDebugStr != null) { - smtpDebug = Boolean.parseBoolean(smtpDebugStr); - } + String namespace = "alert.smtp"; + + mailSender = new SMTPMailSender(configs, namespace); - _emailAlert = new EmailAlert(emailAddresses, smtpHost, smtpPort, useAuth, smtpUsername, smtpPassword, emailSender, smtpDebug); return true; } @Override public void clearAlert(AlertType alertType, long dataCenterId, long podId) { try { - if (_emailAlert != null) { - _emailAlert.clearAlert(alertType.getType(), dataCenterId, podId); - } + clearAlert(alertType.getType(), dataCenterId, podId); } catch (Exception ex) { s_logger.error("Problem clearing email alert", ex); } } @Override - public void sendAlert(AlertType alertType, long dataCenterId, Long podId, String subject, String body) { - // TODO: queue up these messages and send them as one set of issues once a certain number of issues is reached? If that's the case, - // shouldn't we have a type/severity as part of the API so that severe errors get sent right away? - try { - if (_emailAlert != null) { - _emailAlert.sendAlert(alertType, dataCenterId, podId, subject, body); - } else { - s_alertsLogger.warn(" alertType:: " + alertType + " // dataCenterId:: " + dataCenterId + " // podId:: " + podId + " // clusterId:: " + null + - " // message:: " + subject + " // body:: " + body); - } - } catch (Exception ex) { - s_logger.error("Problem sending email alert", ex); + public void sendAlert(AlertType alertType, long dataCenterId, Long podId, String subject, String content) { + AlertVO alert = null; + if ((alertType != AlertManager.AlertType.ALERT_TYPE_HOST) && (alertType != AlertManager.AlertType.ALERT_TYPE_USERVM) + && (alertType != AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER) && (alertType != AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY) + && (alertType != AlertManager.AlertType.ALERT_TYPE_SSVM) && (alertType != AlertManager.AlertType.ALERT_TYPE_STORAGE_MISC) + && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE)) { + alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId); } + if (alert == null) { + AlertVO newAlert = new AlertVO(); + newAlert.setType(alertType.getType()); + newAlert.setSubject(subject); + newAlert.setPodId(podId); + newAlert.setDataCenterId(dataCenterId); + newAlert.setSentCount(1); + newAlert.setLastSent(new Date()); + newAlert.setName(alertType.getName()); + _alertDao.persist(newAlert); + } else { + s_logger.debug(String.format("Have already sent: [%s] emails for alert type [%s] -- skipping send email.", alert.getSentCount(), alertType)); + return; + } + + SMTPMailProperties mailProps = new SMTPMailProperties(); + mailProps.setSender(new MailAddress(senderAddress)); + mailProps.setSubject(subject); + mailProps.setContent(content); + mailProps.setContentType("text/plain"); + + Set addresses = new HashSet<>(); + for (String recipient : recipients) { + addresses.add(new MailAddress(recipient)); + } + + mailProps.setRecipients(addresses); + mailSender.sendMail(mailProps); } - class EmailAlert { - private Session _smtpSession; - private InternetAddress[] _recipientList; - private final String _smtpHost; - private int _smtpPort = -1; - private boolean _smtpUseAuth = false; - private final String _smtpUsername; - private final String _smtpPassword; - private final String _emailSender; - - public EmailAlert(String[] recipientList, String smtpHost, int smtpPort, boolean smtpUseAuth, final String smtpUsername, final String smtpPassword, - String emailSender, boolean smtpDebug) { - if (recipientList != null) { - _recipientList = new InternetAddress[recipientList.length]; - for (int i = 0; i < recipientList.length; i++) { - try { - _recipientList[i] = new InternetAddress(recipientList[i], recipientList[i]); - } catch (Exception ex) { - s_logger.error("Exception creating address for: " + recipientList[i], ex); - } - } - } - - _smtpHost = smtpHost; - _smtpPort = smtpPort; - _smtpUseAuth = smtpUseAuth; - _smtpUsername = smtpUsername; - _smtpPassword = smtpPassword; - _emailSender = emailSender; - - if (_smtpHost != null) { - Properties smtpProps = new Properties(); - smtpProps.put("mail.smtp.host", smtpHost); - smtpProps.put("mail.smtp.port", smtpPort); - smtpProps.put("mail.smtp.auth", "" + smtpUseAuth); - if (smtpUsername != null) { - smtpProps.put("mail.smtp.user", smtpUsername); - } - - smtpProps.put("mail.smtps.host", smtpHost); - smtpProps.put("mail.smtps.port", smtpPort); - smtpProps.put("mail.smtps.auth", "" + smtpUseAuth); - if (smtpUsername != null) { - smtpProps.put("mail.smtps.user", smtpUsername); - } - - if ((smtpUsername != null) && (smtpPassword != null)) { - _smtpSession = Session.getInstance(smtpProps, new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(smtpUsername, smtpPassword); - } - }); - } else { - _smtpSession = Session.getInstance(smtpProps); - } - _smtpSession.setDebug(smtpDebug); - } else { - _smtpSession = null; - } - } - - // TODO: make sure this handles SSL transport (useAuth is true) and regular - protected void sendAlert(AlertType alertType, long dataCenterId, Long podId, String subject, String content) throws MessagingException, - UnsupportedEncodingException { - s_alertsLogger.warn(" alertType:: " + alertType + " // dataCenterId:: " + dataCenterId + " // podId:: " + - podId + " // clusterId:: " + null + " // message:: " + subject); - AlertVO alert = null; - if ((alertType != AlertManager.AlertType.ALERT_TYPE_HOST) && - (alertType != AlertManager.AlertType.ALERT_TYPE_USERVM) && - (alertType != AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER) && - (alertType != AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY) && - (alertType != AlertManager.AlertType.ALERT_TYPE_SSVM) && - (alertType != AlertManager.AlertType.ALERT_TYPE_STORAGE_MISC) && - (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE)) { - alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId); - } - - if (alert == null) { - // set up a new alert - AlertVO newAlert = new AlertVO(); - newAlert.setType(alertType.getType()); - newAlert.setSubject(subject); - newAlert.setPodId(podId); - newAlert.setDataCenterId(dataCenterId); - newAlert.setSentCount(1); // initialize sent count to 1 since we are now sending an alert - newAlert.setLastSent(new Date()); - newAlert.setName(alertType.getName()); - _alertDao.persist(newAlert); - } else { - if (s_logger.isDebugEnabled()) { - s_logger.debug("Have already sent: " + alert.getSentCount() + " emails for alert type '" + alertType + "' -- skipping send email"); - } - return; - } - - if (_smtpSession != null) { - SMTPMessage msg = new SMTPMessage(_smtpSession); - msg.setSender(new InternetAddress(_emailSender, _emailSender)); - msg.setFrom(new InternetAddress(_emailSender, _emailSender)); - for (InternetAddress address : _recipientList) { - msg.addRecipient(RecipientType.TO, address); - } - msg.setSubject(subject); - msg.setSentDate(new Date()); - msg.setContent(content, "text/plain"); - msg.saveChanges(); - - SMTPTransport smtpTrans = null; - if (_smtpUseAuth) { - smtpTrans = new SMTPSSLTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } else { - smtpTrans = new SMTPTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); - } - smtpTrans.connect(); - smtpTrans.sendMessage(msg, msg.getAllRecipients()); - smtpTrans.close(); - } - } - - public void clearAlert(short alertType, long dataCenterId, Long podId) { - if (alertType != -1) { - AlertVO alert = _alertDao.getLastAlert(alertType, dataCenterId, podId); - if (alert != null) { - AlertVO updatedAlert = _alertDao.createForUpdate(); - updatedAlert.setResolved(new Date()); - _alertDao.update(alert.getId(), updatedAlert); - } + public void clearAlert(short alertType, long dataCenterId, Long podId) { + if (alertType != -1) { + AlertVO alert = _alertDao.getLastAlert(alertType, dataCenterId, podId); + if (alert != null) { + AlertVO updatedAlert = _alertDao.createForUpdate(); + updatedAlert.setResolved(new Date()); + _alertDao.update(alert.getId(), updatedAlert); } } } @@ -254,7 +138,7 @@ public class UsageAlertManagerImpl extends ManagerBase implements AlertManager { sendAlert(alertType, dataCenterId, podId, msg, msg); return true; } catch (Exception ex) { - s_logger.warn("Failed to generate an alert of type=" + alertType + "; msg=" + msg); + s_logger.warn("Failed to generate an alert of type=" + alertType + "; msg=" + msg, ex); return false; } } diff --git a/utils/pom.xml b/utils/pom.xml index 8f42567762d..347ba5b8a69 100755 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -184,6 +184,15 @@ commons-compress ${cs.commons-compress.version} + + javax.mail + mail + + + org.apache.commons + commons-email + 1.5 + org.apache.commons commons-lang3 diff --git a/utils/src/main/java/org/apache/cloudstack/utils/mailing/MailAddress.java b/utils/src/main/java/org/apache/cloudstack/utils/mailing/MailAddress.java new file mode 100644 index 00000000000..aab51fef54f --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/mailing/MailAddress.java @@ -0,0 +1,63 @@ +/* + * + * 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.apache.cloudstack.utils.mailing; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class MailAddress { + + private final String address; + private final String personal; + + public MailAddress(String address) { + this.address = address; + this.personal = address; + } + + public MailAddress(String address, String personal) { + this.address = address; + this.personal = personal; + } + + public String getAddress() { + return address; + } + + public String getPersonal() { + return personal; + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + @Override + public boolean equals(Object obj) { + String[] excludedFields = {"personal"}; + return EqualsBuilder.reflectionEquals(this, obj, excludedFields); + } + + @Override + public String toString() { + ToStringBuilder tsb = new ToStringBuilder(this, ToStringStyle.JSON_STYLE); + tsb.append("mailAddress", address); + return tsb.build(); + } + +} diff --git a/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailProperties.java b/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailProperties.java new file mode 100644 index 00000000000..ef5d895acf4 --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailProperties.java @@ -0,0 +1,89 @@ +/* + * + * 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.apache.cloudstack.utils.mailing; + +import java.util.Date; +import java.util.Set; + +public class SMTPMailProperties { + + private MailAddress sender; + private MailAddress from; + private Set recipients; + private String subject; + private Date sentDate; + private Object content; + private String contentType; + + public SMTPMailProperties() { + } + + public MailAddress getSender() { + return sender; + } + + public void setSender(MailAddress sender) { + this.sender = sender; + } + + public MailAddress getFrom() { + return from; + } + + public void setFrom(MailAddress from) { + this.from = from; + } + + public Set getRecipients() { + return recipients; + } + + public void setRecipients(Set recipients) { + this.recipients = recipients; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public Date getSentDate() { + return sentDate; + } + + public void setSentDate(Date sentDate) { + this.sentDate = sentDate; + } + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + +} diff --git a/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailSender.java b/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailSender.java new file mode 100644 index 00000000000..42497eb2922 --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPMailSender.java @@ -0,0 +1,231 @@ +/* + * + * 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.apache.cloudstack.utils.mailing; + +import com.sun.mail.smtp.SMTPMessage; +import com.sun.mail.smtp.SMTPSSLTransport; +import com.sun.mail.smtp.SMTPTransport; + +import java.io.UnsupportedEncodingException; + +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.URLName; +import javax.mail.internet.InternetAddress; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.mail.EmailConstants; +import org.apache.log4j.Logger; + +public class SMTPMailSender { + + private Logger logger = Logger.getLogger(SMTPMailSender.class); + + protected Session session = null; + protected SMTPSessionProperties sessionProps; + + protected static final String CONFIG_HOST = "host"; + protected static final String CONFIG_PORT = "port"; + protected static final String CONFIG_USE_AUTH = "useAuth"; + protected static final String CONFIG_USERNAME = "username"; + protected static final String CONFIG_PASSWORD = "password"; + protected static final String CONFIG_DEBUG_MODE = "debug"; + protected static final String CONFIG_USE_STARTTLS = "useStartTLS"; + protected static final String CONFIG_ENABLED_SECURITY_PROTOCOLS = "enabledSecurityProtocols"; + protected static final String CONFIG_TIMEOUT = "timeout"; + protected static final String CONFIG_CONNECTION_TIMEOUT = "connectiontimeout"; + + protected Map configs; + protected String namespace; + + public SMTPMailSender(Map configs, String namespace) { + + if (namespace == null) { + logger.error("Unable to configure SMTP session due to null namespace."); + return; + } + + this.configs = configs; + this.namespace = namespace; + this.sessionProps = configureSessionProperties(); + + if (StringUtils.isNotBlank(sessionProps.getHost())) { + Properties props = new Properties(); + + props.put(EmailConstants.MAIL_HOST, sessionProps.getHost()); + props.put(EmailConstants.MAIL_PORT, sessionProps.getPort()); + props.put(EmailConstants.MAIL_SMTP_AUTH, sessionProps.getUseAuth()); + + String username = sessionProps.getUsername(); + + if (username != null) { + props.put(EmailConstants.MAIL_SMTP_USER, username); + } + + String protocols = sessionProps.getEnabledSecurityProtocols(); + if (StringUtils.isNotBlank(protocols)) { + props.put("mail.smtp.ssl.protocols", protocols); + } + + if (sessionProps.getUseAuth()) { + props.put(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE, sessionProps.getUseStartTLS()); + } + + if (sessionProps.getTimeout() != null) { + props.put(EmailConstants.MAIL_SMTP_TIMEOUT, sessionProps.getTimeout()); + } + + if (sessionProps.getConnectionTimeout() != null) { + props.put(EmailConstants.MAIL_SMTP_CONNECTIONTIMEOUT, sessionProps.getConnectionTimeout()); + } + + String password = sessionProps.getPassword(); + if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) { + session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } else { + session = Session.getInstance(props); + } + + session.setDebug(sessionProps.getDebugMode()); + + } else { + logger.debug("Unable to instantiate SMTP mail session due to empty or null host."); + } + } + + protected String getConfig(String config) { + return this.configs.get(String.format("%s.%s", namespace, config)); + } + + protected SMTPSessionProperties configureSessionProperties() { + String host = getConfig(CONFIG_HOST); + String port = getConfig(CONFIG_PORT); + String useAuth = getConfig(CONFIG_USE_AUTH); + String username = getConfig(CONFIG_USERNAME); + String password = getConfig(CONFIG_PASSWORD); + String debugMode = getConfig(CONFIG_DEBUG_MODE); + String useStartTLS = getConfig(CONFIG_USE_STARTTLS); + String enabledSecurityProtocols = getConfig(CONFIG_ENABLED_SECURITY_PROTOCOLS); + String timeout = getConfig(CONFIG_TIMEOUT); + String connectionTimeout = getConfig(CONFIG_CONNECTION_TIMEOUT); + + SMTPSessionProperties sessionProps = new SMTPSessionProperties(); + + sessionProps.setHost(host); + sessionProps.setPort(NumberUtils.toInt(port, 25)); + sessionProps.setUseAuth(BooleanUtils.toBoolean(useAuth)); + sessionProps.setUsername(username); + sessionProps.setPassword(password); + sessionProps.setUseStartTLS(BooleanUtils.toBoolean(useStartTLS)); + sessionProps.setEnabledSecurityProtocols(enabledSecurityProtocols); + sessionProps.setDebugMode(BooleanUtils.toBoolean(debugMode)); + + sessionProps.setTimeout(timeout == null ? null : NumberUtils.toInt(timeout)); + sessionProps.setConnectionTimeout(connectionTimeout == null ? null : NumberUtils.toInt(connectionTimeout)); + + return sessionProps; + } + + public boolean sendMail(SMTPMailProperties mailProps) { + if (session == null) { + logger.error("Unable to send mail due to null session."); + return false; + } + + try { + SMTPMessage message = createMessage(mailProps); + + SMTPTransport smtpTrans = createSmtpTransport(); + + smtpTrans.connect(); + smtpTrans.sendMessage(message, message.getAllRecipients()); + smtpTrans.close(); + + return true; + } catch (MessagingException | UnsupportedEncodingException ex) { + logger.error(String.format("Unable to send mail [%s] to the recipcients [%s].", mailProps.getSubject(), mailProps.getRecipients().toString()), ex); + } + + return false; + + } + + protected SMTPMessage createMessage(SMTPMailProperties mailProps) throws MessagingException, UnsupportedEncodingException { + SMTPMessage message = new SMTPMessage(session); + + MailAddress sender = mailProps.getSender(); + MailAddress from = mailProps.getFrom(); + + if (from == null) { + from = sender; + } + + message.setSender(new InternetAddress(sender.getAddress(), sender.getPersonal())); + message.setFrom(new InternetAddress(from.getAddress(), from.getPersonal())); + + setMailRecipients(message, mailProps.getRecipients(), mailProps.getSubject()); + + message.setSubject(mailProps.getSubject()); + message.setSentDate(mailProps.getSentDate() != null ? mailProps.getSentDate() : new Date()); + message.setContent(mailProps.getContent(), mailProps.getContentType()); + message.saveChanges(); + return message; + } + + protected SMTPTransport createSmtpTransport() { + URLName urlName = new URLName("smtp", sessionProps.getHost(), sessionProps.getPort(), null, sessionProps.getUsername(), sessionProps.getPassword()); + + if (sessionProps.getUseAuth() && !sessionProps.getUseStartTLS()) { + return new SMTPSSLTransport(session, urlName); + } + + return new SMTPTransport(session, urlName); + } + + protected boolean setMailRecipients(SMTPMessage message, Set recipients, String subject) throws UnsupportedEncodingException, MessagingException { + for (MailAddress recipient : recipients) { + if (StringUtils.isNotBlank(recipient.getAddress())) { + try { + InternetAddress address = new InternetAddress(recipient.getAddress(), recipient.getPersonal()); + message.addRecipient(Message.RecipientType.TO, address); + } catch (MessagingException ex) { + logger.error(String.format("Unable to create InternetAddres for address [%s].", recipient), ex); + } + } + } + + if (recipients.isEmpty() || message.getAllRecipients().length == 0) { + logger.error("Unable to send mail due to empty list of recipients."); + logger.debug(String.format("Unable to send message [%s].", subject)); + return false; + } + + return true; + } + +} diff --git a/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPSessionProperties.java b/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPSessionProperties.java new file mode 100644 index 00000000000..cc7023df2b1 --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/mailing/SMTPSessionProperties.java @@ -0,0 +1,113 @@ +/* + * + * 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.apache.cloudstack.utils.mailing; + +public class SMTPSessionProperties { + + private String host; + private int port; + private Boolean useAuth; + private String username; + private String password; + private Boolean useStartTLS; + private String enabledSecurityProtocols; + private Boolean debugMode; + private Integer timeout; + private Integer connectionTimeout; + + public SMTPSessionProperties() { + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public Boolean getUseAuth() { + return useAuth; + } + + public void setUseAuth(Boolean useAuth) { + this.useAuth = useAuth; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Boolean getUseStartTLS() { + return useStartTLS; + } + + public void setUseStartTLS(Boolean useStartTLS) { + this.useStartTLS = useStartTLS; + } + + public String getEnabledSecurityProtocols() { + return enabledSecurityProtocols; + } + + public void setEnabledSecurityProtocols(String enabledSecurityProtocols) { + this.enabledSecurityProtocols = enabledSecurityProtocols; + } + + public Boolean getDebugMode() { + return debugMode; + } + + public void setDebugMode(Boolean debugMode) { + this.debugMode = debugMode; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public Integer getConnectionTimeout() { + return connectionTimeout; + } + + public void setConnectionTimeout(Integer connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + +} diff --git a/utils/src/test/java/org/apache/cloudstack/utils/mailing/SMTPMailSenderTest.java b/utils/src/test/java/org/apache/cloudstack/utils/mailing/SMTPMailSenderTest.java new file mode 100644 index 00000000000..bb2290b3793 --- /dev/null +++ b/utils/src/test/java/org/apache/cloudstack/utils/mailing/SMTPMailSenderTest.java @@ -0,0 +1,617 @@ +/* + * + * 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.apache.cloudstack.utils.mailing; + +import com.sun.mail.smtp.SMTPMessage; +import com.sun.mail.smtp.SMTPTransport; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import javax.mail.Address; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import junit.framework.TestCase; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.mail.EmailConstants; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SMTPMailSenderTest extends TestCase { + + private SMTPMailSender smtpMailSender; + private Map configsMock = Mockito.mock(Map.class); + private String namespace = "test"; + private String enabledProtocols = "mail.smtp.ssl.protocols"; + + @Before + public void before() { + smtpMailSender = new SMTPMailSender(configsMock, namespace); + } + + private String getConfigName(String config) { + return String.format("%s.%s", namespace, config); + } + + @Test + public void validateSetSessionPropertiesUseStartTLSTrue() { + Map configs = new HashMap<>(); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_STARTTLS), "true"); + + smtpMailSender.configs = configs; + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertTrue(props.getUseStartTLS()); + } + + @Test + public void validateSetSessionPropertiesUseStartTLSFalse() { + Map configs = new HashMap<>(); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_STARTTLS), "false"); + + smtpMailSender.configs = configs; + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertFalse(props.getUseStartTLS()); + } + + @Test + public void validateSetSessionPropertiesUseStartTLSUndefinedUseDefaultFalse() { + SMTPMailSender smtpMailSender = new SMTPMailSender(configsMock, namespace); + + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertFalse(props.getUseStartTLS()); + } + + @Test + public void validateSetSessionPropertiesUseAuthTrue() { + Map configs = new HashMap<>(); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_AUTH), "true"); + + smtpMailSender.configs = configs; + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertTrue(props.getUseAuth()); + } + + @Test + public void validateSetSessionPropertiesUseAuthFalse() { + Map configs = new HashMap<>(); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_AUTH), "false"); + + smtpMailSender.configs = configs; + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertFalse(props.getUseAuth()); + } + + @Test + public void validateSetSessionPropertiesUseAuthUndefinedUseDefaultFalse() { + SMTPMailSender smtpMailSender = new SMTPMailSender(configsMock, namespace); + + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertFalse(props.getUseAuth()); + } + + @Test + public void validateSetSessionPropertiesDebugModeTrue() { + Map configs = new HashMap<>(); + configs.put(getConfigName(SMTPMailSender.CONFIG_DEBUG_MODE), "true"); + + smtpMailSender.configs = configs; + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertTrue(props.getDebugMode()); + } + + @Test + public void validateSetSessionPropertiesDebugModeFalse() { + Map configs = new HashMap<>(); + configs.put(getConfigName(SMTPMailSender.CONFIG_DEBUG_MODE), "false"); + + smtpMailSender.configs = configs; + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertFalse(props.getDebugMode()); + } + + @Test + public void validateSetSessionPropertiesDebugModeUndefinedUseDefaultFalse() { + SMTPMailSender smtpMailSender = new SMTPMailSender(configsMock, namespace); + + SMTPSessionProperties props = smtpMailSender.configureSessionProperties(); + + assertFalse(props.getDebugMode()); + } + + @Test + public void validateSMTPMailSenderConstructorHostDefinedAsNullNoSessionCreated() { + SMTPMailSender smtpMailSender = new SMTPMailSender(new HashMap<>(), namespace); + + assertNull(smtpMailSender.sessionProps.getHost()); + assertNull(smtpMailSender.session); + } + + @Test + public void validateSMTPMailSenderConstructorHostDefinedAsEmptyNoSessionCreated() { + Map configs = new HashMap<>(); + + String host = ""; + + configs.put(getConfigName(SMTPMailSender.CONFIG_HOST), host); + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(host, smtpMailSender.sessionProps.getHost()); + assertNull(smtpMailSender.session); + } + + @Test + public void validateSMTPMailSenderConstructorHostDefinedAsBlankNoSessionCreated() { + Map configs = new HashMap<>(); + + String host = " "; + + configs.put(getConfigName(SMTPMailSender.CONFIG_HOST), host); + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(host, smtpMailSender.sessionProps.getHost()); + assertNull(smtpMailSender.session); + } + + @Test + public void validateSMTPMailSenderConstructorHostDefinedSessionCreated() { + Map configs = new HashMap<>(); + + String host = "smtp.acme.org"; + + configs.put(getConfigName(SMTPMailSender.CONFIG_HOST), host); + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(host, smtpMailSender.sessionProps.getHost()); + assertNotNull(smtpMailSender.session); + } + + private Map getConfigsWithHost() { + Map configs = new HashMap<>(); + + String host = "smtp.acme.org"; + + configs.put(getConfigName(SMTPMailSender.CONFIG_HOST), host); + + return configs; + } + + @Test + public void validateSMTPMailSenderConstructorPortUndefinedUseDefault25() { + Map configs = getConfigsWithHost(); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(25, smtpMailSender.sessionProps.getPort()); + } + + @Test + public void validateSMTPMailSenderConstructorPortDefined() { + Map configs = getConfigsWithHost(); + + int port = 465; + configs.put(getConfigName(SMTPMailSender.CONFIG_PORT), String.valueOf(port)); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(port, smtpMailSender.sessionProps.getPort()); + } + + @Test + public void validateSMTPMailSenderConstructorWithTimeoutUndefined() { + Map configs = getConfigsWithHost(); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNull(smtpMailSender.sessionProps.getTimeout()); + } + + @Test + public void validateSMTPMailSenderConstructorWithTimeoutDefined() { + Map configs = getConfigsWithHost(); + + Integer timeout = 12345; + configs.put(getConfigName(SMTPMailSender.CONFIG_TIMEOUT), String.valueOf(timeout)); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(timeout, smtpMailSender.sessionProps.getTimeout()); + } + + @Test + public void validateSMTPMailSenderConstructorWithConnectionTimeoutUndefined() { + Map configs = getConfigsWithHost(); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNull(smtpMailSender.sessionProps.getConnectionTimeout()); + } + + @Test + public void validateSMTPMailSenderConstructorWithConnectionTimeoutDefined() { + Map configs = getConfigsWithHost(); + + Integer connectionTimeout = 12345; + configs.put(getConfigName(SMTPMailSender.CONFIG_CONNECTION_TIMEOUT), String.valueOf(connectionTimeout)); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(connectionTimeout, smtpMailSender.sessionProps.getConnectionTimeout()); + } + + @Test + public void validateSMTPMailSenderConstructorWithUsernameUndefined() { + Map configs = getConfigsWithHost(); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNull(smtpMailSender.sessionProps.getUsername()); + } + + @Test + public void validateSMTPMailSenderConstructorWithUsernameDefinedAsEmpty() { + Map configs = getConfigsWithHost(); + + String username = ""; + configs.put(getConfigName(SMTPMailSender.CONFIG_USERNAME), username); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNotNull(smtpMailSender.sessionProps.getUsername()); + assertEquals(username, smtpMailSender.session.getProperties().get(EmailConstants.MAIL_SMTP_USER)); + } + + @Test + public void validateSMTPMailSenderConstructorWithUsernameDefinedAsBlank() { + Map configs = getConfigsWithHost(); + + String username = " "; + configs.put(getConfigName(SMTPMailSender.CONFIG_USERNAME), username); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNotNull(smtpMailSender.sessionProps.getUsername()); + assertEquals(username, smtpMailSender.session.getProperties().get(EmailConstants.MAIL_SMTP_USER)); + } + + @Test + public void validateSMTPMailSenderConstructorWithValidUsername() { + Map configs = getConfigsWithHost(); + + String username = "test@test.com"; + configs.put(getConfigName(SMTPMailSender.CONFIG_USERNAME), username); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNotNull(smtpMailSender.sessionProps.getUsername()); + assertEquals(username, smtpMailSender.session.getProperties().get(EmailConstants.MAIL_SMTP_USER)); + } + + @Test + public void validateSMTPMailSenderConstructorWithProtocolsUndefined() { + Map configs = getConfigsWithHost(); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNull(smtpMailSender.sessionProps.getEnabledSecurityProtocols()); + assertNull(smtpMailSender.session.getProperties().get(enabledProtocols)); + } + + @Test + public void validateSMTPMailSenderConstructorWithProtocolsDefinedAsEmpty() { + Map configs = getConfigsWithHost(); + + String protocols = ""; + configs.put(getConfigName(SMTPMailSender.CONFIG_ENABLED_SECURITY_PROTOCOLS), protocols); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(protocols, smtpMailSender.sessionProps.getEnabledSecurityProtocols()); + assertNull(smtpMailSender.session.getProperties().get(enabledProtocols)); + } + + @Test + public void validateSMTPMailSenderConstructorWithProtocolsDefinedAsBlank() { + Map configs = getConfigsWithHost(); + + String protocols = " "; + configs.put(getConfigName(SMTPMailSender.CONFIG_ENABLED_SECURITY_PROTOCOLS), protocols); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(protocols, smtpMailSender.sessionProps.getEnabledSecurityProtocols()); + assertNull(smtpMailSender.session.getProperties().get(enabledProtocols)); + } + + @Test + public void validateSMTPMailSenderConstructorWithValidProtocol() { + Map configs = getConfigsWithHost(); + + String protocols = "TLSv1"; + configs.put(getConfigName(SMTPMailSender.CONFIG_ENABLED_SECURITY_PROTOCOLS), protocols); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(protocols, smtpMailSender.sessionProps.getEnabledSecurityProtocols()); + assertEquals(protocols, smtpMailSender.session.getProperties().get(enabledProtocols)); + } + + @Test + public void validateSMTPMailSenderConstructorWithMultipleValidsProtocols() { + Map configs = getConfigsWithHost(); + + String protocols = "TLSv1 TLSv1.2"; + configs.put(getConfigName(SMTPMailSender.CONFIG_ENABLED_SECURITY_PROTOCOLS), protocols); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertEquals(protocols, smtpMailSender.sessionProps.getEnabledSecurityProtocols()); + assertEquals(protocols, smtpMailSender.session.getProperties().get(enabledProtocols)); + } + + @Test + public void validateSMTPMailSenderConstructorUseAuthFalseUseStartTLSFalseStartTLSEnabledMustBeNull() { + Map configs = getConfigsWithHost(); + + Boolean useAuth = false; + Boolean useStartTLS = false; + + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_AUTH), String.valueOf(useAuth)); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_STARTTLS), String.valueOf(useStartTLS)); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNull(smtpMailSender.session.getProperties().get(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE)); + } + + @Test + public void validateSMTPMailSenderConstructorUseAuthFalseUseStartTLSTrueStartTLSEnabledMustBeNull() { + Map configs = getConfigsWithHost(); + + Boolean useAuth = false; + Boolean useStartTLS = true; + + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_AUTH), String.valueOf(useAuth)); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_STARTTLS), String.valueOf(useStartTLS)); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertNull(smtpMailSender.session.getProperties().get(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE)); + } + + @Test + public void validateSMTPMailSenderConstructorUseAuthTrueUseStartTLSFalseStartTLSEnabledMustBeFalse() { + Map configs = getConfigsWithHost(); + + Boolean useAuth = true; + Boolean useStartTLS = false; + + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_AUTH), String.valueOf(useAuth)); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_STARTTLS), String.valueOf(useStartTLS)); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertFalse((boolean)smtpMailSender.session.getProperties().get(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE)); + } + + @Test + public void validateSMTPMailSenderConstructorUseAuthTrueUseStartTLSTrueStartTLSEnabledMustBeFalse() { + Map configs = getConfigsWithHost(); + + Boolean useAuth = true; + Boolean useStartTLS = true; + + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_AUTH), String.valueOf(useAuth)); + configs.put(getConfigName(SMTPMailSender.CONFIG_USE_STARTTLS), String.valueOf(useStartTLS)); + + SMTPMailSender smtpMailSender = new SMTPMailSender(configs, namespace); + + assertTrue((boolean)smtpMailSender.session.getProperties().get(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE)); + } + + @Test + public void validateSMTPMailSenderCreateMessageFromDefinedAsNull() throws MessagingException, UnsupportedEncodingException { + smtpMailSender = smtpMailSender = Mockito.spy(smtpMailSender); + + SMTPMailProperties mailProps = new SMTPMailProperties(); + + mailProps.setSender(new MailAddress("test@test.com")); + mailProps.setContent("A simple test"); + mailProps.setContentType("text/plain"); + + Mockito.doReturn(true).when(smtpMailSender).setMailRecipients(Mockito.any(SMTPMessage.class), Mockito.any(), Mockito.any()); + + SMTPMessage message = smtpMailSender.createMessage(mailProps); + + assertEquals("\"test@test.com\" ", message.getFrom()[0].toString()); + } + + @Test + public void validateSMTPMailSenderCreateMessageFromDefined() throws MessagingException, UnsupportedEncodingException { + smtpMailSender = smtpMailSender = Mockito.spy(smtpMailSender); + + SMTPMailProperties mailProps = new SMTPMailProperties(); + + mailProps.setSender(new MailAddress("test@test.com")); + mailProps.setFrom(new MailAddress("test2@test2.com", "TEST2")); + mailProps.setContent("A simple test"); + mailProps.setContentType("text/plain"); + + Mockito.doReturn(true).when(smtpMailSender).setMailRecipients(Mockito.any(SMTPMessage.class), Mockito.any(), Mockito.any()); + + SMTPMessage message = smtpMailSender.createMessage(mailProps); + + assertEquals("TEST2 ", message.getFrom()[0].toString()); + } + + @Test + public void validateSMTPMailSenderCreateMessageSentDateDefined() throws MessagingException, UnsupportedEncodingException { + smtpMailSender = smtpMailSender = Mockito.spy(smtpMailSender); + + SMTPMailProperties mailProps = new SMTPMailProperties(); + + mailProps.setSender(new MailAddress("test@test.com")); + mailProps.setContent("A simple test"); + mailProps.setContentType("text/plain"); + + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.set(Calendar.YEAR, 2015); + cal.set(Calendar.MONTH, 1); + cal.set(Calendar.DAY_OF_MONTH, 1); + + mailProps.setSentDate(cal.getTime()); + + Mockito.doReturn(true).when(smtpMailSender).setMailRecipients(Mockito.any(SMTPMessage.class), Mockito.any(), Mockito.any()); + + SMTPMessage message = smtpMailSender.createMessage(mailProps); + assertTrue(DateUtils.truncatedEquals(cal.getTime(), message.getSentDate(), Calendar.SECOND)); + } + + @Test + public void validateSMTPMailSenderCreateMessageSubjectContentAndContentTypeDefined() throws MessagingException, UnsupportedEncodingException, IOException { + smtpMailSender = smtpMailSender = Mockito.spy(smtpMailSender); + + SMTPMailProperties mailProps = new SMTPMailProperties(); + + String subject = "A TEST"; + String content = "A simple test"; + String contentType = "text/plain;charset=utf8"; + + mailProps.setSender(new MailAddress("test@test.com")); + mailProps.setSubject(subject); + mailProps.setContent(content); + mailProps.setContentType(contentType); + + Mockito.doReturn(true).when(smtpMailSender).setMailRecipients(Mockito.any(SMTPMessage.class), Mockito.any(), Mockito.any()); + + SMTPMessage message = smtpMailSender.createMessage(mailProps); + assertEquals(subject, message.getSubject()); + assertEquals(content, message.getContent()); + assertEquals(contentType, message.getContentType()); + } + + @Test + public void setMailRecipientsTest() throws UnsupportedEncodingException, MessagingException { + SMTPMessage messageMock = new SMTPMessage(Mockito.mock(MimeMessage.class)); + + Set recipients = new HashSet<>(); + recipients.add(new MailAddress(null)); + recipients.add(new MailAddress("")); + recipients.add(new MailAddress(" ")); + recipients.add(new MailAddress("smtp.acme.org")); + recipients.add(new MailAddress("smtp.acme2.org", "Coyote")); + + boolean returnOfSetEmail = smtpMailSender.setMailRecipients(messageMock, recipients, "A simple test"); + + Address[] allRecipients = messageMock.getAllRecipients(); + + int expectedNumberOfValidRecipientsConfigured = 2; + assertEquals(expectedNumberOfValidRecipientsConfigured, allRecipients.length); + + assertEquals("\"smtp.acme.org\" ", allRecipients[0].toString()); + assertEquals("Coyote ", allRecipients[1].toString()); + + assertTrue(returnOfSetEmail); + } + + @Test + public void setMailRecipientsTestOnlyInvalidEmailSettings() throws UnsupportedEncodingException, MessagingException { + SMTPMessage messageMock = new SMTPMessage(Mockito.mock(MimeMessage.class)); + + messageMock = messageMock = Mockito.spy(messageMock); + Mockito.doReturn(new Address[0]).when(messageMock).getAllRecipients(); + + Set recipients = new HashSet<>(); + recipients.add(new MailAddress(null)); + recipients.add(new MailAddress("")); + recipients.add(new MailAddress(" ")); + + boolean returnOfSetEmail = smtpMailSender.setMailRecipients(messageMock, recipients, "A simple test"); + + Address[] allRecipients = messageMock.getAllRecipients(); + + int expectedNumberOfValidRecipientsConfigured = 0; + assertEquals(expectedNumberOfValidRecipientsConfigured, allRecipients.length); + + assertFalse(returnOfSetEmail); + } + + @Test + public void validateSMTPMailSenderSendMailWithNullSession() { + SMTPMailProperties mailProps = new SMTPMailProperties(); + + boolean returnOfSendMail = smtpMailSender.sendMail(mailProps); + + assertFalse(returnOfSendMail); + } + + @Test + public void validateSMTPMailSenderSendMailWithValidSession() throws MessagingException, UnsupportedEncodingException { + smtpMailSender = smtpMailSender = Mockito.spy(smtpMailSender); + SMTPMailProperties mailProps = new SMTPMailProperties(); + + smtpMailSender.session = Session.getDefaultInstance(Mockito.mock(Properties.class)); + + Mockito.doReturn(Mockito.mock(SMTPMessage.class)).when(smtpMailSender).createMessage(Mockito.any(SMTPMailProperties.class)); + Mockito.doReturn(Mockito.mock(SMTPTransport.class)).when(smtpMailSender).createSmtpTransport(); + + boolean returnOfSendMail = smtpMailSender.sendMail(mailProps); + + assertTrue(returnOfSendMail); + } + + @Test + public void validateSMTPMailSenderGetConfigPropertyUndefinedMustReturnNull() { + smtpMailSender = smtpMailSender = Mockito.spy(smtpMailSender); + + String returnOfPropertyThatDoesNotExist = smtpMailSender.getConfig("test"); + + assertNull(returnOfPropertyThatDoesNotExist); + } + + public void validateSMTPMailSenderGetConfigPropertyDefinedMustReturnIt() { + smtpMailSender = smtpMailSender = Mockito.spy(smtpMailSender); + + Map configs = new HashMap<>(); + + String host = "smtp.acme.org"; + configs.put(getConfigName(SMTPMailSender.CONFIG_HOST), host); + + smtpMailSender.configs = configs; + + String returnOfPropertyThatExist = smtpMailSender.getConfig(getConfigName(SMTPMailSender.CONFIG_HOST)); + + assertNotNull(returnOfPropertyThatExist); + assertNotNull(host, returnOfPropertyThatExist); + } +}