#130 upgraded Spring Boot to 4.0.6

This commit is contained in:
Fabio Formosa
2026-05-09 11:48:24 +02:00
parent e90648c027
commit 1e48e1803f
50 changed files with 302 additions and 236 deletions

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<version>4.0.6</version>
</parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
@@ -41,17 +41,17 @@
</developers>
<properties>
<java.version>9</java.version>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.projectlombok.version>1.18.30</org.projectlombok.version>
<maven-surefire-plugin.version>2.22.0</maven-surefire-plugin.version>
<maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version>
<jacoco-maven-plugin.version>0.8.8</jacoco-maven-plugin.version>
<maven-javadoc-plugin.version>3.4.1</maven-javadoc-plugin.version>
<org.projectlombok.version>1.18.42</org.projectlombok.version>
<maven-surefire-plugin.version>3.5.4</maven-surefire-plugin.version>
<maven-failsafe-plugin.version>3.5.4</maven-failsafe-plugin.version>
<jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version>
<maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>
<nexus-staging-maven-plugin.version>1.6.7</nexus-staging-maven-plugin.version>
<maven-release-plugin.version>2.5.3</maven-release-plugin.version>
<maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version>
<sonar-maven-plugin.version>3.11.0.3922</sonar-maven-plugin.version>
<sonar-maven-plugin.version>5.2.0.4988</sonar-maven-plugin.version>
<sonar.organization>fabioformosa</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.exclusions>
@@ -111,12 +111,11 @@
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -125,9 +124,15 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<release>${java.version}</release>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>

View File

@@ -18,8 +18,8 @@
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<springdoc-openapi.version>1.5.12</springdoc-openapi.version>
<java.version>9</java.version>
<springdoc-openapi.version>3.0.3</springdoc-openapi.version>
<java.version>17</java.version>
<sonar.exclusions>**/QuartManagerApplicationTests.java, **/OpenApiConfig.java</sonar.exclusions>
</properties>
@@ -61,6 +61,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MISC -->
<dependency>
@@ -86,21 +91,6 @@
<artifactId>metamorphosis-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
@@ -125,15 +115,10 @@
<!-- OAS -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-common</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<!-- TEST -->
<dependency>

View File

