diff --git a/batch-files/redis-cache/start-local-redis-stack.bat.bat b/batch-files/redis-cache/start-local-redis-stack.bat.bat new file mode 100644 index 00000000..57bb43a5 --- /dev/null +++ b/batch-files/redis-cache/start-local-redis-stack.bat.bat @@ -0,0 +1 @@ +docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest \ No newline at end of file diff --git a/code/gms-backend/src/main/java/io/github/gms/GmsApplication.java b/code/gms-backend/src/main/java/io/github/gms/GmsApplication.java index 3e0f00b4..3cd60152 100644 --- a/code/gms-backend/src/main/java/io/github/gms/GmsApplication.java +++ b/code/gms-backend/src/main/java/io/github/gms/GmsApplication.java @@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; /** @@ -13,7 +14,8 @@ */ @SpringBootApplication(exclude = { LdapRepositoriesAutoConfiguration.class, - JacksonAutoConfiguration.class + JacksonAutoConfiguration.class, + RedisAutoConfiguration.class }) public class GmsApplication { diff --git a/code/gms-backend/src/main/java/io/github/gms/common/config/RedisCacheConfig.java b/code/gms-backend/src/main/java/io/github/gms/common/config/RedisCacheConfig.java index cb412233..247d556a 100644 --- a/code/gms-backend/src/main/java/io/github/gms/common/config/RedisCacheConfig.java +++ b/code/gms-backend/src/main/java/io/github/gms/common/config/RedisCacheConfig.java @@ -1,6 +1,7 @@ package io.github.gms.common.config; import io.github.gms.common.config.cache.ApiCacheKeyGenerator; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; @@ -13,6 +14,7 @@ import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; @@ -31,6 +33,17 @@ @ConditionalOnProperty(name = "config.cache.redis.enabled", havingValue = "true") public class RedisCacheConfig { + @Value("${config.cache.redis.host}") + private String host; + + @Value("${config.cache.redis.port}") + private Integer port; + + @Bean + public LettuceConnectionFactory lettuceConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + @Primary @Bean public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { diff --git a/code/gms-backend/src/main/java/io/github/gms/common/dto/ErrorResponseDto.java b/code/gms-backend/src/main/java/io/github/gms/common/dto/ErrorResponseDto.java index ad90ee45..2a644fa8 100644 --- a/code/gms-backend/src/main/java/io/github/gms/common/dto/ErrorResponseDto.java +++ b/code/gms-backend/src/main/java/io/github/gms/common/dto/ErrorResponseDto.java @@ -1,11 +1,15 @@ package io.github.gms.common.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import java.io.Serial; import java.io.Serializable; import java.time.ZonedDateTime; +import static io.github.gms.common.util.Constants.DATE_FORMAT; + /** * @author Peter Szrnka * @since 1.0 @@ -14,10 +18,12 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class ErrorResponseDto implements Serializable { + @Serial private static final long serialVersionUID = 6418018474049813605L; private String correlationId; private String message; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_FORMAT) private ZonedDateTime timestamp; public ErrorResponseDto(String message, String correlationId, ZonedDateTime timestamp) { diff --git a/code/gms-backend/src/main/java/io/github/gms/common/filter/SecureHeaderInitializerFilter.java b/code/gms-backend/src/main/java/io/github/gms/common/filter/SecureHeaderInitializerFilter.java index 02a62a9a..7d0e2a2f 100644 --- a/code/gms-backend/src/main/java/io/github/gms/common/filter/SecureHeaderInitializerFilter.java +++ b/code/gms-backend/src/main/java/io/github/gms/common/filter/SecureHeaderInitializerFilter.java @@ -1,21 +1,6 @@ package io.github.gms.common.filter; -import java.io.IOException; -import java.util.Set; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - import com.google.common.collect.Sets; - import io.github.gms.auth.AuthorizationService; import io.github.gms.auth.model.AuthorizationResponse; import io.github.gms.common.enums.JwtConfigType; @@ -28,6 +13,19 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A custom Spring filter used to authorize user by parsing JWT token. @@ -60,6 +58,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (shouldSkipUrl(request.getRequestURI())) { filterChain.doFilter(request, response); + MDC.clear(); return; } diff --git a/code/gms-backend/src/main/java/io/github/gms/secure/controller/UserController.java b/code/gms-backend/src/main/java/io/github/gms/secure/controller/UserController.java index 1bff241b..97554718 100644 --- a/code/gms-backend/src/main/java/io/github/gms/secure/controller/UserController.java +++ b/code/gms-backend/src/main/java/io/github/gms/secure/controller/UserController.java @@ -72,7 +72,7 @@ public ResponseEntity delete(@PathVariable("id") Long id) { @PostMapping("/{id}") @PreAuthorize(ROLE_ADMIN) @Audited(operation = EventOperation.TOGGLE_STATUS) - public ResponseEntity toggle(@PathVariable("id") Long id, @RequestParam boolean enabled) { + public ResponseEntity toggle(@PathVariable("id") Long id, @RequestParam("enabled") boolean enabled) { service.toggleStatus(id, enabled); return new ResponseEntity<>(HttpStatus.OK); } @@ -97,7 +97,7 @@ public ResponseEntity getMfaQrCode() { @PostMapping("/toggle_mfa") @PreAuthorize(ALL_ROLE) @Audited(operation = EventOperation.TOGGLE_MFA) - public ResponseEntity toggleMfa(@RequestParam boolean enabled) { + public ResponseEntity toggleMfa(@RequestParam("enabled") boolean enabled) { service.toggleMfa(enabled); return new ResponseEntity<>(HttpStatus.OK); } diff --git a/code/gms-backend/src/main/resources/application.properties b/code/gms-backend/src/main/resources/application.properties index 157b6b72..cb54e1de 100644 --- a/code/gms-backend/src/main/resources/application.properties +++ b/code/gms-backend/src/main/resources/application.properties @@ -65,5 +65,5 @@ spring.flyway.locations=classpath:db/${SELECTED_DB}/migration # Cache (Redis) config.cache.redis.enabled=${ENABLE_REDIS_CACHE:false} -spring.redis.host=${REDIS_HOST} -spring.redis.port=${REDIS_PORT} \ No newline at end of file +config.cache.redis.host=${REDIS_HOST} +config.cache.redis.port=${REDIS_PORT} \ No newline at end of file diff --git a/code/gms-backend/src/test/java/io/github/gms/api/controller/ApiIntegrationTest.java b/code/gms-backend/src/test/java/io/github/gms/api/controller/ApiIntegrationTest.java index 73cc4006..b8e7b8e0 100644 --- a/code/gms-backend/src/test/java/io/github/gms/api/controller/ApiIntegrationTest.java +++ b/code/gms-backend/src/test/java/io/github/gms/api/controller/ApiIntegrationTest.java @@ -4,6 +4,7 @@ import io.github.gms.secure.repository.KeystoreAliasRepository; import io.github.gms.util.DemoData; import io.github.gms.util.TestUtils; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; @@ -12,12 +13,14 @@ import java.util.Map; +import static io.github.gms.util.TestConstants.TAG_INTEGRATION_TEST; import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Peter Szrnka * @since 1.0 */ +@Tag(TAG_INTEGRATION_TEST) class ApiIntegrationTest extends AbstractIntegrationTest { @Autowired diff --git a/code/gms-backend/src/test/java/io/github/gms/common/filter/SecureHeaderInitializerFilterTest.java b/code/gms-backend/src/test/java/io/github/gms/common/filter/SecureHeaderInitializerFilterTest.java index 7d83aeb6..ea0639f1 100644 --- a/code/gms-backend/src/test/java/io/github/gms/common/filter/SecureHeaderInitializerFilterTest.java +++ b/code/gms-backend/src/test/java/io/github/gms/common/filter/SecureHeaderInitializerFilterTest.java @@ -1,33 +1,6 @@ package io.github.gms.common.filter; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.MockedStatic; -import org.slf4j.MDC; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseCookie; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - import com.google.common.collect.Sets; - import io.github.gms.abstraction.AbstractUnitTest; import io.github.gms.auth.AuthorizationService; import io.github.gms.auth.model.AuthorizationResponse; @@ -42,6 +15,31 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.slf4j.MDC; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Peter Szrnka @@ -76,7 +74,7 @@ void shouldSkip() { filter.doFilterInternal(request, response, filterChain); // assert - assertNotNull(MDC.get(MdcParameter.CORRELATION_ID.getDisplayName())); + assertNull(MDC.get(MdcParameter.CORRELATION_ID.getDisplayName())); verify(authorizationService, never()).authorize(any(HttpServletRequest.class)); verify(filterChain).doFilter(any(), any()); verify(response, never()).sendError(HttpStatus.OK.value(), ERROR_MESSAGE); diff --git a/code/gms-backend/src/test/java/io/github/gms/controller/security/KeystoreAdminRoleSecurityTest.java b/code/gms-backend/src/test/java/io/github/gms/controller/security/KeystoreAdminRoleSecurityTest.java index 629695f8..71f88030 100644 --- a/code/gms-backend/src/test/java/io/github/gms/controller/security/KeystoreAdminRoleSecurityTest.java +++ b/code/gms-backend/src/test/java/io/github/gms/controller/security/KeystoreAdminRoleSecurityTest.java @@ -1,6 +1,7 @@ package io.github.gms.controller.security; import io.github.gms.abstraction.AbstractAdminRoleSecurityTest; +import io.github.gms.secure.dto.GetSecureValueDto; import io.github.gms.secure.dto.IdNamePairListDto; import io.github.gms.secure.dto.KeystoreDto; import io.github.gms.secure.dto.KeystoreListDto; @@ -49,11 +50,13 @@ void testListFailWithHttp403() { @Test void testGetValueFailWithHttp403() { - HttpEntity requestEntity = new HttpEntity<>(TestUtils.getHttpHeaders(jwt)); + GetSecureValueDto dto = new GetSecureValueDto(); + dto.setEntityId(DemoData.KEYSTORE_ID); + HttpEntity requestEntity = new HttpEntity<>(dto, TestUtils.getHttpHeaders(jwt)); // assert HttpClientErrorException.Forbidden exception = assertThrows(HttpClientErrorException.Forbidden.class, () -> - executeHttpGet("/secure/keystore/value/" + DemoData.KEYSTORE_ID, requestEntity, String.class)); + executeHttpPost("/secure/keystore/value", requestEntity, String.class)); assertTrue(exception.getMessage().startsWith("403")); } diff --git a/code/gms-backend/src/test/resources/application.properties b/code/gms-backend/src/test/resources/application.properties index 7f1cd1b2..4f3d7a08 100644 --- a/code/gms-backend/src/test/resources/application.properties +++ b/code/gms-backend/src/test/resources/application.properties @@ -41,4 +41,9 @@ config.message.old.limit=1;d # Jobs config.job.secretRotation.enabled=false config.job.eventMaintenance.enabled=false -config.job.messageCleanup.enabled=false \ No newline at end of file +config.job.messageCleanup.enabled=false + +# Redis +spring.data.redis.repositories.enabled=false +config.cache.redis.host=localhost +config.cache.redis.port=6379 \ No newline at end of file