Skip to content

Commit

Permalink
feat: add an API for validate email config
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Mar 7, 2024
1 parent dde0cd6 commit 22e0c63
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package run.halo.app.notification;

import com.fasterxml.jackson.databind.JsonNode;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.util.Pair;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.notification.EmailSenderHelper.EmailSenderConfig;

/**
* <p>A notifier that can send email.</p>
Expand All @@ -34,7 +32,8 @@ public class EmailNotifier implements ReactiveNotifier {

private final SubscriberEmailResolver subscriberEmailResolver;
private final NotificationTemplateRender notificationTemplateRender;
private final AtomicReference<Pair<EmailSenderConfig, JavaMailSenderImpl>>
private final EmailSenderHelper emailSenderHelper;
private final AtomicReference<Pair<EmailSenderConfig, JavaMailSender>>
emailSenderConfigPairRef = new AtomicReference<>();

@Override
Expand All @@ -48,7 +47,7 @@ public Mono<Void> notify(NotificationContext context) {
return Mono.empty();
}

JavaMailSenderImpl javaMailSender = getJavaMailSender(emailSenderConfig);
JavaMailSender javaMailSender = getJavaMailSender(emailSenderConfig);

String recipient = context.getMessage().getRecipient();
var subscriber = new Subscription.Subscriber();
Expand Down Expand Up @@ -83,55 +82,20 @@ public Mono<Void> notify(NotificationContext context) {
}

@NonNull
private static MimeMessagePreparator getMimeMessagePreparator(String toEmail,
private MimeMessagePreparator getMimeMessagePreparator(String toEmail,
EmailSenderConfig emailSenderConfig, NotificationContext.MessagePayload payload) {
return mimeMessage -> {
MimeMessageHelper helper =
new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
helper.setFrom(emailSenderConfig.getUsername(), emailSenderConfig.getDisplayName());

helper.setSubject(payload.getTitle());
helper.setText(payload.getRawBody(), payload.getHtmlBody());
helper.setTo(toEmail);
};
}

@NonNull
private static JavaMailSenderImpl createJavaMailSender(EmailSenderConfig emailSenderConfig) {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(emailSenderConfig.getHost());
javaMailSender.setPort(emailSenderConfig.getPort());
javaMailSender.setUsername(emailSenderConfig.getUsername());
javaMailSender.setPassword(emailSenderConfig.getPassword());

Properties props = javaMailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
if ("SSL".equals(emailSenderConfig.getEncryption())) {
props.put("mail.smtp.ssl.enable", "true");
}

if ("TLS".equals(emailSenderConfig.getEncryption())) {
props.put("mail.smtp.starttls.enable", "true");
}

if ("NONE".equals(emailSenderConfig.getEncryption())) {
props.put("mail.smtp.ssl.enable", "false");
props.put("mail.smtp.starttls.enable", "false");
}

if (log.isDebugEnabled()) {
props.put("mail.debug", "true");
}
return javaMailSender;
return emailSenderHelper.createMimeMessagePreparator(emailSenderConfig, toEmail,
payload.getTitle(),
payload.getRawBody(), payload.getHtmlBody());
}

JavaMailSenderImpl getJavaMailSender(EmailSenderConfig emailSenderConfig) {
JavaMailSender getJavaMailSender(EmailSenderConfig emailSenderConfig) {
return emailSenderConfigPairRef.updateAndGet(pair -> {
if (pair != null && pair.getFirst().equals(emailSenderConfig)) {
return pair;
}
return Pair.of(emailSenderConfig, createJavaMailSender(emailSenderConfig));
return Pair.of(emailSenderConfig,
emailSenderHelper.createJavaMailSender(emailSenderConfig));
}).getSecond();
}

Expand All @@ -156,24 +120,4 @@ Mono<String> appendHtmlBodyFooter(ReasonAttributes attributes) {
</div>
""", attributes);
}

@Data
static class EmailSenderConfig {
private boolean enable;
private String displayName;
private String username;
private String password;
private String host;
private Integer port;
private String encryption;

/**
* Gets email display name.
*
* @return display name if not blank, otherwise username.
*/
public String getDisplayName() {
return StringUtils.defaultIfBlank(displayName, username);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package run.halo.app.notification;

import lombok.Data;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;

public interface EmailSenderHelper {

@NonNull
JavaMailSender createJavaMailSender(EmailSenderConfig senderConfig);

@NonNull
MimeMessagePreparator createMimeMessagePreparator(EmailSenderConfig senderConfig,
String toEmail, String subject, String raw, String html);

@Data
class EmailSenderConfig {
private boolean enable;
private String displayName;
private String username;
private String password;
private String host;
private Integer port;
private String encryption;

/**
* Gets email display name.
*
* @return display name if not blank, otherwise username.
*/
public String getDisplayName() {
return StringUtils.defaultIfBlank(displayName, username);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package run.halo.app.notification;

import java.nio.charset.StandardCharsets;
import java.util.Properties;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Component;

/**
* <p>A default implementation of {@link EmailSenderHelper}.</p>
*
* @author guqing
* @since 2.14.0
*/
@Slf4j
@Component
public class EmailSenderHelperImpl implements EmailSenderHelper {

@Override
@NonNull
public JavaMailSender createJavaMailSender(EmailSenderConfig senderConfig) {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(senderConfig.getHost());
javaMailSender.setPort(senderConfig.getPort());
javaMailSender.setUsername(senderConfig.getUsername());
javaMailSender.setPassword(senderConfig.getPassword());

Properties props = javaMailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
if ("SSL".equals(senderConfig.getEncryption())) {
props.put("mail.smtp.ssl.enable", "true");
}

if ("TLS".equals(senderConfig.getEncryption())) {
props.put("mail.smtp.starttls.enable", "true");
}

if ("NONE".equals(senderConfig.getEncryption())) {
props.put("mail.smtp.ssl.enable", "false");
props.put("mail.smtp.starttls.enable", "false");
}

if (log.isDebugEnabled()) {
props.put("mail.debug", "true");
}
return javaMailSender;
}

@Override
@NonNull
public MimeMessagePreparator createMimeMessagePreparator(EmailSenderConfig senderConfig,
String toEmail, String subject, String raw, String html) {
return mimeMessage -> {
MimeMessageHelper helper =
new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
helper.setFrom(senderConfig.getUsername(), senderConfig.getDisplayName());

helper.setSubject(subject);
helper.setText(raw, html);
helper.setTo(toEmail);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package run.halo.app.notification.endpoint;

import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;

import io.swagger.v3.oas.annotations.media.Schema;
import java.security.Principal;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
import org.springframework.mail.MailException;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebInputException;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.User;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.extension.GroupVersion;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.notification.EmailSenderHelper;

/**
* Validation endpoint for email config.
*
* @author guqing
* @since 2.14.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class EmailConfigValidationEndpoint implements CustomEndpoint {
private static final String EMAIL_SUBJECT = "测试邮件 - 验证邮箱连通性";
private static final String EMAIL_BODY = """
你好!
这是一封测试邮件,旨在验证您的邮箱发件配置是否正确。
此邮件由系统自动发送,请勿回复。
祝好
""";

private final EmailSenderHelper emailSenderHelper;
private final ReactiveExtensionClient client;

@Override
public RouterFunction<ServerResponse> endpoint() {
var tag = "console.api.notification.halo.run/v1alpha1/Notifier";
return SpringdocRouteBuilder.route()
.POST("/notifiers/default-email-notifier/validation", this::validateEmailConfig,
builder -> builder.operationId("ValidateEmailConfig")
.description("Validate email config")
.tag(tag)
.requestBody(requestBodyBuilder()
.required(true)
.implementation(ValidationRequest.class)
)
.response(responseBuilder().implementation(Void.class))
)
.build();
}

private Mono<ServerResponse> validateEmailConfig(ServerRequest request) {
return request.bodyToMono(ValidationRequest.class)
.switchIfEmpty(
Mono.error(new ServerWebInputException("Required request body is missing."))
)
.flatMap(validationRequest -> getCurrentUserEmail()
.flatMap(recipient -> {
var mailSender = emailSenderHelper.createJavaMailSender(validationRequest);
var message = emailSenderHelper.createMimeMessagePreparator(validationRequest,
recipient, EMAIL_SUBJECT, EMAIL_BODY, EMAIL_BODY);
try {
mailSender.send(message);
} catch (MailException e) {
String errorMsg =
"Failed to send email, please check your email configuration.";
log.error(errorMsg, e);
throw new ServerWebInputException(errorMsg, null, e);
}
return ServerResponse.ok().build();
})
);
}

Mono<String> getCurrentUserEmail() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Principal::getName)
.flatMap(username -> client.fetch(User.class, username))
.map(user -> user.getSpec().getEmail())
.defaultIfEmpty(StringUtils.EMPTY)
.doOnNext(email -> {
if (StringUtils.isBlank(email)) {
throw new ServerWebInputException(
"Your email is missing, please set it in your profile.");
}
});
}

@Data
@EqualsAndHashCode(callSuper = true)
@Schema(name = "EmailConfigValidationRequest")
static class ValidationRequest extends EmailSenderHelper.EmailSenderConfig {
}

@Override
public GroupVersion groupVersion() {
return GroupVersion.parseAPIVersion("console.api.notification.halo.run/v1alpha1");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ spec:
name: enable
- $formkit: verifyForm
if: "$enable"
actions: http://localhost:8090/
actions: /apis/console.api.notification.halo.run/v1alpha1/notifiers/default-email-notifier/validation
label: 测试邮箱
children:
- $formkit: text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ rules:
- apiGroups: [ "api.console.halo.run" ]
resources: [ "notifiers/sender-config" ]
verbs: [ "get", "update" ]
- apiGroups: [ "console.api.notification.halo.run" ]
resources: [ "notifiers/validation" ]
verbs: [ "default-email-notifier" ]

0 comments on commit 22e0c63

Please sign in to comment.