Skip to content

Commit

Permalink
Add SslBundle support to MailSender
Browse files Browse the repository at this point in the history
  • Loading branch information
ruifigueira committed Mar 25, 2024
1 parent e3f8c34 commit fdb86de
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 4 deletions.
Expand Up @@ -76,6 +76,11 @@ public class MailProperties {
*/
private String jndiName;

/**
* SSL configuration.
*/
private final Ssl ssl = new Ssl();

public String getHost() {
return this.host;
}
Expand Down Expand Up @@ -136,4 +141,45 @@ public String getJndiName() {
return this.jndiName;
}

public Ssl getSsl() {
return this.ssl;
}

public static class Ssl {

/**
* Whether to enable SSL support. If enabled, {@code mail.<protocol>.ssl.enable}
* property is set to {@code true}.
*/
private boolean enabled = false;

/**
* SSL bundle name. If not null, {@code mail.<protocol>.ssl.socketFactory}
* property is set to a {@code SSLSocketFactory} obtained from the corresponding
* SSL bundle.
* <p>
* Note that the {@code STARTTLS} command can use the corresponding
* {@code SSLSocketFactory}, even if {@code mail.<protocol>.ssl.enable} property
* is not set.
*/
private String bundle;

public boolean isEnabled() {
return this.enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getBundle() {
return this.bundle;
}

public void setBundle(String bundle) {
this.bundle = bundle;
}

}

}
Expand Up @@ -19,8 +19,11 @@
import java.util.Map;
import java.util.Properties;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailSender;
Expand All @@ -38,6 +41,12 @@
@ConditionalOnProperty(prefix = "spring.mail", name = "host")
class MailSenderPropertiesConfiguration {

private final ObjectProvider<SslBundles> sslBundles;

MailSenderPropertiesConfiguration(ObjectProvider<SslBundles> sslBundles) {
this.sslBundles = sslBundles;
}

@Bean
@ConditionalOnMissingBean(JavaMailSender.class)
JavaMailSenderImpl mailSender(MailProperties properties) {
Expand All @@ -57,8 +66,21 @@ private void applyProperties(MailProperties properties, JavaMailSenderImpl sende
if (properties.getDefaultEncoding() != null) {
sender.setDefaultEncoding(properties.getDefaultEncoding().name());
}
if (!properties.getProperties().isEmpty()) {
sender.setJavaMailProperties(asProperties(properties.getProperties()));
Properties javaMailProperties = asProperties(properties.getProperties());
String protocol = properties.getProtocol();
if (protocol == null || protocol.isEmpty()) {
protocol = "smtp";
}
if (properties.getSsl().isEnabled()) {
javaMailProperties.setProperty("mail." + protocol + ".ssl.enable", "true");
}
if (properties.getSsl().getBundle() != null) {
SslBundle sslBundle = this.sslBundles.getObject().getBundle(properties.getSsl().getBundle());
javaMailProperties.put("mail." + protocol + ".ssl.socketFactory",
sslBundle.createSslContext().getSocketFactory());
}
if (!javaMailProperties.isEmpty()) {
sender.setJavaMailProperties(javaMailProperties);
}
}

Expand Down
Expand Up @@ -19,6 +19,7 @@
import java.util.Properties;

import javax.naming.Context;
import javax.net.ssl.SSLSocketFactory;

import jakarta.mail.Session;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -29,6 +30,7 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jndi.JndiPropertiesHidingClassLoader;
import org.springframework.boot.autoconfigure.jndi.TestableInitialContextFactory;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -49,8 +51,9 @@
*/
class MailSenderAutoConfigurationTests {

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(MailSenderAutoConfiguration.class, MailSenderValidatorAutoConfiguration.class));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class,
MailSenderValidatorAutoConfiguration.class, SslAutoConfiguration.class));

private ClassLoader threadContextClassLoader;

Expand Down Expand Up @@ -240,6 +243,61 @@ void connectionOnStartupNotCalled() {
});
}

@Test
void smtpSslEnabled() {
this.contextRunner.withPropertyValues("spring.mail.host:localhost", "spring.mail.ssl.enabled:true")
.run((context) -> {
assertThat(context).hasSingleBean(JavaMailSenderImpl.class);
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThat(mailSender.getJavaMailProperties()).containsEntry("mail.smtp.ssl.enable", "true");
});
}

@Test
void smtpSslBundle() {
this.contextRunner
.withPropertyValues("spring.mail.host:localhost", "spring.mail.ssl.bundle:test-bundle",
"spring.ssl.bundle.jks.test-bundle.keystore.location:classpath:test.jks",
"spring.ssl.bundle.jks.test-bundle.keystore.password:secret",
"spring.ssl.bundle.jks.test-bundle.key.password:password")
.run((context) -> {
assertThat(context).hasSingleBean(JavaMailSenderImpl.class);
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThat(mailSender.getJavaMailProperties()).doesNotContainKey("mail.smtp.ssl.enable");
Object property = mailSender.getJavaMailProperties().get("mail.smtp.ssl.socketFactory");
assertThat(property).isInstanceOf(SSLSocketFactory.class);
});
}

@Test
void smtpsSslEnabled() {
this.contextRunner
.withPropertyValues("spring.mail.host:localhost", "spring.mail.protocol:smtps",
"spring.mail.ssl.enabled:true")
.run((context) -> {
assertThat(context).hasSingleBean(JavaMailSenderImpl.class);
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThat(mailSender.getJavaMailProperties()).containsEntry("mail.smtps.ssl.enable", "true");
});
}

@Test
void smtpsSslBundle() {
this.contextRunner
.withPropertyValues("spring.mail.host:localhost", "spring.mail.protocol:smtps",
"spring.mail.ssl.bundle:test-bundle",
"spring.ssl.bundle.jks.test-bundle.keystore.location:classpath:test.jks",
"spring.ssl.bundle.jks.test-bundle.keystore.password:secret",
"spring.ssl.bundle.jks.test-bundle.key.password:password")
.run((context) -> {
assertThat(context).hasSingleBean(JavaMailSenderImpl.class);
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThat(mailSender.getJavaMailProperties()).doesNotContainKey("mail.smtps.ssl.enable");
Object property = mailSender.getJavaMailProperties().get("mail.smtps.ssl.socketFactory");
assertThat(property).isInstanceOf(SSLSocketFactory.class);
});
}

private Session configureJndiSession(String name) {
Properties properties = new Properties();
Session session = Session.getDefaultInstance(properties);
Expand Down

0 comments on commit fdb86de

Please sign in to comment.