Compare commits
2 Commits
main
...
email-serv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7702335893 | ||
|
|
3c733ddc8c |
@ -100,6 +100,8 @@ dependencies {
|
||||
implementation("commons-validator:commons-validator:1.7")
|
||||
implementation("org.springframework.boot:spring-boot-configuration-processor")
|
||||
implementation("com.iqser.red.commons:storage-commons:2.22.0")
|
||||
implementation("jakarta.mail:jakarta.mail-api:2.1.2")
|
||||
implementation("org.eclipse.angus:angus-mail:2.0.2")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.cloud:spring-cloud-starter-openfeign")
|
||||
testImplementation("org.projectlombok:lombok")
|
||||
@ -111,8 +113,8 @@ dependencies {
|
||||
testImplementation("org.springframework.amqp:spring-rabbit-test")
|
||||
testImplementation("org.testcontainers:postgresql:1.18.3")
|
||||
testImplementation("com.github.dasniko:testcontainers-keycloak:2.5.0")
|
||||
testImplementation("org.testcontainers:testcontainers:1.18.3")
|
||||
testImplementation("org.testcontainers:junit-jupiter:1.18.3")
|
||||
testImplementation("org.testcontainers:testcontainers:1.19.0")
|
||||
testImplementation("org.testcontainers:junit-jupiter:1.19.0")
|
||||
testAnnotationProcessor("org.projectlombok:lombok")
|
||||
|
||||
}
|
||||
|
||||
@ -6,11 +6,12 @@ import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPResponse;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@ -22,6 +23,7 @@ public interface SMTPConfigurationResource {
|
||||
|
||||
String TEST_PATH = "/test";
|
||||
|
||||
String TEST_EMAIL = "testEmail";
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ -42,7 +44,7 @@ public interface SMTPConfigurationResource {
|
||||
@PostMapping(value = SMTP_PATH + TEST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Test SMTP Settings to KeyCloak")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "SMTP Configuration is valid."), @ApiResponse(responseCode = "400", description = "SMTP test failed.")})
|
||||
void testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel);
|
||||
SMTPResponse testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel, @RequestParam(value = TEST_EMAIL, required = false) String testEmail);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
|
||||
@ -6,8 +6,10 @@ import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagem
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@ -16,6 +18,8 @@ import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.SMTPConfigurationResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.EmailService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -28,9 +32,12 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class SMTPConfigurationController implements SMTPConfigurationResource, PublicResource {
|
||||
|
||||
private final static String SMTP_PASSWORD_KEY = "FFORESIGHT_SMTP_PASSWORD";
|
||||
private final static String DEFAULT_PASSWORD = "**********";
|
||||
|
||||
private final RealmService realmService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final EncryptionDecryptionService encryptionDecryptionService;
|
||||
private final EmailService emailService;
|
||||
|
||||
|
||||
@Override
|
||||
@ -59,28 +66,23 @@ public class SMTPConfigurationController implements SMTPConfigurationResource, P
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> convertSMTPConfigurationModelToMap(SMTPConfiguration smtpConfigurationModel) {
|
||||
|
||||
Map<String, Object> propertiesMap = objectMapper.convertValue(smtpConfigurationModel, Map.class);
|
||||
Map<String, String> stringPropertiesMap = new HashMap<>();
|
||||
propertiesMap.forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
stringPropertiesMap.put(key, value.toString());
|
||||
} else {
|
||||
stringPropertiesMap.put(key, "");
|
||||
}
|
||||
});
|
||||
return stringPropertiesMap;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
|
||||
public void testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel) {
|
||||
public SMTPResponse testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfiguration, @RequestParam(value = TEST_EMAIL, required = false) String testEmail) {
|
||||
|
||||
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
|
||||
realmService.realm(TenantContext.getTenantId()).testSMTPConnection(propertiesMap);
|
||||
String targetEmail;
|
||||
if (StringUtils.isBlank(testEmail)) {
|
||||
// will send e-mail to self in case testEmail is not set
|
||||
targetEmail = smtpConfiguration.getFrom();
|
||||
} else {
|
||||
targetEmail = testEmail;
|
||||
}
|
||||
|
||||
updatePassword(smtpConfiguration);
|
||||
smtpConfiguration.setPassword(encryptionDecryptionService.decrypt(smtpConfiguration.getPassword()));
|
||||
|
||||
return emailService.send(convertSMTPConfigurationModelToMap(smtpConfiguration), targetEmail, "Redaction Test message", "This is a test message");
|
||||
}
|
||||
|
||||
|
||||
@ -95,4 +97,32 @@ public class SMTPConfigurationController implements SMTPConfigurationResource, P
|
||||
|
||||
}
|
||||
|
||||
private Map<String, String> convertSMTPConfigurationModelToMap(SMTPConfiguration smtpConfigurationModel) {
|
||||
|
||||
Map<String, Object> propertiesMap = objectMapper.convertValue(smtpConfigurationModel, Map.class);
|
||||
Map<String, String> stringPropertiesMap = new HashMap<>();
|
||||
propertiesMap.forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
stringPropertiesMap.put(key, value.toString());
|
||||
} else {
|
||||
stringPropertiesMap.put(key, "");
|
||||
}
|
||||
});
|
||||
return stringPropertiesMap;
|
||||
}
|
||||
|
||||
private void updatePassword(SMTPConfiguration smtpConfiguration) {
|
||||
|
||||
if (DEFAULT_PASSWORD.equals(smtpConfiguration.getPassword())) {
|
||||
try {
|
||||
var password = realmService.realm(TenantContext.getTenantId()).toRepresentation().getAttributesOrEmpty().get(SMTP_PASSWORD_KEY);
|
||||
smtpConfiguration.setPassword(password);
|
||||
} catch (Exception e) {
|
||||
log.info("No current SMTP Config exists", e);
|
||||
}
|
||||
} else {
|
||||
smtpConfiguration.setPassword(encryptionDecryptionService.encrypt(smtpConfiguration.getPassword()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing a simplified version of the SMTP test connection response.")
|
||||
public class SMTPResponse {
|
||||
|
||||
@Schema(description = "Parameter containing status code of the response.")
|
||||
private int statusCode;
|
||||
@Schema(description = "Parameter containing the reason phrase of the response.")
|
||||
private String reasonPhrase;
|
||||
|
||||
}
|
||||
@ -0,0 +1,169 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.ws.rs.BadRequestException;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPResponse;
|
||||
|
||||
import jakarta.mail.Address;
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.Multipart;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.Transport;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeBodyPart;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import jakarta.mail.internet.MimeMultipart;
|
||||
import jakarta.mail.internet.MimeUtility;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EmailService {
|
||||
|
||||
|
||||
public SMTPResponse send(Map<String, String> config, String address, String subject, String textBody) {
|
||||
|
||||
Transport transport = null;
|
||||
try {
|
||||
|
||||
Properties props = new Properties();
|
||||
|
||||
if (config.containsKey("host")) {
|
||||
props.setProperty("mail.smtp.host", config.get("host"));
|
||||
}
|
||||
|
||||
boolean auth = "true".equals(config.get("auth"));
|
||||
boolean ssl = "true".equals(config.get("ssl"));
|
||||
boolean starttls = "true".equals(config.get("starttls"));
|
||||
|
||||
if (config.containsKey("port") && config.get("port") != null) {
|
||||
props.setProperty("mail.smtp.port", config.get("port"));
|
||||
}
|
||||
|
||||
if (auth) {
|
||||
props.setProperty("mail.smtp.auth", "true");
|
||||
}
|
||||
|
||||
if (ssl) {
|
||||
props.setProperty("mail.smtp.ssl.enable", "true");
|
||||
}
|
||||
|
||||
if (starttls) {
|
||||
props.setProperty("mail.smtp.starttls.enable", "true");
|
||||
}
|
||||
|
||||
if (ssl || starttls) {
|
||||
props.put("mail.smtp.ssl.protocols", getSupportedSslProtocols());
|
||||
}
|
||||
|
||||
props.setProperty("mail.smtp.timeout", "10000");
|
||||
props.setProperty("mail.smtp.connectiontimeout", "10000");
|
||||
|
||||
String from = config.get("from");
|
||||
String fromDisplayName = config.get("fromDisplayName");
|
||||
String replyTo = config.get("replyTo");
|
||||
String replyToDisplayName = config.get("replyToDisplayName");
|
||||
String envelopeFrom = config.get("envelopeFrom");
|
||||
|
||||
Session session = Session.getInstance(props);
|
||||
|
||||
Multipart multipart = new MimeMultipart("alternative");
|
||||
|
||||
MimeBodyPart textPart = new MimeBodyPart();
|
||||
textPart.setText(textBody, "UTF-8");
|
||||
multipart.addBodyPart(textPart);
|
||||
|
||||
Message msg = new MimeMessage(session);
|
||||
msg.setFrom(toInternetAddress(from, fromDisplayName));
|
||||
|
||||
msg.setReplyTo(new Address[]{toInternetAddress(from, fromDisplayName)});
|
||||
|
||||
if (isNotBlank(replyTo)) {
|
||||
msg.setReplyTo(new Address[]{toInternetAddress(replyTo, replyToDisplayName)});
|
||||
}
|
||||
|
||||
if (isNotBlank(envelopeFrom)) {
|
||||
props.setProperty("mail.smtp.from", envelopeFrom);
|
||||
}
|
||||
|
||||
msg.setHeader("To", address);
|
||||
msg.setSubject(MimeUtility.encodeText(subject, StandardCharsets.UTF_8.name(), null));
|
||||
msg.setContent(multipart);
|
||||
msg.saveChanges();
|
||||
msg.setSentDate(new Date());
|
||||
|
||||
transport = session.getTransport("smtp");
|
||||
|
||||
if (auth) {
|
||||
transport.connect(config.get("user"), config.get("password"));
|
||||
} else {
|
||||
transport.connect();
|
||||
}
|
||||
|
||||
transport.sendMessage(msg, new InternetAddress[]{new InternetAddress(address)});
|
||||
|
||||
return SMTPResponse.builder()
|
||||
.statusCode(200)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
return SMTPResponse.builder()
|
||||
.statusCode(400)
|
||||
.reasonPhrase(e.getMessage())
|
||||
.build();
|
||||
} finally {
|
||||
if (transport != null) {
|
||||
try {
|
||||
transport.close();
|
||||
} catch (MessagingException e) {
|
||||
log.warn("Failed to close transport", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected InternetAddress toInternetAddress(String email, String displayName) throws UnsupportedEncodingException, AddressException, BadRequestException {
|
||||
|
||||
if (email == null || "".equals(email.trim())) {
|
||||
throw new BadRequestException("Please provide a valid address");
|
||||
}
|
||||
|
||||
if (displayName == null || "".equals(displayName.trim())) {
|
||||
return new InternetAddress(email);
|
||||
}
|
||||
|
||||
return new InternetAddress(email, displayName, "utf-8");
|
||||
}
|
||||
|
||||
|
||||
private String getSupportedSslProtocols() {
|
||||
|
||||
try {
|
||||
String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
|
||||
if (protocols != null) {
|
||||
return String.join(" ", protocols);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to get list of supported SSL protocols", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isNotBlank(String str) {
|
||||
|
||||
return str != null && !"".equals(str.trim());
|
||||
}
|
||||
|
||||
}
|
||||
@ -27,14 +27,7 @@ public class SMTPConfigurationTest extends AbstractTenantUserManagementIntegrati
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
|
||||
SMTPConfiguration newConfig = new SMTPConfiguration();
|
||||
newConfig.setAuth(true);
|
||||
newConfig.setFrom("from@knecon.com");
|
||||
newConfig.setHost("test.knecon.com");
|
||||
newConfig.setPassword("secret");
|
||||
newConfig.setUser("user");
|
||||
newConfig.setStarttls(true);
|
||||
newConfig.setSsl(false);
|
||||
SMTPConfiguration newConfig = provideTestSMTPConfiguration();
|
||||
smtpConfigurationClient.updateSMTPConfiguration(newConfig);
|
||||
|
||||
var currentSMTPConfiguration = smtpConfigurationClient.getCurrentSMTPConfiguration();
|
||||
@ -50,4 +43,34 @@ public class SMTPConfigurationTest extends AbstractTenantUserManagementIntegrati
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSMTPConnection() {
|
||||
|
||||
TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID);
|
||||
|
||||
SMTPConfiguration smtpConfiguration = provideTestSMTPConfiguration();
|
||||
|
||||
var response = smtpConfigurationClient.testSMTPConfiguration(smtpConfiguration, "");
|
||||
|
||||
// Fails because we are not using a smtp config
|
||||
assertThat(response.getStatusCode()).isEqualTo(400);
|
||||
assertThat(response.getReasonPhrase()).isEqualTo("Couldn't connect to host, port: test.knecon.com, 25; timeout 10000");
|
||||
|
||||
TenantContext.clear();
|
||||
}
|
||||
|
||||
private SMTPConfiguration provideTestSMTPConfiguration() {
|
||||
|
||||
SMTPConfiguration smtpConfiguration = new SMTPConfiguration();
|
||||
smtpConfiguration.setAuth(true);
|
||||
smtpConfiguration.setFrom("from@knecon.com");
|
||||
smtpConfiguration.setHost("test.knecon.com");
|
||||
smtpConfiguration.setPassword("secret");
|
||||
smtpConfiguration.setUser("user");
|
||||
smtpConfiguration.setStarttls(true);
|
||||
smtpConfiguration.setSsl(false);
|
||||
|
||||
return smtpConfiguration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user