fix platform security and SMTP boundaries
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package com.lab.labtimesheet.config;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.lab.labtimesheet.feature.account.service.BootstrapService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@Import(TestcontainersConfiguration.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
class SecurityResponseIntegrationTest {
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private BootstrapService bootstrap;
|
||||
|
||||
@Test
|
||||
void assetsRemainPublicBeforeAndAfterBootstrap() throws Exception {
|
||||
mockMvc.perform(get("/assets/review-test.css").with(anonymous()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("asset")));
|
||||
|
||||
bootstrap.bootstrap("admin@example.com", "Admin", "correct horse battery staple");
|
||||
|
||||
mockMvc.perform(get("/assets/review-test.css").with(anonymous()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("asset")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationAndActivationResponsesDoNotSendReferrers() throws Exception {
|
||||
bootstrap.bootstrap("admin@example.com", "Admin", "correct horse battery staple");
|
||||
|
||||
mockMvc.perform(get("/login"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("Referrer-Policy", "no-referrer"));
|
||||
mockMvc.perform(get("/activate").param("token", "non-secret-test-fixture"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("Referrer-Policy", "no-referrer"));
|
||||
}
|
||||
}
|
||||
+21
@@ -33,11 +33,13 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
@Import({TestcontainersConfiguration.class, AccountActivationIntegrationTest.MailProbeConfiguration.class})
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
class AccountActivationIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
@@ -138,6 +140,25 @@ class AccountActivationIntegrationTest {
|
||||
assertThat(summary.activeInternships()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void internshipCannotActivateBeforeItsBusinessStartDate() {
|
||||
bootstrap.bootstrap("admin@example.com", "Admin", "correct horse battery staple");
|
||||
long adminId = accounts.requireActiveAdminId("admin@example.com");
|
||||
activateSmtp(adminId);
|
||||
mail.messages.clear();
|
||||
|
||||
var creation = accounts.create(new CreateAccountCommand(
|
||||
"future-intern@example.com", "Future Intern", GlobalRole.INTERN, "STU-FUTURE",
|
||||
LocalDate.of(2026, 8, 15), LocalDate.of(2026, 12, 31)), adminId);
|
||||
assertThat(accounts.activate(mail.onlyActivationToken(), "future secure password")).isTrue();
|
||||
|
||||
assertThatThrownBy(() -> accounts.activateInternship(creation.userId(), adminId))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("start date");
|
||||
assertThat(internProfiles.findById(creation.userId()).orElseThrow().getInternshipStatus())
|
||||
.isEqualTo(InternshipStatus.NOT_STARTED);
|
||||
}
|
||||
|
||||
private void activateSmtp(long adminId) {
|
||||
long draftId = smtp.saveDraft(adminId, new SmtpDraft(
|
||||
"mailpit", 1025, SecurityMode.NONE, null, null, "admin@example.com", "Lab Timesheet"));
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.lab.labtimesheet.feature.integration.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.lab.labtimesheet.feature.integration.model.SecurityMode;
|
||||
import com.lab.labtimesheet.feature.integration.model.dto.SmtpConnection;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
|
||||
class JavaMailSmtpProbeTest {
|
||||
@Test
|
||||
void appliesFiniteTimeoutsAndConfiguredFromName() throws Exception {
|
||||
var sender = new CapturingMailSender();
|
||||
var probe = new JavaMailSmtpProbe(() -> sender);
|
||||
var connection = new SmtpConnection(
|
||||
"smtp.example.com", 587, SecurityMode.STARTTLS, "user", "password",
|
||||
"noreply@example.com", "Lab Timesheet");
|
||||
|
||||
probe.send(connection, "admin@example.com", "Subject", "Body");
|
||||
|
||||
assertThat(sender.getJavaMailProperties())
|
||||
.containsEntry("mail.smtp.connectiontimeout", "5000")
|
||||
.containsEntry("mail.smtp.timeout", "5000")
|
||||
.containsEntry("mail.smtp.writetimeout", "5000");
|
||||
var from = (InternetAddress) sender.message.getFrom()[0];
|
||||
assertThat(from.getAddress()).isEqualTo("noreply@example.com");
|
||||
assertThat(from.getPersonal()).isEqualTo("Lab Timesheet");
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesFiniteTimeoutsToImplicitTlsTransport() {
|
||||
var sender = new CapturingMailSender();
|
||||
var probe = new JavaMailSmtpProbe(() -> sender);
|
||||
var connection = new SmtpConnection(
|
||||
"smtp.example.com", 465, SecurityMode.TLS, null, null,
|
||||
"noreply@example.com", "Lab Timesheet");
|
||||
|
||||
probe.send(connection, "admin@example.com", "Subject", "Body");
|
||||
|
||||
assertThat(sender.getProtocol()).isEqualTo("smtps");
|
||||
assertThat(sender.getJavaMailProperties())
|
||||
.containsEntry("mail.smtps.connectiontimeout", "5000")
|
||||
.containsEntry("mail.smtps.timeout", "5000")
|
||||
.containsEntry("mail.smtps.writetimeout", "5000");
|
||||
}
|
||||
|
||||
static final class CapturingMailSender extends JavaMailSenderImpl {
|
||||
private MimeMessage message;
|
||||
|
||||
@Override
|
||||
public void send(MimeMessage... mimeMessages) {
|
||||
assertThat(mimeMessages).hasSize(1);
|
||||
message = mimeMessages[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
asset
|
||||
Reference in New Issue
Block a user