@@ -6,8 +6,8 @@ import io.swagger.v3.oas.models.info.License;
import it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths;
import lombok.Generated;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -39,10 +39,10 @@ public class OpenApiConfig {
@ConditionalOnProperty(name = "quartz-manager.oas.enabled")
@Bean
public GroupedOpenApi quartzManagerStoreOpenApi(@Autowired(required = false) @Qualifier("quartzManagerOpenApiCustomiser") Optional<OpenApiCustomiser> openApiCustomiser) {
public GroupedOpenApi quartzManagerStoreOpenApi(@Autowired(required = false) @Qualifier("quartzManagerOpenApiCustomizer") Optional<OpenApiCustomizer> openApiCustomizer) {
String[] paths = {QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/**"};
GroupedOpenApi.Builder groupedOpenApiBuilder = GroupedOpenApi.builder().group("quartz-manager").pathsToMatch(paths);
openApiCustomiser.ifPresent(groupedOpenApiBuilder::addOpenApiCustomiser);
openApiCustomizer.ifPresent(groupedOpenApiBuilder::addOpenApiCustomizer);
return groupedOpenApiBuilder.build();
}

View File

@@ -1,9 +1,38 @@
package it.fabioformosa.quartzmanager.api.configuration;
import it.fabioformosa.metamorphosis.core.converters.AbstractBaseConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import java.lang.reflect.Field;
import java.util.List;
@ComponentScan(basePackages = {"it.fabioformosa.quartzmanager.api"})
@Configuration
public class QuartzManagerApiConfig {
@Bean
public ConversionService conversionService(List<Converter<?, ?>> converters) {
DefaultConversionService conversionService = new DefaultConversionService();
converters.forEach(conversionService::addConverter);
converters.stream()
.filter(AbstractBaseConverter.class::isInstance)
.map(AbstractBaseConverter.class::cast)
.forEach(converter -> setConversionService(converter, conversionService));
return conversionService;
}
private void setConversionService(AbstractBaseConverter<?, ?> converter, ConversionService conversionService) {
try {
Field conversionServiceField = AbstractBaseConverter.class.getDeclaredField("conversionService");
conversionServiceField.setAccessible(true);
conversionServiceField.set(converter, conversionService);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException("Unable to initialize Quartz Manager converters", e);
}
}
}

View File

@@ -4,14 +4,14 @@ import it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@ComponentScan(basePackages = {"it.fabioformosa.quartzmanager.api.websockets"})
@EnableWebSocketMessageBroker
public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {

View File

@@ -19,7 +19,7 @@ import org.quartz.SchedulerException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import jakarta.validation.Valid;
@Slf4j
@RequestMapping(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL)

View File

@@ -8,15 +8,23 @@ import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
import org.quartz.JobKey;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
@Component
public class TriggerToTriggerDTO<S extends Trigger, T extends TriggerDTO> extends AbstractBaseConverter<S, T> {
public abstract class TriggerToTriggerDTO<S extends Trigger, T extends TriggerDTO> extends AbstractBaseConverter<S, T> {
@Autowired
private TriggerKeyToTriggerKeyDTO triggerKeyToTriggerKeyDTO;
@Autowired
private JobKeyToJobKeyDTO jobKeyToJobKeyDTO;
@Autowired
private JobKeyToJobDetailDTO jobKeyToJobDetailDTO;
@Override
protected void convert(S source, T target) {
TriggerKey triggerKey = source.getKey();
TriggerKeyDTO triggerKeyDTO = conversionService.convert(triggerKey, TriggerKeyDTO.class);
TriggerKeyDTO triggerKeyDTO = triggerKeyToTriggerKeyDTO.convert(triggerKey);
target.setTriggerKeyDTO(triggerKeyDTO);
target.setStartTime(source.getStartTime());
@@ -29,16 +37,15 @@ public class TriggerToTriggerDTO<S extends Trigger, T extends TriggerDTO> extend
target.setMayFireAgain(source.mayFireAgain());
JobKey jobKey = source.getJobKey();
JobKeyDTO jobKeyDTO = conversionService.convert(jobKey, JobKeyDTO.class);
if (jobKey == null) {
return;
}
JobKeyDTO jobKeyDTO = jobKeyToJobKeyDTO.convert(jobKey);
target.setJobKeyDTO(jobKeyDTO);
JobDetailDTO jobDetailDTO = conversionService.convert(jobKey, JobDetailDTO.class);
JobDetailDTO jobDetailDTO = jobKeyToJobDetailDTO.convert(jobKey);
target.setJobDetailDTO(jobDetailDTO);
}
@Override
protected T createOrRetrieveTarget(S source) {
return (T) new TriggerDTO();
}
}

View File

@@ -3,8 +3,8 @@ package it.fabioformosa.quartzmanager.api.dto;
import it.fabioformosa.quartzmanager.api.validators.ValidTriggerRepetition;
import lombok.*;
import lombok.experimental.SuperBuilder;
import javax.annotation.Nullable;
import javax.validation.constraints.Positive;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.Positive;
import java.util.Map;
@ValidTriggerRepetition

View File

@@ -5,7 +5,7 @@ import it.fabioformosa.quartzmanager.api.validators.ValidTriggerPeriod;
import lombok.*;
import lombok.experimental.SuperBuilder;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.util.Date;
@ValidTriggerPeriod

View File

@@ -9,7 +9,7 @@ import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
/**
* Extends this class to create a job that produces LogRecord to be displayed

View File

@@ -8,7 +8,7 @@ import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import jakarta.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;

View File

@@ -21,7 +21,7 @@ public class SimpleTriggerService extends AbstractSchedulerService {
}
public SimpleTriggerDTO scheduleSimpleTrigger(SimpleTriggerCommandDTO simpleTriggerCommandDTO) throws SchedulerException, ClassNotFoundException {
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(simpleTriggerCommandDTO.getSimpleTriggerInputDTO().getJobClass());
Class<? extends Job> jobClass = Class.forName(simpleTriggerCommandDTO.getSimpleTriggerInputDTO().getJobClass()).asSubclass(Job.class);
JobDetail jobDetail = JobBuilder.newJob()
.ofType(jobClass)
.storeDurably(false)

View File

@@ -7,7 +7,6 @@ import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -26,9 +25,9 @@ public class TriggerService {
public List<TriggerKeyDTO> fetchTriggers() throws SchedulerException {
Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.anyTriggerGroup());
return (List<TriggerKeyDTO>) conversionService.convert(triggerKeys,
TypeDescriptor.collection(Set.class, TypeDescriptor.valueOf(TriggerKey.class)),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(TriggerKeyDTO.class)));
return triggerKeys.stream()
.map(triggerKey -> conversionService.convert(triggerKey, TriggerKeyDTO.class))
.toList();
}
}

View File

@@ -2,8 +2,8 @@ package it.fabioformosa.quartzmanager.api.validators;
import it.fabioformosa.quartzmanager.api.dto.TriggerRepetitionDTO;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class ValidRepetitionValidator implements ConstraintValidator<ValidTriggerRepetition, TriggerRepetitionDTO> {

View File

@@ -1,7 +1,7 @@
package it.fabioformosa.quartzmanager.api.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

View File

@@ -2,8 +2,8 @@ package it.fabioformosa.quartzmanager.api.validators;
import it.fabioformosa.quartzmanager.api.dto.TriggerPeriodDTO;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class ValidTriggerPeriodValidator implements ConstraintValidator<ValidTriggerPeriod, TriggerPeriodDTO> {
@Override

View File

@@ -1,7 +1,7 @@
package it.fabioformosa.quartzmanager.api.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

View File

@@ -7,8 +7,8 @@ import it.fabioformosa.quartzmanager.api.services.JobService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -28,7 +28,7 @@ class JobControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
@MockitoBean
private JobService jobService;
@Test

View File

@@ -3,7 +3,7 @@ package it.fabioformosa.quartzmanager.api.controllers;
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;

View File

@@ -8,8 +8,8 @@ import it.fabioformosa.quartzmanager.api.services.SchedulerService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -26,7 +26,7 @@ class SchedulerControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
@MockitoBean
private SchedulerService schedulerService;
@Test

View File

@@ -13,8 +13,8 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -35,7 +35,7 @@ class SimpleTriggerControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
@MockitoBean
private SimpleTriggerService simpleTriggerService;
@AfterEach

View File

@@ -14,8 +14,8 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -35,7 +35,7 @@ class SimpleTriggerControllerValidationTest {
@Autowired
private MockMvc mockMvc;
@MockBean
@MockitoBean
private SimpleTriggerService simpleTriggerService;
@AfterEach

View File

@@ -5,8 +5,8 @@ import it.fabioformosa.quartzmanager.api.services.TriggerService;
import org.junit.jupiter.api.AfterEach;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -19,7 +19,7 @@ class TriggerControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
@MockitoBean
private TriggerService triggerService;
@AfterEach

View File

@@ -5,13 +5,14 @@ import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerInputDTO;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.support.ParameterDeclarations;
import java.util.Date;
import java.util.stream.Stream;
public class InvalidSimpleTriggerCommandDTOProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext extensionContext) {
return Stream.of(
Arguments.of(buildSimpleTriggerWithBlankMandatoryFields()),
Arguments.of(buildSimpleTriggerWithRepeatCountAndWithoutRepeatInterval()),
@@ -22,20 +23,28 @@ public class InvalidSimpleTriggerCommandDTOProvider implements ArgumentsProvider
}
private SimpleTriggerInputDTO buildSimpleTriggerWithNegativeRepeatInterval() {
return minimalSimpleTriggerBuilder().repeatInterval(-2000L).repeatCount(10).build();
SimpleTriggerInputDTO simpleTriggerInputDTO = minimalSimpleTrigger();
simpleTriggerInputDTO.setRepeatInterval(-2000L);
simpleTriggerInputDTO.setRepeatCount(10);
return simpleTriggerInputDTO;
}
private static SimpleTriggerInputDTO buildSimpleTriggerWithRepeatIntervalAndWithoutRepeatCount() {
return minimalSimpleTriggerBuilder().repeatInterval(1L).build();
SimpleTriggerInputDTO simpleTriggerInputDTO = minimalSimpleTrigger();
simpleTriggerInputDTO.setRepeatInterval(1L);
return simpleTriggerInputDTO;
}
private static SimpleTriggerInputDTO.SimpleTriggerInputDTOBuilder<?, ?> minimalSimpleTriggerBuilder() {
return SimpleTriggerInputDTO.builder()
.jobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob");
private static SimpleTriggerInputDTO minimalSimpleTrigger() {
SimpleTriggerInputDTO simpleTriggerInputDTO = new SimpleTriggerInputDTO();
simpleTriggerInputDTO.setJobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob");
return simpleTriggerInputDTO;
}
private static SimpleTriggerInputDTO buildSimpleTriggerWithRepeatCountAndWithoutRepeatInterval() {
return minimalSimpleTriggerBuilder().repeatCount(1).build();
SimpleTriggerInputDTO simpleTriggerInputDTO = minimalSimpleTrigger();
simpleTriggerInputDTO.setRepeatCount(1);
return simpleTriggerInputDTO;
}
private static SimpleTriggerInputDTO buildSimpleTriggerWithBlankMandatoryFields() {
@@ -43,7 +52,10 @@ public class InvalidSimpleTriggerCommandDTOProvider implements ArgumentsProvider
}
private static SimpleTriggerInputDTO buildSimpleTriggerWithInvalidTriggerPeriod() {
return minimalSimpleTriggerBuilder().endDate(new Date()).startDate(DateUtils.addHoursToNow(1)).build();
SimpleTriggerInputDTO simpleTriggerInputDTO = minimalSimpleTrigger();
simpleTriggerInputDTO.setEndDate(new Date());
simpleTriggerInputDTO.setStartDate(DateUtils.addHoursToNow(1));
return simpleTriggerInputDTO;
}
}

View File

@@ -2,7 +2,6 @@ package it.fabioformosa.quartzmanager.api.controllers.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import lombok.SneakyThrows;
public class TestUtils {
@@ -10,12 +9,11 @@ public class TestUtils {
static public ObjectMapper objectMapper = new ObjectMapper();
static{
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true)); // StdDateFormat is ISO8601 since jackson 2.9
}
@SneakyThrows
static public String toJson(Object object){
return objectMapper.writeValueAsString(object);
return objectMapper.writeValueAsString(object).replace("+00:00", "Z");
};
}

View File

@@ -45,7 +45,7 @@ class SampleJobTest {
JobExecutionContext jobExecutionContext = Mockito.mock(JobExecutionContext.class);
String triggerName = "test-trigger";
ScheduleBuilder schedulerBuilder = SimpleScheduleBuilder.simpleSchedule()
ScheduleBuilder<?> schedulerBuilder = SimpleScheduleBuilder.simpleSchedule()
.withRepeatCount(5)
.withIntervalInMilliseconds(1000L);
JobDetail jobDetail = JobBuilder

View File

@@ -16,7 +16,7 @@
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>9</java.version>
<java.version>17</java.version>
</properties>
<dependencies>

View File

@@ -17,8 +17,8 @@
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>9</java.version>
<springdoc-openapi.version>1.5.12</springdoc-openapi.version>
<java.version>17</java.version>
<springdoc-openapi.version>3.0.3</springdoc-openapi.version>
<sonar.exclusions>**/SpringApplicationTest.java</sonar.exclusions>
</properties>
@@ -44,12 +44,29 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- Misc -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
<artifactId>jjwt-api</artifactId>
<version>0.13.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@@ -60,16 +77,11 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- OAS -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
<optional>true</optional>
</dependency>
@@ -100,6 +112,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@@ -7,6 +7,7 @@ import it.fabioformosa.quartzmanager.api.security.helpers.impl.*;
import it.fabioformosa.quartzmanager.api.security.properties.InMemoryAccountProperties;
import it.fabioformosa.quartzmanager.api.security.properties.JwtSecurityProperties;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
@@ -19,10 +20,11 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
@@ -48,7 +50,7 @@ import static it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths
@ComponentScan(basePackages = {"it.fabioformosa.quartzmanager.api.security"})
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableMethodSecurity(prePostEnabled = true)
public class QuartzManagerSecurityConfig {
private static final String[] PATTERNS_SWAGGER_UI = {"/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**"};
@@ -72,6 +74,12 @@ public class QuartzManagerSecurityConfig {
@Autowired
private ObjectMapper objectMapper;
@Bean
@ConditionalOnMissingBean(ObjectMapper.class)
public static ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
@@ -101,17 +109,17 @@ public class QuartzManagerSecurityConfig {
public SecurityFilterChain filterChain(HttpSecurity http,
@Qualifier("quartzManagerInMemoryAuthentication") InMemoryUserDetailsManager userDetailsService,
AuthenticationManager authenticationManager) throws Exception {
http.antMatcher(QUARTZ_MANAGER_API_ANT_MATCHER).csrf().disable() //
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //
.exceptionHandling().authenticationEntryPoint(restAuthEntryPoint()).and() //
.addFilterBefore(jwtAuthenticationTokenFilter(userDetailsService), BasicAuthenticationFilter.class) //
.authorizeRequests();
http.securityMatcher(QUARTZ_MANAGER_API_ANT_MATCHER)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exception -> exception.authenticationEntryPoint(restAuthEntryPoint()))
.addFilterBefore(jwtAuthenticationTokenFilter(userDetailsService), BasicAuthenticationFilter.class);
QuartzManagerHttpSecurity.from(http).withLoginConfigurer(loginConfigurer(), logoutConfigurer()) //
.login(QUARTZ_MANAGER_LOGIN_PATH, authenticationManager).logout(QUARTZ_MANAGER_LOGOUT_PATH);
http.authorizeRequests()
.antMatchers(QUARTZ_MANAGER_API_ANT_MATCHER).authenticated();
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(QUARTZ_MANAGER_API_ANT_MATCHER).authenticated());
return http.build();
}
@@ -119,11 +127,11 @@ public class QuartzManagerSecurityConfig {
@Bean(name = "quartzManagerWebSecurityCustomizer")
public WebSecurityCustomizer webSecurityCustomizer(@Value("${quartz-manager.oas.enabled:false}") Boolean oasEnabled) {
return web -> {
web.ignoring()//
.antMatchers(HttpMethod.GET, QUARTZ_MANAGER_UI_ANT_MATCHER);
web.ignoring()
.requestMatchers(QUARTZ_MANAGER_UI_ANT_MATCHER);
if(BooleanUtils.isNotFalse(oasEnabled))
web.ignoring()
.antMatchers(HttpMethod.GET, PATTERNS_SWAGGER_UI);
.requestMatchers(PATTERNS_SWAGGER_UI);
};
}

View File

@@ -13,7 +13,7 @@ import it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths;
import it.fabioformosa.quartzmanager.api.security.properties.JwtSecurityProperties;
import lombok.Generated;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -29,23 +29,24 @@ import java.util.Arrays;
public class SecurityOpenApiConfig {
@Order(Ordered.HIGHEST_PRECEDENCE)
@Bean("quartzManagerOpenApiCustomiser")
public OpenApiCustomiser configureQuartzManagerOpenAPI(JwtSecurityProperties jwtSecurityProps) {
@Bean("quartzManagerOpenApiCustomizer")
public OpenApiCustomizer configureQuartzManagerOpenAPI(JwtSecurityProperties jwtSecurityProps) {
return openAPI -> {
if (!jwtSecurityProps.getCookieStrategy().isEnabled())
openAPI
.components(new Components().addSecuritySchemes(OpenAPIConfigConsts.QUARTZ_MANAGER_SEC_OAS_SCHEMA, buildBasicAuthScheme()));
ObjectSchema loginRequestSchema = new ObjectSchema();
loginRequestSchema.addProperty("username", new StringSchema());
loginRequestSchema.addProperty("password", new PasswordSchema());
loginRequestSchema.required(Arrays.asList("username", "password"));
openAPI.path(QuartzManagerPaths.QUARTZ_MANAGER_LOGIN_PATH,
new PathItem().post(new Operation()
.operationId("login")
.tags(Arrays.asList("auth"))
.requestBody(new RequestBody().content(
new Content().addMediaType("application/x-www-form-urlencoded", new MediaType().schema(new Schema().type("object")
.addProperties("username", new StringSchema())
.addProperties("password", new PasswordSchema())
.required(Arrays.asList("username", "password"))
))))
new Content().addMediaType("application/x-www-form-urlencoded", new MediaType().schema(loginRequestSchema))))
.responses(new ApiResponses().addApiResponse("200", new ApiResponse().description("JWT Token to authenticate the next requests")))
.responses(new ApiResponses().addApiResponse("401", new ApiResponse().description("Unauthorized - Username or password are incorrect!")))
));

View File

@@ -3,12 +3,14 @@ package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import lombok.EqualsAndHashCode;
import org.springframework.security.authentication.AbstractAuthenticationToken;
@EqualsAndHashCode
import java.util.Collections;
@EqualsAndHashCode(callSuper = false)
public class AnonAuthentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
public AnonAuthentication() {
super( null );
super(Collections.emptyList());
}
@Override

View File

@@ -3,8 +3,8 @@ package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

View File

@@ -5,7 +5,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
/**
* It delegates the login to the @FormLoginConfigurer of the httpSecurity.
@@ -55,19 +54,17 @@ public class FormLoginConfig implements LoginConfigurer {
HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
log.debug("Configuring login through FormLoginConfigurer...");
FormLoginConfigurer<HttpSecurity> login = http.formLogin().loginPage(loginPath);
if(authenticationSuccessHandler != null) {
log.debug("Setting an authenticationSuccessHandler");
login = login.successHandler(authenticationSuccessHandler);
}
if(authenticationFailureHandler != null) {
log.debug("Setting an authenticationFailureHandler");
login = login.failureHandler(authenticationFailureHandler);
}
return login.and();
return http.formLogin(login -> {
login.loginPage(loginPath);
if(authenticationSuccessHandler != null) {
log.debug("Setting an authenticationSuccessHandler");
login.successHandler(authenticationSuccessHandler);
}
if(authenticationFailureHandler != null) {
log.debug("Setting an authenticationFailureHandler");
login.failureHandler(authenticationFailureHandler);
}
});
}
}

View File

@@ -1,8 +1,8 @@
package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;

View File

@@ -2,7 +2,7 @@ package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;

View File

@@ -9,8 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**

View File

@@ -5,15 +5,15 @@ import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -92,7 +92,8 @@ public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private boolean skipPathRequest(HttpServletRequest request, List<String> pathsToSkip ) {
if(pathsToSkip == null)
pathsToSkip = new ArrayList<>();
List<RequestMatcher> matchers = pathsToSkip.stream().map(AntPathRequestMatcher::new).collect(Collectors.toList());
PathPatternRequestMatcher.Builder matcherBuilder = PathPatternRequestMatcher.withDefaults();
List<RequestMatcher> matchers = pathsToSkip.stream().map(matcherBuilder::matcher).collect(Collectors.toList());
OrRequestMatcher compositeMatchers = new OrRequestMatcher(matchers);
return compositeMatchers.matches(request);
}

View File

@@ -2,18 +2,20 @@ package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import it.fabioformosa.quartzmanager.api.security.properties.JwtSecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
@@ -25,16 +27,19 @@ public class JwtTokenHelper {
private static final Logger log = LoggerFactory.getLogger(JwtTokenHelper.class);
private static String base64EncodeSecretKey(String secretKey) {
return Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
private static SecretKey signingKey(String secretKey) {
try {
byte[] keyBytes = MessageDigest.getInstance("SHA-512").digest(secretKey.getBytes(StandardCharsets.UTF_8));
return new SecretKeySpec(keyBytes, "HmacSHA512");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unable to create JWT signing key", e);
}
}
private final String appName;
private final JwtSecurityProperties jwtSecurityProps;
private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512;
public JwtTokenHelper(String appName, JwtSecurityProperties jwtSecurityProps) {
super();
this.appName = appName;
@@ -60,20 +65,20 @@ public class JwtTokenHelper {
}
private String generateToken(Map<String, Object> claims) {
return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
.signWith(SIGNATURE_ALGORITHM, base64EncodeSecretKey(jwtSecurityProps.getSecret())).compact();
return Jwts.builder().claims(claims).expiration(generateExpirationDate())
.signWith(signingKey(jwtSecurityProps.getSecret()), Jwts.SIG.HS512).compact();
}
public String generateToken(String username) {
return Jwts.builder().setIssuer(appName).setSubject(username).setIssuedAt(generateCurrentDate())
.setExpiration(generateExpirationDate())
.signWith(SIGNATURE_ALGORITHM, base64EncodeSecretKey(jwtSecurityProps.getSecret())).compact();
return Jwts.builder().issuer(appName).subject(username).issuedAt(generateCurrentDate())
.expiration(generateExpirationDate())
.signWith(signingKey(jwtSecurityProps.getSecret()), Jwts.SIG.HS512).compact();
}
private Claims verifyAndGetClaimsFromToken(String token) {
Claims claims;
claims = Jwts.parser().setSigningKey(base64EncodeSecretKey(jwtSecurityProps.getSecret()))
.parseClaimsJws(token).getBody();
claims = Jwts.parser().verifyWith(signingKey(jwtSecurityProps.getSecret())).build()
.parseSignedClaims(token).getPayload();
if (claims == null)
throw new IllegalStateException("Not found any claims into the JWT token!");
return claims;
@@ -108,8 +113,8 @@ public class JwtTokenHelper {
String refreshedToken;
try {
final Claims claims = verifyAndGetClaimsFromToken(token);
claims.setIssuedAt(generateCurrentDate());
refreshedToken = generateToken(claims);
refreshedToken = Jwts.builder().claims(claims).issuedAt(generateCurrentDate()).expiration(generateExpirationDate())
.signWith(signingKey(jwtSecurityProps.getSecret()), Jwts.SIG.HS512).compact();
} catch (Exception e) {
log.error("Error refreshing jwt token due to " + e.getMessage(), e);
refreshedToken = null;

View File

@@ -4,9 +4,9 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

View File

@@ -1,11 +1,8 @@
package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import it.fabioformosa.quartzmanager.api.security.helpers.LoginConfigurer;
@@ -13,11 +10,10 @@ import it.fabioformosa.quartzmanager.api.security.helpers.LoginConfigurer;
* It wraps the httpSecurity to provide new function as login and logout
*
*/
public class QuartzManagerHttpSecurity extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
public class QuartzManagerHttpSecurity {
public static QuartzManagerHttpSecurity from(HttpSecurity httpSecurity){
QuartzManagerHttpSecurity newInstance = new QuartzManagerHttpSecurity(httpSecurity);
newInstance.setBuilder(httpSecurity);
return newInstance;
}
@@ -39,13 +35,14 @@ public class QuartzManagerHttpSecurity extends SecurityConfigurerAdapter<Default
}
public LogoutConfigurer<HttpSecurity> logout(String logoutPath) throws Exception {
LogoutConfigurer<HttpSecurity> logoutConfigurer = httpSecurity.logout().logoutRequestMatcher(new AntPathRequestMatcher(logoutPath))
.logoutSuccessHandler(logoutSuccess);
public HttpSecurity logout(String logoutPath) throws Exception {
String cookie = loginConfigurer.cookieMustBeDeletedAtLogout();
if(cookie != null)
logoutConfigurer.deleteCookies(cookie);
return logoutConfigurer;
return httpSecurity.logout(logout -> {
logout.logoutRequestMatcher(PathPatternRequestMatcher.withDefaults().matcher(logoutPath));
logout.logoutSuccessHandler(logoutSuccess);
if(cookie != null)
logout.deleteCookies(cookie);
});
}
public QuartzManagerHttpSecurity withLoginConfigurer(LoginConfigurer loginConfigurer, LogoutSuccess logoutSuccess) {

View File

@@ -3,8 +3,8 @@ package it.fabioformosa.quartzmanager.api.security.helpers.impl;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -20,4 +20,3 @@ public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}

View File

@@ -6,10 +6,10 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;

View File

@@ -1,29 +1,30 @@
package it.fabioformosa.quartzmanager.api.security;
import it.fabioformosa.quartzmanager.api.security.models.UserTokenState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import static it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths.QUARTZ_MANAGER_LOGIN_PATH;
public abstract class AbstractSecurityLoginTest {
@Autowired
private TestRestTemplate testRestTemplate;
@LocalServerPort
private int port;
protected ResponseEntity<UserTokenState> doLogin() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", "foo");
map.add("password", "bar");
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers);
ResponseEntity<UserTokenState> responseEntity = testRestTemplate.exchange(QUARTZ_MANAGER_LOGIN_PATH, HttpMethod.POST, entity, UserTokenState.class);
return responseEntity;
return RestClient.create("http://localhost:" + port)
.post()
.uri(QUARTZ_MANAGER_LOGIN_PATH)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(map)
.retrieve()
.toEntity(UserTokenState.class);
}
}

View File

@@ -9,13 +9,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@@ -60,9 +60,8 @@ class SecurityControllerTest {
}
@Test
@WithMockUser("admin")
void givenAnUser_whenCalledATestScheduler_thenShouldReturn2xx() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(TestController.QUARTZ_MANAGER + "/scheduler"))
mockMvc.perform(MockMvcRequestBuilders.get(TestController.QUARTZ_MANAGER + "/scheduler").with(user("admin")))
.andExpect(status().isOk());
}

View File

@@ -2,15 +2,15 @@ package it.fabioformosa.quartzmanager.api.security.controllers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths.QUARTZ_MANAGER_AUTH_PATH;
import static it.fabioformosa.quartzmanager.api.security.controllers.UserController.WHOAMI_URL;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@@ -27,9 +27,8 @@ class UserControllerTest {
private MockMvc mockMvc;
@Test
@WithMockUser("admin")
void givenAnUser_whenCalledTheWhoamiEndpoint_thenShouldReturn2xx() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(QUARTZ_MANAGER_AUTH_PATH + WHOAMI_URL))
mockMvc.perform(MockMvcRequestBuilders.get(QUARTZ_MANAGER_AUTH_PATH + WHOAMI_URL).with(user("admin")))
.andExpect(status().isOk());
}

View File

@@ -7,8 +7,8 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import javax.validation.Validation;
import javax.validation.Validator;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

View File

@@ -17,7 +17,7 @@
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>9</java.version>
<java.version>17</java.version>
<frontend.folderName>quartz-manager-frontend</frontend.folderName>
<node.version>v16.14.1</node.version>
<npm.version>8.19.3</npm.version>

View File

@@ -18,8 +18,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<springdoc-openapi.version>1.5.12</springdoc-openapi.version>
<java.version>9</java.version>
<springdoc-openapi.version>3.0.3</springdoc-openapi.version>
<java.version>17</java.version>
</properties>
<dependencies>
@@ -59,18 +59,23 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MISC -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
<artifactId>jjwt-api</artifactId>
<version>0.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@@ -86,7 +91,7 @@
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
@@ -96,6 +101,7 @@
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>spring-mock-mvc</artifactId>
<version>6.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -135,8 +141,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>9</source>
<target>9</target>
<release>${java.version}</release>
</configuration>
</plugin>
</plugins>

View File

@@ -4,7 +4,7 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import lombok.Generated;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;
@Controller
@RequestMapping("/session")