From 95f0e2ee0349b242782ed40b11a24888289ceaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Ca=C3=B1izares=20Mata?= Date: Sun, 14 Jan 2024 23:34:01 +0100 Subject: [PATCH] [CONLUZ-37] Implemented a validation class to stop the app bootstrap if required config parameters are not present (#39) --- .../lucoenergia/conluz/AppConfigCheck.java | 36 +++++++++++++++++++ .../lucoenergia/conluz/ConLuzApplication.java | 1 - .../security/auth/InvalidTokenException.java | 5 +++ .../security/auth/JwtAuthRepository.java | 17 ++++++--- .../security/auth/JwtConfiguration.java | 2 +- .../auth/SecretKeyNotFoundException.java | 4 +++ src/main/resources/logback-spring.xml | 2 +- .../user/get/GetAllUsersControllerTest.java | 3 +- .../security/auth/JwtAuthRepositoryTest.java | 20 +++++++++++ 9 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/lucoenergia/conluz/AppConfigCheck.java create mode 100644 src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/SecretKeyNotFoundException.java diff --git a/src/main/java/org/lucoenergia/conluz/AppConfigCheck.java b/src/main/java/org/lucoenergia/conluz/AppConfigCheck.java new file mode 100644 index 0000000..924fe41 --- /dev/null +++ b/src/main/java/org/lucoenergia/conluz/AppConfigCheck.java @@ -0,0 +1,36 @@ +package org.lucoenergia.conluz; + +import org.lucoenergia.conluz.infrastructure.shared.security.auth.JwtConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +@Component +public class AppConfigCheck implements ApplicationRunner { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppConfigCheck.class); + + private final JwtConfiguration jwtConfiguration; + + public AppConfigCheck(JwtConfiguration jwtConfiguration) { + this.jwtConfiguration = jwtConfiguration; + } + + @Override + public void run(ApplicationArguments args) { + isSecretKeyPresent(); + } + + private void isSecretKeyPresent() { + // Secret key must be present + String secretKey = jwtConfiguration.getSecretKey(); + if (secretKey == null || secretKey.isBlank()) { + LOGGER.error("Secret key not found. You must provide a secret key using the parameter {}", + JwtConfiguration.CONLUZ_JWT_SECRET_KEY); + System.exit(1); + } + } +} + diff --git a/src/main/java/org/lucoenergia/conluz/ConLuzApplication.java b/src/main/java/org/lucoenergia/conluz/ConLuzApplication.java index c955cec..a7b3ab5 100644 --- a/src/main/java/org/lucoenergia/conluz/ConLuzApplication.java +++ b/src/main/java/org/lucoenergia/conluz/ConLuzApplication.java @@ -9,5 +9,4 @@ public class ConLuzApplication { public static void main(String[] args) { SpringApplication.run(ConLuzApplication.class, args); } - } diff --git a/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/InvalidTokenException.java b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/InvalidTokenException.java index 625412e..a09d6a2 100644 --- a/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/InvalidTokenException.java +++ b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/InvalidTokenException.java @@ -8,6 +8,11 @@ public InvalidTokenException(String token) { this.token = token; } + public InvalidTokenException(String token, Throwable ex) { + super(ex); + this.token = token; + } + public String getToken() { return token; } diff --git a/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepository.java b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepository.java index 8d32bae..37566e5 100644 --- a/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepository.java +++ b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepository.java @@ -2,6 +2,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @@ -86,14 +87,22 @@ private boolean isTokenExpired(Token token) { } private Key getKey() { - byte[] keyBytes = Decoders.BASE64.decode(jwtConfiguration.getSecretKey()); + String secretKey = jwtConfiguration.getSecretKey(); + if (secretKey == null || secretKey.isBlank()) { + throw new SecretKeyNotFoundException(); + } + byte[] keyBytes = Decoders.BASE64.decode(secretKey); return Keys.hmacShaKeyFor(keyBytes); } private Claims getAllClaims(Token token) { - return Jwts - .parserBuilder().setSigningKey(getKey()).build() - .parseClaimsJws(token.getToken()).getBody(); + try { + return Jwts + .parserBuilder().setSigningKey(getKey()).build() + .parseClaimsJws(token.getToken()).getBody(); + } catch (MalformedJwtException e) { + throw new InvalidTokenException(token.getToken(), e); + } } private T getClaim(Token token, Function claimsResolver) { diff --git a/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtConfiguration.java b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtConfiguration.java index d6a20e5..ae77c61 100644 --- a/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtConfiguration.java +++ b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtConfiguration.java @@ -7,7 +7,7 @@ @Configuration public class JwtConfiguration { - private static final String CONLUZ_JWT_SECRET_KEY = "CONLUZ_JWT_SECRET_KEY"; + public static final String CONLUZ_JWT_SECRET_KEY = "CONLUZ_JWT_SECRET_KEY"; @Value("${conluz.security.jwt.expiration-time}") private Integer expirationTime; diff --git a/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/SecretKeyNotFoundException.java b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/SecretKeyNotFoundException.java new file mode 100644 index 0000000..e2de541 --- /dev/null +++ b/src/main/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/SecretKeyNotFoundException.java @@ -0,0 +1,4 @@ +package org.lucoenergia.conluz.infrastructure.shared.security.auth; + +public class SecretKeyNotFoundException extends RuntimeException { +} diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 88b83d5..1d4fc76 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -14,7 +14,7 @@ - + diff --git a/src/test/java/org/lucoenergia/conluz/infrastructure/admin/user/get/GetAllUsersControllerTest.java b/src/test/java/org/lucoenergia/conluz/infrastructure/admin/user/get/GetAllUsersControllerTest.java index 81253a3..a82249a 100644 --- a/src/test/java/org/lucoenergia/conluz/infrastructure/admin/user/get/GetAllUsersControllerTest.java +++ b/src/test/java/org/lucoenergia/conluz/infrastructure/admin/user/get/GetAllUsersControllerTest.java @@ -9,6 +9,7 @@ import org.lucoenergia.conluz.domain.admin.user.UserMother; import org.lucoenergia.conluz.domain.admin.user.create.CreateUserRepository; import org.lucoenergia.conluz.infrastructure.shared.BaseControllerTest; +import org.lucoenergia.conluz.infrastructure.shared.security.auth.InvalidTokenException; import org.lucoenergia.conluz.infrastructure.shared.security.auth.JwtAuthenticationFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; @@ -278,7 +279,7 @@ void testWithWrongToken() { final String wrongToken = JwtAuthenticationFilter.AUTHORIZATION_HEADER_PREFIX + "wrong"; - Assertions.assertThrows(MalformedJwtException.class, + Assertions.assertThrows(InvalidTokenException.class, () -> mockMvc.perform(get("/api/v1/users") .header(HttpHeaders.AUTHORIZATION, wrongToken) .contentType(MediaType.APPLICATION_JSON))); diff --git a/src/test/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepositoryTest.java b/src/test/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepositoryTest.java index caed810..7146727 100644 --- a/src/test/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepositoryTest.java +++ b/src/test/java/org/lucoenergia/conluz/infrastructure/shared/security/auth/JwtAuthRepositoryTest.java @@ -3,6 +3,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; import org.lucoenergia.conluz.domain.admin.user.User; import org.lucoenergia.conluz.domain.admin.user.auth.Token; import org.lucoenergia.conluz.domain.admin.user.UserMother; @@ -15,6 +18,8 @@ @ExtendWith(MockitoExtension.class) class JwtAuthRepositoryTest { + private final static String SECRET_KEY = "b5f86373ba5d7593f4c6eab57862bf4be76369c1adbe263ae2d50ddae40b8ca2"; + @InjectMocks private JwtAuthRepository repository; @@ -50,12 +55,27 @@ void testTokenClaims() { @Test void testGetUserIdByInvalidToken() { String invalidToken = "invalid-token"; + + Mockito.when(jwtConfiguration.getSecretKey()).thenReturn(SECRET_KEY); + InvalidTokenException exception = Assertions.assertThrows(InvalidTokenException.class, () -> repository.getUserIdFromToken(Token.of(invalidToken))); Assertions.assertEquals(invalidToken, exception.getToken()); } + @ParameterizedTest + @ValueSource(strings = {"", " "}) + @NullSource + void testMissingSecretKey(String secretKey) { + String invalidToken = "invalid-token"; + + Mockito.when(jwtConfiguration.getSecretKey()).thenReturn(secretKey); + + Assertions.assertThrows(SecretKeyNotFoundException.class, + () -> repository.getUserIdFromToken(Token.of(invalidToken))); + } + private void mockJwtConfig() { // Mock expiration time and JWT secret key Mockito.when(jwtConfiguration.getExpirationTime()).thenReturn(30);