Polish "Add SslBundle support to MailSender"

See gh-40037
This commit is contained in:
Moritz Halbritter 2024-07-04 15:26:33 +02:00
parent e7424eacf8
commit cf2b08b8a6
2 changed files with 20 additions and 38 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -68,11 +68,11 @@ class MailSenderAutoConfigurationIntegrationTests {
return message.getSubject(); return message.getSubject();
} }
catch (MessagingException ex) { catch (MessagingException ex) {
throw new RuntimeException(ex); throw new RuntimeException("Failed to get message subject", ex);
} }
} }
private void assertMessagesContainSubject(Session session, String subject) { private void assertMessagesContainSubject(Session session, String subject) throws MessagingException {
try (Store store = session.getStore("pop3")) { try (Store store = session.getStore("pop3")) {
String host = session.getProperty("mail.pop3.host"); String host = session.getProperty("mail.pop3.host");
int port = Integer.parseInt(session.getProperty("mail.pop3.port")); int port = Integer.parseInt(session.getProperty("mail.pop3.port"));
@ -86,20 +86,13 @@ class MailSenderAutoConfigurationIntegrationTests {
.contains(subject)); .contains(subject));
} }
} }
catch (MessagingException ex) {
throw new RuntimeException(ex);
}
} }
@Nested @Nested
class ImplicitTlsTests { class ImplicitTlsTests {
final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, SslAutoConfiguration.class));
@Container @Container
private static final MailpitContainer mailpit = TestImage.container(MailpitContainer.class) private static final MailpitContainer mailpit = TestImage.container(MailpitContainer.class)
// force ssl connection
.withSmtpRequireTls(true) .withSmtpRequireTls(true)
.withSmtpTlsCert(MountableFile .withSmtpTlsCert(MountableFile
.forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.crt")) .forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.crt"))
@ -107,6 +100,9 @@ class MailSenderAutoConfigurationIntegrationTests {
.forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.key")) .forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.key"))
.withPop3Auth("user:pass"); .withPop3Auth("user:pass");
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, SslAutoConfiguration.class));
@Test @Test
void sendEmailWithSslEnabledAndCert() { void sendEmailWithSslEnabledAndCert() {
this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(), this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(),
@ -115,14 +111,11 @@ class MailSenderAutoConfigurationIntegrationTests {
"spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt", "spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt",
"spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt", "spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt",
"spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key", "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key",
// pop3
"spring.mail.properties.mail.pop3.host:" + mailpit.getHost(), "spring.mail.properties.mail.pop3.host:" + mailpit.getHost(),
"spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port()) "spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port())
.run((context) -> { .run((context) -> {
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
mailSender.send(createMessage("Hello World!")); mailSender.send(createMessage("Hello World!"));
assertMessagesContainSubject(mailSender.getSession(), "Hello World!"); assertMessagesContainSubject(mailSender.getSession(), "Hello World!");
}); });
} }
@ -134,7 +127,6 @@ class MailSenderAutoConfigurationIntegrationTests {
"spring.mail.port:" + mailpit.getSmtpPort(), "spring.mail.ssl.enabled:true") "spring.mail.port:" + mailpit.getSmtpPort(), "spring.mail.ssl.enabled:true")
.run((context) -> { .run((context) -> {
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail")))
.withRootCauseInstanceOf(CertPathBuilderException.class); .withRootCauseInstanceOf(CertPathBuilderException.class);
}); });
@ -150,7 +142,6 @@ class MailSenderAutoConfigurationIntegrationTests {
"spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key") "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key")
.run((context) -> { .run((context) -> {
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail")))
.withRootCauseInstanceOf(SocketTimeoutException.class); .withRootCauseInstanceOf(SocketTimeoutException.class);
}); });
@ -161,9 +152,6 @@ class MailSenderAutoConfigurationIntegrationTests {
@Nested @Nested
class StarttlsTests { class StarttlsTests {
final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, SslAutoConfiguration.class));
@Container @Container
private static final MailpitContainer mailpit = TestImage.container(MailpitContainer.class) private static final MailpitContainer mailpit = TestImage.container(MailpitContainer.class)
.withSmtpRequireStarttls(true) .withSmtpRequireStarttls(true)
@ -173,6 +161,9 @@ class MailSenderAutoConfigurationIntegrationTests {
.forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.key")) .forClasspathResource("/org/springframework/boot/autoconfigure/mail/ssl/test-server.key"))
.withPop3Auth("user:pass"); .withPop3Auth("user:pass");
final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, SslAutoConfiguration.class));
@Test @Test
void sendEmailWithStarttlsAndCertAndSslDisabled() { void sendEmailWithStarttlsAndCertAndSslDisabled() {
this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(), this.contextRunner.withPropertyValues("spring.mail.host:" + mailpit.getHost(),
@ -182,14 +173,11 @@ class MailSenderAutoConfigurationIntegrationTests {
"spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt", "spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt",
"spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt", "spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt",
"spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key", "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key",
// pop3
"spring.mail.properties.mail.pop3.host:" + mailpit.getHost(), "spring.mail.properties.mail.pop3.host:" + mailpit.getHost(),
"spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port()) "spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port())
.run((context) -> { .run((context) -> {
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
mailSender.send(createMessage("Sent with STARTTLS")); mailSender.send(createMessage("Sent with STARTTLS"));
assertMessagesContainSubject(mailSender.getSession(), "Sent with STARTTLS"); assertMessagesContainSubject(mailSender.getSession(), "Sent with STARTTLS");
}); });
} }
@ -203,12 +191,10 @@ class MailSenderAutoConfigurationIntegrationTests {
"spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt", "spring.ssl.bundle.pem.test-bundle.truststore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-ca.crt",
"spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt", "spring.ssl.bundle.pem.test-bundle.keystore.certificate=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.crt",
"spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key", "spring.ssl.bundle.pem.test-bundle.keystore.private-key=classpath:org/springframework/boot/autoconfigure/mail/ssl/test-client.key",
// pop3
"spring.mail.properties.mail.pop3.host:" + mailpit.getHost(), "spring.mail.properties.mail.pop3.host:" + mailpit.getHost(),
"spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port()) "spring.mail.properties.mail.pop3.port:" + mailpit.getPop3Port())
.run((context) -> { .run((context) -> {
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail")))
.withRootCauseInstanceOf(SSLException.class); .withRootCauseInstanceOf(SSLException.class);
}); });
@ -223,7 +209,6 @@ class MailSenderAutoConfigurationIntegrationTests {
"spring.mail.properties.mail.smtp.starttls.required:true") "spring.mail.properties.mail.smtp.starttls.required:true")
.run((context) -> { .run((context) -> {
JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class); JavaMailSenderImpl mailSender = context.getBean(JavaMailSenderImpl.class);
assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail"))) assertThatException().isThrownBy(() -> mailSender.send(createMessage("Should fail")))
.withRootCauseInstanceOf(CertPathBuilderException.class); .withRootCauseInstanceOf(CertPathBuilderException.class);
}); });

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import java.util.Properties;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.mail.MailProperties.Ssl;
import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -29,6 +30,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailSender; import org.springframework.mail.MailSender;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.util.StringUtils;
/** /**
* Auto-configure a {@link MailSender} based on properties configuration. * Auto-configure a {@link MailSender} based on properties configuration.
@ -41,21 +43,15 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
@ConditionalOnProperty(prefix = "spring.mail", name = "host") @ConditionalOnProperty(prefix = "spring.mail", name = "host")
class MailSenderPropertiesConfiguration { class MailSenderPropertiesConfiguration {
private final ObjectProvider<SslBundles> sslBundles;
MailSenderPropertiesConfiguration(ObjectProvider<SslBundles> sslBundles) {
this.sslBundles = sslBundles;
}
@Bean @Bean
@ConditionalOnMissingBean(JavaMailSender.class) @ConditionalOnMissingBean(JavaMailSender.class)
JavaMailSenderImpl mailSender(MailProperties properties) { JavaMailSenderImpl mailSender(MailProperties properties, ObjectProvider<SslBundles> sslBundles) {
JavaMailSenderImpl sender = new JavaMailSenderImpl(); JavaMailSenderImpl sender = new JavaMailSenderImpl();
applyProperties(properties, sender); applyProperties(properties, sender, sslBundles.getIfAvailable());
return sender; return sender;
} }
private void applyProperties(MailProperties properties, JavaMailSenderImpl sender) { private void applyProperties(MailProperties properties, JavaMailSenderImpl sender, SslBundles sslBundles) {
sender.setHost(properties.getHost()); sender.setHost(properties.getHost());
if (properties.getPort() != null) { if (properties.getPort() != null) {
sender.setPort(properties.getPort()); sender.setPort(properties.getPort());
@ -68,14 +64,15 @@ class MailSenderPropertiesConfiguration {
} }
Properties javaMailProperties = asProperties(properties.getProperties()); Properties javaMailProperties = asProperties(properties.getProperties());
String protocol = properties.getProtocol(); String protocol = properties.getProtocol();
if (protocol == null || protocol.isEmpty()) { if (!StringUtils.hasLength(protocol)) {
protocol = "smtp"; protocol = "smtp";
} }
if (properties.getSsl().isEnabled()) { Ssl ssl = properties.getSsl();
if (ssl.isEnabled()) {
javaMailProperties.setProperty("mail." + protocol + ".ssl.enable", "true"); javaMailProperties.setProperty("mail." + protocol + ".ssl.enable", "true");
} }
if (properties.getSsl().getBundle() != null) { if (ssl.getBundle() != null) {
SslBundle sslBundle = this.sslBundles.getObject().getBundle(properties.getSsl().getBundle()); SslBundle sslBundle = sslBundles.getBundle(ssl.getBundle());
javaMailProperties.put("mail." + protocol + ".ssl.socketFactory", javaMailProperties.put("mail." + protocol + ".ssl.socketFactory",
sslBundle.createSslContext().getSocketFactory()); sslBundle.createSslContext().getSocketFactory());
} }