Add password authentification on external smtp servers (#240)

* Add SMTP Authentification

* refactor: restrict access to getEmailSession()

* fix: FreemarkerEmailFactory test, defined protocols if TLS port is used to send email

* fix: add check for TLSv1.3 support
This commit is contained in:
Georgy Litvinov 2022-04-14 16:14:38 +02:00 committed by GitHub
parent 03624e5d1b
commit eff04e0979
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 90 additions and 9 deletions

View file

@ -9,12 +9,17 @@ import java.net.ConnectException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Properties; import java.util.Properties;
import java.util.Scanner; import java.util.Scanner;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session; import javax.mail.Session;
import javax.mail.internet.AddressException; import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.net.ssl.SSLContext;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
@ -40,11 +45,18 @@ import freemarker.template.Configuration;
* syntactically invalid, an exception is thrown and startup is aborted. * syntactically invalid, an exception is thrown and startup is aborted.
*/ */
public class FreemarkerEmailFactory { public class FreemarkerEmailFactory {
private static final Log log = LogFactory private static final Log log = LogFactory
.getLog(FreemarkerEmailFactory.class); .getLog(FreemarkerEmailFactory.class);
private static final int DEFAULT_SMTP_PORT = 25;
private static final int TLS_PORT = 587;
private static final int SSL_PORT = 465;
public static final String SMTP_HOST_PROPERTY = "email.smtpHost"; public static final String SMTP_HOST_PROPERTY = "email.smtpHost";
public static final String REPLY_TO_PROPERTY = "email.replyTo"; public static final String REPLY_TO_PROPERTY = "email.replyTo";
public static final String EMAIL_PASSWORD = "email.password";
public static final String EMAIL_USERNAME = "email.username";
public static final String EMAIL_PORT = "email.port";
private static final String ATTRIBUTE_NAME = FreemarkerEmailFactory.class private static final String ATTRIBUTE_NAME = FreemarkerEmailFactory.class
.getName(); .getName();
@ -91,13 +103,20 @@ public class FreemarkerEmailFactory {
private final String smtpHost; private final String smtpHost;
private final InternetAddress replyToAddress; private final InternetAddress replyToAddress;
private final Session emailSession; private final Session emailSession;
private final String password;
private final String userName;
private final int emailPort;
public FreemarkerEmailFactory(ServletContext ctx) { public FreemarkerEmailFactory(ServletContext ctx) {
this.smtpHost = getSmtpHostFromConfig(ctx); this.smtpHost = getSmtpHostFromConfig(ctx);
new SmtpHostTester().test(this.smtpHost); this.emailPort = getPortFromConfig(ctx);
new SmtpHostTester().test(this.smtpHost, emailPort);
this.replyToAddress = getReplyToAddressFromConfig(ctx); this.replyToAddress = getReplyToAddressFromConfig(ctx);
this.password = getPasswordFromConfig(ctx);
this.userName = getUserNameFromConfig(ctx);
this.emailSession = createEmailSession(smtpHost); this.emailSession = createEmailSession(smtpHost);
} }
String getSmtpHost() { String getSmtpHost() {
@ -108,7 +127,7 @@ public class FreemarkerEmailFactory {
return replyToAddress; return replyToAddress;
} }
Session getEmailSession() { private Session getEmailSession() {
return emailSession; return emailSession;
} }
@ -121,6 +140,28 @@ public class FreemarkerEmailFactory {
return hostName; return hostName;
} }
private String getPasswordFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String password = config.getProperty(EMAIL_PASSWORD, "");
return password;
}
private String getUserNameFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String userName = config.getProperty(EMAIL_USERNAME, "");
return userName;
}
private int getPortFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String port = config.getProperty(EMAIL_PORT, Integer.toString(DEFAULT_SMTP_PORT));
try {
return Integer.parseInt(port);
} catch (NumberFormatException e) {
return DEFAULT_SMTP_PORT;
}
}
private InternetAddress getReplyToAddressFromConfig(ServletContext ctx) { private InternetAddress getReplyToAddressFromConfig(ServletContext ctx) {
ConfigurationProperties config = ConfigurationProperties.getBean(ctx); ConfigurationProperties config = ConfigurationProperties.getBean(ctx);
String rawAddress = config.getProperty(REPLY_TO_PROPERTY, ""); String rawAddress = config.getProperty(REPLY_TO_PROPERTY, "");
@ -150,7 +191,43 @@ public class FreemarkerEmailFactory {
private Session createEmailSession(String hostName) { private Session createEmailSession(String hostName) {
Properties props = new Properties(System.getProperties()); Properties props = new Properties(System.getProperties());
props.put("mail.smtp.host", hostName); props.put("mail.smtp.host", hostName);
return Session.getDefaultInstance(props, null); props.put("mail.smtp.port", emailPort);
if (emailPort == TLS_PORT) {
props.put("mail.smtp.starttls.enable", "true");
if (isTLS13Supported()) {
props.put("mail.smtp.ssl.protocols", "TLSv1.3 TLSv1.2");
}
}
if (emailPort == SSL_PORT) {
props.put("mail.smtp.socketFactory.port", emailPort);
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
}
Authenticator auth = null;
if (!password.isEmpty() && !userName.isEmpty()) {
props.put("mail.smtp.auth", "true");
auth = getAuthenticator();
}
return Session.getDefaultInstance(props, auth);
}
private boolean isTLS13Supported() {
String[] protocols;
try {
protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
return (Arrays.stream(protocols).anyMatch("TLSv1.3"::equals));
} catch (NoSuchAlgorithmException e) {
log.error("No SSL context found. Suppose TLSv1.3 is not supported.");
log.error(e, e);
}
return false;
}
private Authenticator getAuthenticator() {
return new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
};
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -188,17 +265,18 @@ public class FreemarkerEmailFactory {
/** /**
* Try to open a connection to the SMTP host and conduct an "empty" * Try to open a connection to the SMTP host and conduct an "empty"
* conversation using SMTP. * conversation using SMTP.
* @param emailPort
* *
* @throws InvalidSmtpHost * @throws InvalidSmtpHost
* If anything goes wrong. * If anything goes wrong.
*/ */
public void test(String smtpHost) throws InvalidSmtpHost { public void test(String smtpHost, int emailPort) throws InvalidSmtpHost {
Socket socket = null; Socket socket = null;
PrintStream out = null; PrintStream out = null;
Scanner in = null; Scanner in = null;
try { try {
InetAddress hostAddr = InetAddress.getByName(smtpHost); InetAddress hostAddr = InetAddress.getByName(smtpHost);
socket = new Socket(hostAddr, SMTP_PORT); socket = new Socket(hostAddr, emailPort);
out = new PrintStream(socket.getOutputStream()); out = new PrintStream(socket.getOutputStream());
in = new Scanner(new InputStreamReader(socket.getInputStream())); in = new Scanner(new InputStreamReader(socket.getInputStream()));
@ -216,7 +294,7 @@ public class FreemarkerEmailFactory {
"host name is not recognized"); "host name is not recognized");
} catch (ConnectException e) { } catch (ConnectException e) {
throw new InvalidSmtpHost(smtpHost, throw new InvalidSmtpHost(smtpHost,
"refused connection on port " + SMTP_PORT); "refused connection on port " + emailPort);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("unrecognized problem: ", e); throw new RuntimeException("unrecognized problem: ", e);
} finally { } finally {

View file

@ -40,6 +40,9 @@ vitro.local.solr.url = http://localhost:8983/solr/vitrocore
# Example: # Example:
# email.smtpHost = smtp.mydomain.edu # email.smtpHost = smtp.mydomain.edu
# email.replyTo = vitroAdmin@mydomain.edu # email.replyTo = vitroAdmin@mydomain.edu
# email.username = vivtroAdmin@mydomain.edu
# email.password = secret
# email.port = 25 or 465 or 587
# #
email.smtpHost = email.smtpHost =
email.replyTo = email.replyTo =