fix platform security and SMTP boundaries
This commit is contained in:
@@ -9,6 +9,7 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class SecurityConfiguration {
|
||||
@@ -27,10 +28,13 @@ class SecurityConfiguration {
|
||||
throws Exception {
|
||||
return http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/bootstrap/**", "/activate/**", "/login", "/error", "/actuator/health")
|
||||
.requestMatchers(
|
||||
"/bootstrap/**", "/activate/**", "/login", "/error", "/assets/**",
|
||||
"/actuator/health")
|
||||
.permitAll()
|
||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||
.anyRequest().authenticated())
|
||||
.headers(headers -> headers.referrerPolicy(policy -> policy.policy(ReferrerPolicy.NO_REFERRER)))
|
||||
.formLogin(form -> form.loginPage("/login").defaultSuccessUrl("/", true))
|
||||
.logout(logout -> logout.logoutSuccessUrl("/login?logout"))
|
||||
.addFilterBefore(bootstrapAccessFilter, AuthorizationFilter.class)
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ public class BootstrapAccessFilter extends OncePerRequestFilter {
|
||||
|
||||
private static boolean allowedBeforeBootstrap(String path) {
|
||||
return path.equals("/bootstrap") || path.startsWith("/bootstrap/")
|
||||
|| path.equals("/actuator/health") || path.startsWith("/bootstrap-assets/")
|
||||
|| path.equals("/actuator/health") || path.startsWith("/assets/")
|
||||
|| path.equals("/error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,14 @@ public class AccountService {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an active Intern's internship from {@code NOT_STARTED} to {@code ACTIVE} once the configured
|
||||
* internship start date has arrived in the application's business timezone.
|
||||
*
|
||||
* @param internUserId Intern account whose internship should start
|
||||
* @param adminId active Admin authorizing the state transition
|
||||
* @throws IllegalStateException when the internship start date has not arrived
|
||||
*/
|
||||
@Transactional
|
||||
public void activateInternship(long internUserId, long adminId) {
|
||||
AppUser admin = users.findById(adminId)
|
||||
@@ -124,9 +132,12 @@ public class AccountService {
|
||||
if (intern.getGlobalRole() != GlobalRole.INTERN || intern.getAccountStatus() != AccountStatus.ACTIVE) {
|
||||
throw new IllegalArgumentException("An active Intern account is required");
|
||||
}
|
||||
internProfiles.findForUpdateByUserId(internUserId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Intern profile not found"))
|
||||
.activate(clock.instant());
|
||||
InternProfile profile = internProfiles.findForUpdateByUserId(internUserId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Intern profile not found"));
|
||||
if (LocalDate.now(clock).isBefore(profile.getInternshipStartDate())) {
|
||||
throw new IllegalStateException("Internship cannot activate before its start date");
|
||||
}
|
||||
profile.activate(clock.instant());
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
|
||||
+39
-7
@@ -1,18 +1,40 @@
|
||||
package com.lab.labtimesheet.feature.integration.service;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.lab.labtimesheet.feature.integration.model.dto.SmtpConnection;
|
||||
import com.lab.labtimesheet.feature.integration.model.SecurityMode;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Sends immediate SMTP messages through a freshly configured JavaMail client.
|
||||
* Connections are bounded by finite network timeouts so an Admin test or
|
||||
* activation delivery cannot block a request indefinitely.
|
||||
*/
|
||||
@Component
|
||||
class JavaMailSmtpProbe implements SmtpProbe {
|
||||
private static final String TIMEOUT_MILLIS = "5000";
|
||||
|
||||
private final Supplier<JavaMailSenderImpl> senderFactory;
|
||||
|
||||
JavaMailSmtpProbe() {
|
||||
this(JavaMailSenderImpl::new);
|
||||
}
|
||||
|
||||
JavaMailSmtpProbe(Supplier<JavaMailSenderImpl> senderFactory) {
|
||||
this.senderFactory = senderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(SmtpConnection connection, String recipient, String subject, String body) {
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
JavaMailSenderImpl sender = senderFactory.get();
|
||||
sender.setHost(connection.host());
|
||||
sender.setPort(connection.port());
|
||||
sender.setUsername(connection.username());
|
||||
@@ -24,11 +46,21 @@ class JavaMailSmtpProbe implements SmtpProbe {
|
||||
} else if (connection.securityMode() == SecurityMode.TLS) {
|
||||
sender.setProtocol("smtps");
|
||||
}
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(connection.fromAddress());
|
||||
message.setTo(recipient);
|
||||
message.setSubject(subject);
|
||||
message.setText(body);
|
||||
String propertyPrefix = connection.securityMode() == SecurityMode.TLS ? "mail.smtps" : "mail.smtp";
|
||||
properties.setProperty(propertyPrefix + ".connectiontimeout", TIMEOUT_MILLIS);
|
||||
properties.setProperty(propertyPrefix + ".timeout", TIMEOUT_MILLIS);
|
||||
properties.setProperty(propertyPrefix + ".writetimeout", TIMEOUT_MILLIS);
|
||||
|
||||
MimeMessage message = sender.createMimeMessage();
|
||||
try {
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, false, StandardCharsets.UTF_8.name());
|
||||
helper.setFrom(connection.fromAddress(), connection.fromName());
|
||||
helper.setTo(recipient);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(body);
|
||||
} catch (MessagingException | UnsupportedEncodingException exception) {
|
||||
throw new IllegalStateException("Unable to construct SMTP message", exception);
|
||||
}
|
||||
sender.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user