From 3ac9fafa807032c19152f0d7b33edb04f7c59869 Mon Sep 17 00:00:00 2001 From: Oleh Astappiev Date: Mon, 16 Sep 2024 16:10:34 +0200 Subject: [PATCH] feat: move smtp settings to .env --- .../de/l3s/learnweb/app/ConfigProvider.java | 4 + .../beans/admin/AdminNotificationBean.java | 8 +- .../beans/publicPages/ContactBean.java | 9 +- .../l3s/learnweb/forum/ForumNotificator.java | 8 +- .../learnweb/user/ConfirmRequiredBean.java | 5 +- .../learnweb/user/EmailConfirmationBean.java | 53 +++++++++ .../de/l3s/learnweb/user/PasswordBean.java | 8 +- .../de/l3s/learnweb/user/ProfileBean.java | 5 +- .../l3s/learnweb/user/RegistrationBean.java | 5 +- src/main/java/de/l3s/learnweb/user/User.java | 26 ---- .../de/l3s/learnweb/web/BounceManager.java | 34 +++++- .../de/l3s/learnweb/web/RequestManager.java | 8 +- src/main/java/de/l3s/mail/Mail.java | 112 +++++------------- src/main/java/de/l3s/mail/MailService.java | 92 ++++++++++++++ .../de/l3s/mail/PasswordAuthenticator.java | 19 +++ .../de/l3s/mail/message/MessageBuilder.java | 10 ++ .../users/ForceEmailValidation.java | 41 ------- src/main/resources/.env.example | 14 +++ src/test/java/de/l3s/mail/MailTest.java | 22 ---- 19 files changed, 293 insertions(+), 190 deletions(-) create mode 100644 src/main/java/de/l3s/learnweb/user/EmailConfirmationBean.java create mode 100644 src/main/java/de/l3s/mail/MailService.java create mode 100644 src/main/java/de/l3s/mail/PasswordAuthenticator.java delete mode 100644 src/main/java/de/l3s/maintenance/users/ForceEmailValidation.java delete mode 100644 src/test/java/de/l3s/mail/MailTest.java diff --git a/src/main/java/de/l3s/learnweb/app/ConfigProvider.java b/src/main/java/de/l3s/learnweb/app/ConfigProvider.java index 95270f6f4..950ffe291 100644 --- a/src/main/java/de/l3s/learnweb/app/ConfigProvider.java +++ b/src/main/java/de/l3s/learnweb/app/ConfigProvider.java @@ -234,6 +234,10 @@ public boolean getPropertyBoolean(final String key) { return "true".equalsIgnoreCase(properties.getProperty(key)); } + public boolean getPropertyBoolean(final String key, final String defaultValue) { + return "true".equalsIgnoreCase(properties.getProperty(key, defaultValue)); + } + public String getEnvironment() { if (environment == null) { if (!isDevelopment()) { diff --git a/src/main/java/de/l3s/learnweb/beans/admin/AdminNotificationBean.java b/src/main/java/de/l3s/learnweb/beans/admin/AdminNotificationBean.java index 324761ea1..3c6436e2b 100644 --- a/src/main/java/de/l3s/learnweb/beans/admin/AdminNotificationBean.java +++ b/src/main/java/de/l3s/learnweb/beans/admin/AdminNotificationBean.java @@ -24,6 +24,7 @@ import de.l3s.learnweb.user.UserDao; import de.l3s.mail.Mail; import de.l3s.mail.MailFactory; +import de.l3s.mail.MailService; import de.l3s.util.bean.BeanHelper; @Named @@ -48,6 +49,9 @@ public class AdminNotificationBean extends ApplicationBean { @Inject private MessageDao messageDao; + @Inject + private MailService mailService; + @PostConstruct public void init() { user = getUser(); @@ -108,8 +112,8 @@ private void sendMail(final ArrayList recipients) { try { mail = MailFactory.buildNotificationEmail(title, text, user.getUsername()).build(user.getLocale()); mail.setReplyTo(user.getEmail()); - mail.setBccRecipients(recipients); - mail.send(); + mail.setRecipientsBcc(recipients); + mailService.send(mail); addMessage(FacesMessage.SEVERITY_INFO, recipients.size() + " emails send"); } catch (Exception e) { diff --git a/src/main/java/de/l3s/learnweb/beans/publicPages/ContactBean.java b/src/main/java/de/l3s/learnweb/beans/publicPages/ContactBean.java index 54aee63db..60199d929 100644 --- a/src/main/java/de/l3s/learnweb/beans/publicPages/ContactBean.java +++ b/src/main/java/de/l3s/learnweb/beans/publicPages/ContactBean.java @@ -5,6 +5,7 @@ import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.mail.MessagingException; import jakarta.validation.constraints.Email; @@ -15,6 +16,7 @@ import de.l3s.learnweb.user.User; import de.l3s.mail.Mail; import de.l3s.mail.MailFactory; +import de.l3s.mail.MailService; @Named @RequestScoped @@ -32,6 +34,9 @@ public class ContactBean extends ApplicationBean implements Serializable { @NotBlank private String message; + @Inject + private MailService mailService; + @PostConstruct public void init() { User user = getUser(); @@ -45,9 +50,9 @@ public void init() { public void sendMail() { try { Mail mail = MailFactory.buildContactFormEmail(name, email, message).build(getLocale()); - mail.setRecipient(config().getSupportEmail()); + mail.addRecipient(config().getSupportEmail()); mail.setReplyTo(email); - mail.send(); + mailService.send(mail); clearForm(); } catch (MessagingException e) { diff --git a/src/main/java/de/l3s/learnweb/forum/ForumNotificator.java b/src/main/java/de/l3s/learnweb/forum/ForumNotificator.java index b90eb46d9..e04037b7e 100644 --- a/src/main/java/de/l3s/learnweb/forum/ForumNotificator.java +++ b/src/main/java/de/l3s/learnweb/forum/ForumNotificator.java @@ -20,6 +20,7 @@ import de.l3s.learnweb.user.UserDao; import de.l3s.mail.Mail; import de.l3s.mail.MailFactory; +import de.l3s.mail.MailService; import de.l3s.util.HashHelper; public class ForumNotificator implements Runnable, Serializable { @@ -33,6 +34,9 @@ public class ForumNotificator implements Runnable, Serializable { @Inject private ForumTopicDao forumTopicDao; + @Inject + private MailService mailService; + @Override public void run() { try { @@ -68,8 +72,8 @@ private void sendMailWithNewTopics(User user, List topics) throws Me List otherTopics = topics.stream().filter(topic -> topic.getUserId() != user.getId()).toList(); Mail mail = MailFactory.buildForumNotificationEmail(user.getUsername(), userTopics, otherTopics, getHash(user)).build(user.getLocale()); - mail.setRecipient(user.getEmail()); - mail.send(); + mail.addRecipient(user.getEmail()); + mailService.send(mail); } public static String getHash(User user) { diff --git a/src/main/java/de/l3s/learnweb/user/ConfirmRequiredBean.java b/src/main/java/de/l3s/learnweb/user/ConfirmRequiredBean.java index 626b1a1f4..8bee4fa28 100644 --- a/src/main/java/de/l3s/learnweb/user/ConfirmRequiredBean.java +++ b/src/main/java/de/l3s/learnweb/user/ConfirmRequiredBean.java @@ -27,6 +27,9 @@ public class ConfirmRequiredBean extends ApplicationBean implements Serializable @Inject private UserDao userDao; + @Inject + private EmailConfirmationBean emailConfirmationBean; + @Override public User getUser() { if (super.getUser() != null) { @@ -47,7 +50,7 @@ public void onSubmitNewEmail() { userDao.save(user); } - if (user.sendEmailConfirmation()) { + if (emailConfirmationBean.sendEmailConfirmation(user)) { addMessage(FacesMessage.SEVERITY_INFO, "email_has_been_sent"); } else { addMessage(FacesMessage.SEVERITY_FATAL, "We were not able to send a confirmation mail"); diff --git a/src/main/java/de/l3s/learnweb/user/EmailConfirmationBean.java b/src/main/java/de/l3s/learnweb/user/EmailConfirmationBean.java new file mode 100644 index 000000000..055ea701e --- /dev/null +++ b/src/main/java/de/l3s/learnweb/user/EmailConfirmationBean.java @@ -0,0 +1,53 @@ +package de.l3s.learnweb.user; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.mail.MessagingException; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import de.l3s.learnweb.app.Learnweb; +import de.l3s.mail.Mail; +import de.l3s.mail.MailFactory; +import de.l3s.mail.MailService; +import de.l3s.util.StringHelper; + +@RequestScoped +public class EmailConfirmationBean implements Serializable { + @Serial + private static final long serialVersionUID = -1427221214029851844L; + private static final Logger log = LogManager.getLogger(EmailConfirmationBean.class); + + @Inject + private TokenDao tokenDao; + + @Inject + private MailService mailService; + + /** + * @return FALSE if an error occurred while sending this message + */ + public boolean sendEmailConfirmation(User user) { + try { + String token = RandomStringUtils.secure().nextAlphanumeric(32); + int tokenId = tokenDao.override(user.getId(), Token.TokenType.EMAIL_CONFIRMATION, token, LocalDateTime.now().plusYears(1)); + + String confirmEmailUrl = Learnweb.config().getServerUrl() + "/lw/user/confirm_email.jsf?" + + "email=" + StringHelper.urlEncode(user.getEmail()) + "&token=" + tokenId + ":" + token; + + Mail mail = MailFactory.buildConfirmEmail(user.getUsername(), confirmEmailUrl).build(user.getLocale()); + mail.addRecipient(user.getEmail()); + mailService.send(mail); + return true; + } catch (MessagingException e) { + log.error("Can't send confirmation mail to {}", this, e); + } + return false; + } +} diff --git a/src/main/java/de/l3s/learnweb/user/PasswordBean.java b/src/main/java/de/l3s/learnweb/user/PasswordBean.java index 1efbf57be..9b45bacb7 100644 --- a/src/main/java/de/l3s/learnweb/user/PasswordBean.java +++ b/src/main/java/de/l3s/learnweb/user/PasswordBean.java @@ -19,6 +19,7 @@ import de.l3s.learnweb.exceptions.HttpException; import de.l3s.mail.Mail; import de.l3s.mail.MailFactory; +import de.l3s.mail.MailService; import de.l3s.util.HashHelper; @Named @@ -32,6 +33,9 @@ public class PasswordBean extends ApplicationBean implements Serializable { @Inject private TokenDao tokenDao; + @Inject + private MailService mailService; + public void submit() { try { List users = new ArrayList<>(); @@ -50,8 +54,8 @@ public void submit() { String link = url + tokenId + ":" + token; Mail mail = MailFactory.buildPasswordChangeEmail(user.getDisplayName(), user.getEmail(), link).build(user.getLocale()); - mail.setRecipient(user.getEmail()); - mail.send(); + mail.addRecipient(user.getEmail()); + mailService.send(mail); } addMessage(FacesMessage.SEVERITY_INFO, "email_has_been_sent"); diff --git a/src/main/java/de/l3s/learnweb/user/ProfileBean.java b/src/main/java/de/l3s/learnweb/user/ProfileBean.java index d26685793..496fc98f6 100644 --- a/src/main/java/de/l3s/learnweb/user/ProfileBean.java +++ b/src/main/java/de/l3s/learnweb/user/ProfileBean.java @@ -76,6 +76,9 @@ public class ProfileBean extends ApplicationBean implements Serializable { @Inject private GroupDao groupDao; + @Inject + private EmailConfirmationBean emailConfirmationBean; + public void onLoad() { User loggedInUser = getUser(); BeanAssert.authorized(loggedInUser); @@ -142,7 +145,7 @@ public void onSaveProfile() { if (StringUtils.isNotEmpty(email) && !StringUtils.equals(selectedUser.getEmail(), email)) { selectedUser.setEmail(email); - if (selectedUser.sendEmailConfirmation()) { + if (emailConfirmationBean.sendEmailConfirmation(selectedUser)) { addMessage(FacesMessage.SEVERITY_INFO, "email_has_been_sent"); } else { addMessage(FacesMessage.SEVERITY_FATAL, "We were not able to send a confirmation mail"); diff --git a/src/main/java/de/l3s/learnweb/user/RegistrationBean.java b/src/main/java/de/l3s/learnweb/user/RegistrationBean.java index 2edd2e465..7aef788f5 100644 --- a/src/main/java/de/l3s/learnweb/user/RegistrationBean.java +++ b/src/main/java/de/l3s/learnweb/user/RegistrationBean.java @@ -79,6 +79,9 @@ public class RegistrationBean extends ApplicationBean implements Serializable { @Inject private ConfirmRequiredBean confirmRequiredBean; + @Inject + private EmailConfirmationBean emailConfirmationBean; + public String onLoad() { if (StringUtils.isNotEmpty(wizard)) { course = courseDao.findByWizard(wizard).orElseThrow(() -> new BadRequestHttpException("register_invalid_wizard_error")); @@ -178,7 +181,7 @@ public String register() { } if ((mailRequired || StringUtils.isNotEmpty(email)) && !user.isEmailConfirmed()) { - user.sendEmailConfirmation(); + emailConfirmationBean.sendEmailConfirmation(user); if (mailRequired) { confirmRequiredBean.setLoggedInUser(user); diff --git a/src/main/java/de/l3s/learnweb/user/User.java b/src/main/java/de/l3s/learnweb/user/User.java index df69f2bfc..fc7e77c1e 100644 --- a/src/main/java/de/l3s/learnweb/user/User.java +++ b/src/main/java/de/l3s/learnweb/user/User.java @@ -14,12 +14,10 @@ import java.util.Objects; import java.util.Optional; -import jakarta.mail.MessagingException; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,13 +32,10 @@ import de.l3s.learnweb.resource.Resource; import de.l3s.learnweb.resource.Tag; import de.l3s.learnweb.user.Organisation.Option; -import de.l3s.mail.Mail; -import de.l3s.mail.MailFactory; import de.l3s.util.Deletable; import de.l3s.util.HasId; import de.l3s.util.PBKDF2; import de.l3s.util.ProfileImageHelper; -import de.l3s.util.StringHelper; public class User implements Comparable, Deletable, HasId, Serializable { private static final Logger log = LogManager.getLogger(User.class); @@ -393,27 +388,6 @@ public int compareTo(User o) { return getUsername().compareTo(o.getUsername()); } - /** - * @return FALSE if an error occurred while sending this message - */ - public boolean sendEmailConfirmation() { - try { - String token = RandomStringUtils.secure().nextAlphanumeric(32); - int tokenId = Learnweb.dao().getTokenDao().override(id, Token.TokenType.EMAIL_CONFIRMATION, token, LocalDateTime.now().plusYears(1)); - - String confirmEmailUrl = Learnweb.config().getServerUrl() + "/lw/user/confirm_email.jsf?" + - "email=" + StringHelper.urlEncode(getEmail()) + "&token=" + tokenId + ":" + token; - - Mail mail = MailFactory.buildConfirmEmail(getUsername(), confirmEmailUrl).build(getLocale()); - mail.setRecipient(getEmail()); - mail.send(); - return true; - } catch (MessagingException e) { - log.error("Can't send confirmation mail to {}", this, e); - } - return false; - } - public boolean isAdmin() { return admin; } diff --git a/src/main/java/de/l3s/learnweb/web/BounceManager.java b/src/main/java/de/l3s/learnweb/web/BounceManager.java index 106495280..1555d1706 100644 --- a/src/main/java/de/l3s/learnweb/web/BounceManager.java +++ b/src/main/java/de/l3s/learnweb/web/BounceManager.java @@ -9,15 +9,18 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import jakarta.enterprise.context.Dependent; import jakarta.inject.Inject; +import jakarta.mail.Authenticator; import jakarta.mail.Flags; import jakarta.mail.Folder; import jakarta.mail.Message; import jakarta.mail.MessagingException; +import jakarta.mail.Session; import jakarta.mail.Store; import jakarta.mail.search.ComparisonTerm; import jakarta.mail.search.ReceivedDateTerm; @@ -26,7 +29,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import de.l3s.mail.Mail; +import de.l3s.learnweb.app.ConfigProvider; +import de.l3s.mail.PasswordAuthenticator; /** * Parses the mailbox every sometime and detects whether bounces are present. Bounced emails are @@ -45,8 +49,26 @@ public class BounceManager { @Inject private BounceDao bounceDao; + @Inject + private ConfigProvider config; + public Store getStore() throws MessagingException { - return Mail.createSession().getStore("imap"); + Properties props = new Properties(); + // props.setProperty("mail.debug", "true"); + props.setProperty("mail.imap.host", config.getProperty("MAIL_IMAP_HOST", config.getProperty("MAIL_SMTP_HOST"))); + props.setProperty("mail.imap.port", config.getProperty("MAIL_IMAP_PORT", "143")); + props.setProperty("mail.imap.socketFactory.port", config.getProperty("MAIL_IMAP_PORT", "143")); + props.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.setProperty("mail.imap.auth", "true"); + if (config.getPropertyBoolean("MAIL_IMAP_STARTTLS", "true")) { + props.setProperty("mail.imap.starttls.enable", "true"); + } + + final Authenticator authenticator = new PasswordAuthenticator( + config.getProperty("MAIL_IMAP_USERNAME", config.getProperty("MAIL_SMTP_USERNAME")), + config.getProperty("MAIL_IMAP_PASSWORD", config.getProperty("MAIL_SMTP_PASSWORD")) + ); + return Session.getInstance(props, authenticator).getStore("imap"); } public void parseInbox() throws MessagingException, IOException { @@ -102,7 +124,7 @@ private String getText(Message msg) throws MessagingException, IOException { * @param msg Message to be examined */ private boolean parseMessage(Message msg) throws MessagingException, IOException { - //Return path is checked first, since in bounce messages those are usually empty or contain just "<>" + // Return path is checked first, since in bounce messages those are usually empty or contain just "<>" String[] returnPaths = msg.getHeader("Return-Path"); if (returnPaths != null && returnPaths.length > 0 && returnPaths[0].length() > 3) { @@ -111,7 +133,7 @@ private boolean parseMessage(Message msg) throws MessagingException, IOException log.debug("BOUNCE: {} {}", msg.getSubject(), msg.getReceivedDate()); - //Checks the status code + // Checks the status code String text = getText(msg); Matcher matcherCode = STATUS_CODE_PATTERN.matcher(text); @@ -133,7 +155,7 @@ private boolean parseMessage(Message msg) throws MessagingException, IOException originalRecipient = matcherRecipient.group(2); } - //Adds message to database + // Adds message to database bounceDao.save(originalRecipient, msg.getReceivedDate().toInstant(), code, description); return true; } @@ -148,7 +170,7 @@ private String getErrorDescription(String errCode) { String[] codes = errCode.split("\\.", 2); - //Transient or permanent + // Transient or permanent if ("4".equals(codes[0])) { description = "Transient Persistent Failure: "; } else if ("5".equals(codes[0])) { diff --git a/src/main/java/de/l3s/learnweb/web/RequestManager.java b/src/main/java/de/l3s/learnweb/web/RequestManager.java index 482188a3a..d5cdc9cf5 100644 --- a/src/main/java/de/l3s/learnweb/web/RequestManager.java +++ b/src/main/java/de/l3s/learnweb/web/RequestManager.java @@ -32,6 +32,7 @@ import de.l3s.learnweb.app.Learnweb; import de.l3s.mail.Mail; import de.l3s.mail.MailFactory; +import de.l3s.mail.MailService; import de.l3s.util.SqlHelper; /** @@ -73,6 +74,9 @@ public class RequestManager implements Serializable { @Inject private RequestDao requestDao; + @Inject + private MailService mailService; + @PostConstruct public void init() { loadBanlist(); @@ -289,8 +293,8 @@ private void sendMail() { List suspiciousTemp = suspiciousRequests.size() > 10 ? suspiciousRequests.subList(0, 10) : suspiciousRequests; Mail mail = MailFactory.buildSuspiciousAlertEmail(suspiciousTemp).build(Locale.ENGLISH); - mail.setRecipient(Learnweb.config().getSupportEmail()); - mail.send(); + mail.addRecipient(Learnweb.config().getSupportEmail()); + mailService.send(mail); } catch (MessagingException e) { log.error("Failed to send admin alert mail. Error: ", e); } diff --git a/src/main/java/de/l3s/mail/Mail.java b/src/main/java/de/l3s/mail/Mail.java index 96b3a54d3..383eb0db2 100644 --- a/src/main/java/de/l3s/mail/Mail.java +++ b/src/main/java/de/l3s/mail/Mail.java @@ -1,106 +1,54 @@ package de.l3s.mail; -import java.util.Collection; -import java.util.Properties; +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; -import jakarta.mail.Authenticator; -import jakarta.mail.Message.RecipientType; -import jakarta.mail.MessagingException; -import jakarta.mail.PasswordAuthentication; -import jakarta.mail.Session; -import jakarta.mail.Transport; -import jakarta.mail.internet.InternetAddress; -import jakarta.mail.internet.MimeMessage; +public class Mail implements Serializable { + @Serial + private static final long serialVersionUID = -5917197634340620811L; -// TODO move settings -public class Mail { - public static final String FROM_ADDRESS = "learnweb@kbs.uni-hannover.de"; - public static final Authenticator AUTHENTICATOR = new PasswordAuthenticator("learnweb", "5-FN!@QENtrXh6V][C}*h8-S=yju"); + String subject; + String text; + String textHtml; - public static Session createSession() throws MessagingException { - Properties props = new Properties(); - props.setProperty("mail.smtp.host", "mail.kbs.uni-hannover.de"); - props.setProperty("mail.smtp.port", "587"); - props.setProperty("mail.smtp.socketFactory.port", "587"); - props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); - props.setProperty("mail.smtp.auth", "true"); - props.setProperty("mail.smtp.starttls.enable", "true"); - props.setProperty("mail.imap.host", "mail.kbs.uni-hannover.de"); - props.setProperty("mail.imap.port", "143"); - props.setProperty("mail.imap.socketFactory.port", "143"); - props.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); - props.setProperty("mail.imap.auth", "true"); - props.setProperty("mail.imap.starttls.enable", "true"); - //props.setProperty("mail.debug", "true"); + ArrayList recipients = new ArrayList<>(); + ArrayList recipientsCc = new ArrayList<>(); + ArrayList recipientsBcc = new ArrayList<>(); + String replyTo; - return Session.getInstance(props, AUTHENTICATOR); + public Mail() { } - private final MimeMessage message; - - public Mail() throws MessagingException { - message = new MimeMessage(createSession()); - message.setFrom(new InternetAddress(FROM_ADDRESS)); - } - - public void send() throws MessagingException { - message.saveChanges(); - Transport.send(message); + public void setSubject(String subject) { + this.subject = subject; } - public void setReplyTo(String address) throws MessagingException { - InternetAddress[] addresses = {new InternetAddress(address)}; - message.setReplyTo(addresses); + public void setText(String text) { + this.text = text; } - public void setRecipient(String address) throws MessagingException { - message.setRecipient(RecipientType.TO, new InternetAddress(address)); + public void setHTML(String text) { + this.textHtml = text; } - public void setBccRecipients(final Collection addresses) throws MessagingException { - int i = 0; - InternetAddress[] recipientsArr = new InternetAddress[addresses.size()]; - - for (String address : addresses) { - recipientsArr[i++] = new InternetAddress(address); - } - - message.setRecipients(RecipientType.BCC, recipientsArr); + public void addRecipient(String address) { + this.recipients.add(address); } - public void setSubject(String subject) throws MessagingException { - message.setSubject(subject); + public void addRecipientBc(String address) { + this.recipientsCc.add(address); } - public void setText(String text) throws MessagingException { - message.setText(text); + public void addRecipientBcc(String address) { + this.recipientsBcc.add(address); } - public void setHTML(String text) throws MessagingException { - message.setText(text, "UTF-8", "html"); + public void setRecipientsBcc(ArrayList addresses) { + this.recipientsBcc = addresses; } - public void setContent(Object html, String type) throws MessagingException { - message.setContent(html, type); - } - - @Override - public String toString() { - return message.toString(); - } - - static class PasswordAuthenticator extends Authenticator { - private final String username; - private final String password; - - PasswordAuthenticator(String username, String password) { - this.username = username; - this.password = password; - } - - @Override - public PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } + public void setReplyTo(String address) { + replyTo = address; } } diff --git a/src/main/java/de/l3s/mail/MailService.java b/src/main/java/de/l3s/mail/MailService.java new file mode 100644 index 000000000..520b550f5 --- /dev/null +++ b/src/main/java/de/l3s/mail/MailService.java @@ -0,0 +1,92 @@ +package de.l3s.mail; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Properties; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.mail.Authenticator; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; + +import de.l3s.learnweb.app.ConfigProvider; + +@ApplicationScoped +public class MailService implements Serializable { + + @Serial + private static final long serialVersionUID = -3122617239830631371L; + + @Inject + private ConfigProvider config; + + public Session createSession() { + Properties props = new Properties(); + // props.setProperty("mail.debug", "true"); + props.setProperty("mail.smtp.host", config.getProperty("MAIL_SMTP_HOST")); + props.setProperty("mail.smtp.port", config.getProperty("MAIL_SMTP_PORT", "587")); + props.setProperty("mail.smtp.socketFactory.port", config.getProperty("MAIL_SMTP_PORT", "587")); + props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.setProperty("mail.smtp.auth", "true"); + if (config.getPropertyBoolean("MAIL_SMTP_STARTTLS", "true")) { + props.setProperty("mail.smtp.starttls.enable", "true"); + } + + final Authenticator authenticator = new PasswordAuthenticator(config.getProperty("MAIL_SMTP_USERNAME"), config.getProperty("MAIL_SMTP_PASSWORD")); + return Session.getInstance(props, authenticator); + } + + public MimeMessage createMessage() { + try { + MimeMessage message = new MimeMessage(createSession()); + message.setFrom(new InternetAddress(config.getProperty("MAIL_FROM_ADDRESS"))); + return message; + } catch (MessagingException e) { + throw new RuntimeException("The from address is not valid", e); + } + } + + public void send(Mail mail) { + try { + MimeMessage message = createMessage(); + if (mail.subject != null) { + message.setSubject(mail.subject); + } + if (mail.text != null) { + message.setText(mail.text); + } + if (mail.textHtml != null) { + message.setText(mail.textHtml, "UTF-8", "html"); + } + if (!mail.recipients.isEmpty()) { + for (String address : mail.recipients) { + message.addRecipient(Message.RecipientType.TO, new InternetAddress(address)); + } + } + if (!mail.recipientsCc.isEmpty()) { + for (String address : mail.recipientsCc) { + message.addRecipient(Message.RecipientType.CC, new InternetAddress(address)); + } + } + if (!mail.recipientsBcc.isEmpty()) { + for (String address : mail.recipientsBcc) { + message.addRecipient(Message.RecipientType.BCC, new InternetAddress(address)); + } + } + if (mail.replyTo != null) { + InternetAddress[] addresses = {new InternetAddress(mail.replyTo)}; + message.setReplyTo(addresses); + } + + message.saveChanges(); + Transport.send(message); + } catch (MessagingException e) { + throw new RuntimeException("Could not send mail", e); + } + } +} diff --git a/src/main/java/de/l3s/mail/PasswordAuthenticator.java b/src/main/java/de/l3s/mail/PasswordAuthenticator.java new file mode 100644 index 000000000..fbaabb191 --- /dev/null +++ b/src/main/java/de/l3s/mail/PasswordAuthenticator.java @@ -0,0 +1,19 @@ +package de.l3s.mail; + +import jakarta.mail.Authenticator; +import jakarta.mail.PasswordAuthentication; + +public class PasswordAuthenticator extends Authenticator { + private final String username; + private final String password; + + public PasswordAuthenticator(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } +} diff --git a/src/main/java/de/l3s/mail/message/MessageBuilder.java b/src/main/java/de/l3s/mail/message/MessageBuilder.java index 9cc18a081..d72cf7071 100644 --- a/src/main/java/de/l3s/mail/message/MessageBuilder.java +++ b/src/main/java/de/l3s/mail/message/MessageBuilder.java @@ -5,6 +5,7 @@ import java.util.Locale; import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; import org.apache.commons.lang3.StringUtils; @@ -103,4 +104,13 @@ public Mail build(Locale locale) throws MessagingException { mail.setHTML(buildHtmlText(bundle)); return mail; } + + public MimeMessage build(Locale locale, MimeMessage message) throws MessagingException { + MessagesBundle bundle = new MessagesBundle(locale); + + message.setSubject(getSubject(bundle)); + message.setText(buildPlainText(bundle)); + message.setText(buildHtmlText(bundle), "UTF-8", "html"); + return message; + } } diff --git a/src/main/java/de/l3s/maintenance/users/ForceEmailValidation.java b/src/main/java/de/l3s/maintenance/users/ForceEmailValidation.java deleted file mode 100644 index 52586b21a..000000000 --- a/src/main/java/de/l3s/maintenance/users/ForceEmailValidation.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.l3s.maintenance.users; - -import java.util.List; - -import org.jdbi.v3.core.Handle; - -import de.l3s.learnweb.user.User; -import de.l3s.learnweb.user.UserDao; -import de.l3s.maintenance.MaintenanceTask; - -/** - * Resent the email authentication link for selected users. - * - * @author Philipp Kemkes - */ -public class ForceEmailValidation extends MaintenanceTask { - - @Override - protected void run(final boolean dryRun) { - try (Handle handle = getLearnweb().openJdbiHandle()) { - List users = handle.select("SELECT * FROM lw_user WHERE is_email_confirmed = 0 and organisation_id != 478 and email != '' " - + "ORDER BY created_at DESC").map(new UserDao.UserMapper()).list(); - - for (User user : users) { - /* - // force creation of mail validation token - String mailBackup = user.getEmail(); - user.setEmail(""); - user.setEmail(mailBackup); - */ - - log.debug(user.getEmail()); - user.sendEmailConfirmation(); - } - } - } - - public static void main(String[] args) { - new ForceEmailValidation().start(args); - } -} diff --git a/src/main/resources/.env.example b/src/main/resources/.env.example index df8c2b434..66d19f90e 100644 --- a/src/main/resources/.env.example +++ b/src/main/resources/.env.example @@ -20,3 +20,17 @@ INTEGRATION_INTERWEB_APIKEY= # If you need to work with videos, install FFmpeg on your system and update these paths FFMPEG_PATH=/usr/bin/ffmpeg FFPROBE_PATH=/usr/bin/ffprobe + +MAIL_FROM_ADDRESS= + +MAIL_SMTP_HOST= +MAIL_SMTP_PORT=587 +MAIL_SMTP_STARTTLS=true +MAIL_SMTP_USERNAME= +MAIL_SMTP_PASSWORD= + +MAIL_IMAP_HOST= +MAIL_IMAP_PORT=143 +MAIL_IMAP_STARTTLS=true +MAIL_IMAP_USERNAME= +MAIL_IMAP_PASSWORD= diff --git a/src/test/java/de/l3s/mail/MailTest.java b/src/test/java/de/l3s/mail/MailTest.java deleted file mode 100644 index 2052abe3c..000000000 --- a/src/test/java/de/l3s/mail/MailTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.l3s.mail; - -import static org.junit.jupiter.api.Assertions.*; - -import jakarta.mail.MessagingException; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -class MailTest { - - @Test - @Disabled("If enabled, it will send an actual email, I don't want to receive the email every time tests is running") - void sendMail() throws MessagingException { - Mail mail = new Mail(); - mail.setRecipient("astappiev@l3s.de"); - mail.setHTML("Hello world!"); - mail.setSubject("Test email"); - mail.send(); - assertTrue(true); - } -}