diff --git a/commandline/pom.xml b/commandline/pom.xml index 57016eb63..16370e614 100644 --- a/commandline/pom.xml +++ b/commandline/pom.xml @@ -71,7 +71,6 @@ com.fasterxml.jackson.core jackson-core - 2.21.4 diff --git a/generate/pom.xml b/generate/pom.xml index 0c60d5ab1..d407af1c1 100644 --- a/generate/pom.xml +++ b/generate/pom.xml @@ -60,7 +60,6 @@ com.fasterxml.jackson.core jackson-core - 2.21.4 diff --git a/pom.xml b/pom.xml index fedbc8795..7e616fdab 100644 --- a/pom.xml +++ b/pom.xml @@ -12,12 +12,12 @@ 21 1.12.797 - 5.11.4 - 1.11.4 - 0.10.7 - 6.2.19 - 6.5.11 - 10.1.55 + 6.0.1 + 6.0.1 + 0.12.6 +7.0.8 + 7.0.5 + 11.0.23 0.90 verify @@ -252,13 +252,7 @@ com.fasterxml.jackson.core jackson-databind - 2.21.4 - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.21.4 + 2.21.5 @@ -472,43 +466,19 @@ ch.qos.logback logback-classic - 1.5.25 + 1.5.36 ch.qos.logback logback-core - 1.5.25 + 1.5.36 org.springdoc - springdoc-openapi-ui - 1.7.0 - - - - org.springframework.security - spring-security-jwt - 1.1.0.RELEASE - - - org.springframework.security - spring-security-web - - - org.bouncycastle - bcprov-jdk15on - - - org.bouncycastle - bcprov-jdk15to18 - - - org.bouncycastle - bcprov-jdk18on - - + springdoc-openapi-starter-webmvc-ui + 2.8.3 @@ -586,20 +556,31 @@ net.logstash.logback logstash-logback-encoder - 8.1 - - - - com.jcabi - jcabi-manifests - 1.1 - - - - org.junit.jupiter - junit-jupiter-api - ${junit.version} - + 9.0 + + + + + tools.jackson.core + jackson-core + 3.1.4 + + + tools.jackson.core + jackson-databind + 3.1.5 + + + tools.jackson.core + jackson-annotations + 3.1.4 + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + net.jodah diff --git a/rest-api/pom.xml b/rest-api/pom.xml index 837b66fdc..a09a2b263 100644 --- a/rest-api/pom.xml +++ b/rest-api/pom.xml @@ -35,9 +35,10 @@ ${spring.framework.version} managed-by-boot-bom - - 5.11.4 - 1.11.4 + + 6.0.1 + 6.0.1 5.5.2 4.0.28 2.2 @@ -45,7 +46,7 @@ 6.1.0 - 6.5.11 + 7.0.5 2.0.13 @@ -60,7 +61,7 @@ org.springframework.boot spring-boot-maven-plugin - 3.5.15 + 4.0.6 repackage @@ -177,40 +178,28 @@ + - org.springframework.security - spring-security-bom - ${spring.security.version} + org.springframework + spring-framework-bom + ${spring-framework.version} pom import - org.springframework.boot - spring-boot-dependencies - 3.5.15 + org.springframework.security + spring-security-bom + ${spring.security.version} pom import - - - org.springframework - spring-webmvc - ${spring-framework.version} - - - org.springframework - spring-beans - ${spring-framework.version} - - - - org.springframework - spring-framework-bom - ${spring-framework.version} + org.springframework.boot + spring-boot-dependencies + 4.0.6 pom import @@ -271,21 +260,7 @@ org.springdoc - springdoc-openapi-ui - - - - - org.springframework.plugin - spring-plugin-core - 1.2.0.RELEASE - - org.springframeworkspring-beans - org.springframeworkspring-core - org.springframeworkspring-context - org.springframeworkspring-aop - org.springframeworkspring-expression - + springdoc-openapi-starter-webmvc-ui @@ -321,10 +296,12 @@ org.springframeworkspring-core${spring-framework.version} org.springframeworkspring-expression${spring-framework.version} + org.springframeworkspring-context${spring-framework.version} + org.springframeworkspring-beans${spring-framework.version} org.springframework.retryspring-retry - com.jcabijcabi-manifests + com.jcabijcabi-manifests1.1 com.amazonawsaws-java-sdk-core diff --git a/rest-api/src/main/java/gov/cms/qpp/conversion/api/RestApiApplication.java b/rest-api/src/main/java/gov/cms/qpp/conversion/api/RestApiApplication.java index a5aefd18c..7298d1212 100644 --- a/rest-api/src/main/java/gov/cms/qpp/conversion/api/RestApiApplication.java +++ b/rest-api/src/main/java/gov/cms/qpp/conversion/api/RestApiApplication.java @@ -1,12 +1,7 @@ package gov.cms.qpp.conversion.api; -import org.apache.catalina.connector.Connector; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.servlet.server.ServletWebServerFactory; -import org.springframework.context.annotation.Bean; /** * Application to be ran diff --git a/rest-api/src/main/java/gov/cms/qpp/conversion/api/config/SecurityConfig.java b/rest-api/src/main/java/gov/cms/qpp/conversion/api/config/SecurityConfig.java index 51a2cb41e..2adabe00a 100644 --- a/rest-api/src/main/java/gov/cms/qpp/conversion/api/config/SecurityConfig.java +++ b/rest-api/src/main/java/gov/cms/qpp/conversion/api/config/SecurityConfig.java @@ -33,7 +33,7 @@ public class SecurityConfig { protected String rtiOrgName; @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain filterChain(HttpSecurity http) { http.securityMatcher(PCF_WILDCARD) .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .csrf(csrf -> csrf.disable()) diff --git a/rest-api/src/main/java/gov/cms/qpp/conversion/api/security/JwtAuthorizationFilter.java b/rest-api/src/main/java/gov/cms/qpp/conversion/api/security/JwtAuthorizationFilter.java index 05c109f36..f7a2e41db 100644 --- a/rest-api/src/main/java/gov/cms/qpp/conversion/api/security/JwtAuthorizationFilter.java +++ b/rest-api/src/main/java/gov/cms/qpp/conversion/api/security/JwtAuthorizationFilter.java @@ -1,14 +1,16 @@ package gov.cms.qpp.conversion.api.security; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -63,19 +65,31 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha chain.doFilter(request, response); } + /** Reusable Jackson mapper for JWT payload decoding. */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final TypeReference> MAP_TYPE = new TypeReference<>() {}; + @SuppressWarnings("unchecked") private Map getPayload(String tokenHeader) { - String tokenWithoutBearer = tokenHeader.replace(TOKEN_PREFIX, ""); - String tokenWithoutSignatureAndBearer = removeSignature(tokenWithoutBearer); - Claims body = Jwts.parser() - .parseClaimsJwt(tokenWithoutSignatureAndBearer) - .getBody(); - return body.get("data", Map.class); - } - - private String removeSignature(String jws) { - int i = jws.lastIndexOf('.'); - return jws.substring(0, i + 1); + String token = tokenHeader.replace(TOKEN_PREFIX, ""); + String[] parts = token.split("\\."); + if (parts.length < 2) { + return Collections.emptyMap(); + } + try { + // Decode the payload (second part) directly — no signature verification needed. + // This works regardless of the alg header (HS256, RS256, none, etc.). + byte[] payloadBytes = Base64.getUrlDecoder().decode(parts[1]); + Map claims = OBJECT_MAPPER.readValue( + new String(payloadBytes, StandardCharsets.UTF_8), MAP_TYPE); + Object data = claims.get("data"); + if (data instanceof Map) { + return (Map) data; + } + } catch (IOException | IllegalArgumentException e) { + // Malformed token — treat as unauthenticated + } + return Collections.emptyMap(); } private boolean isValidCpcPlusOrg(Map payloadMap) { diff --git a/rest-api/src/main/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImpl.java b/rest-api/src/main/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImpl.java index 23960be62..8f8fbbe37 100644 --- a/rest-api/src/main/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImpl.java +++ b/rest-api/src/main/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImpl.java @@ -18,10 +18,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.NoOpResponseErrorHandler; import org.springframework.web.client.RestTemplate; import jakarta.annotation.PostConstruct; @@ -52,6 +51,7 @@ public class ValidationServiceImpl implements ValidationService { public ValidationServiceImpl(final Environment environment) { this.environment = environment; this.restTemplate = new RestTemplate(); + this.restTemplate.setErrorHandler(new NoOpResponseErrorHandler()); } /** @@ -114,7 +114,6 @@ public void validateQpp(ConversionReport conversionReport) { * @return The response from the validation API end-point. */ private ResponseEntity callValidationEndpoint(String url, JsonWrapper qpp) { - restTemplate.setErrorHandler(new NoHandlingErrorHandler()); HttpEntity request = new HttpEntity<>(qpp.toString(), getHeaders()); API_LOG.info("Calling QPP validation API {}", url); @@ -188,19 +187,4 @@ Error getError(String response) { ErrorMessage.class) .getError(); } - - /** - * A private static class that tells the {@link RestTemplate} to not throw an exception on HTTP status 3xx and 4xx. - */ - private static class NoHandlingErrorHandler extends DefaultResponseErrorHandler { - /** - * Empty so it doesn't throw an exception. - * - * @param response The ClientHttpResponse. - */ - @Override - public void handleError(final ClientHttpResponse response) { - //do nothing - } - } } diff --git a/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/DynamoDbConfigTest.java b/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/DynamoDbConfigTest.java index 515b34673..4481bd6b0 100644 --- a/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/DynamoDbConfigTest.java +++ b/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/DynamoDbConfigTest.java @@ -1,12 +1,5 @@ package gov.cms.qpp.conversion.api.config; -import com.amazonaws.SdkClientException; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; -import com.amazonaws.services.dynamodbv2.datamodeling.AttributeTransformer; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; -import com.amazonaws.services.kms.AWSKMS; import org.junit.Before; import org.junit.Rule; import org.junit.jupiter.api.Test; @@ -14,7 +7,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.core.env.Environment; @@ -28,12 +20,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.powermock.api.mockito.PowerMockito.doReturn; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.verifyStatic; -import static org.powermock.api.mockito.PowerMockito.when; -@PowerMockIgnore({"org.apache.xerces.*", "javax.xml.parsers.*", "org.xml.sax.*", "com.sun.org.apache.xerces.*" }) +@SuppressWarnings("unused") // Tests commented out pending PowerMock -> Mockito.mockStatic migration public class DynamoDbConfigTest { // private DynamoDbConfig underTest; diff --git a/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/KmsConfigTest.java b/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/KmsConfigTest.java index e7453d974..b6397244f 100644 --- a/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/KmsConfigTest.java +++ b/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/KmsConfigTest.java @@ -1,21 +1,8 @@ package gov.cms.qpp.conversion.api.config; - -import com.amazonaws.SdkClientException; -import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.AWSKMSClientBuilder; -import org.mockito.Mockito; -import org.mockito.Spy; -import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.when; -@PowerMockIgnore({"org.apache.xerces.*", "javax.xml.parsers.*", "org.xml.sax.*", "com.sun.org.apache.xerces.*" }) +@SuppressWarnings("unused") // Tests commented out pending PowerMock -> Mockito.mockStatic migration public class KmsConfigTest { // @Spy diff --git a/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/S3ConfigTest.java b/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/S3ConfigTest.java index 59893a71b..cfcbf18a6 100644 --- a/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/S3ConfigTest.java +++ b/rest-api/src/test/java/gov/cms/qpp/conversion/api/config/S3ConfigTest.java @@ -1,23 +1,9 @@ package gov.cms.qpp.conversion.api.config; -import com.amazonaws.SdkClientException; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; - -import org.mockito.Mockito; -import org.mockito.Spy; -import org.powermock.core.classloader.annotations.PowerMockIgnore; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.when; -import org.junit.jupiter.api.Test; -import static org.mockito.Mockito.mockStatic; -@PowerMockIgnore({"org.apache.xerces.*", "javax.xml.parsers.*", "org.xml.sax.*", "com.sun.org.apache.xerces.*" }) +@SuppressWarnings("unused") // Tests commented out pending PowerMock -> Mockito.mockStatic migration public class S3ConfigTest { // @Spy diff --git a/rest-api/src/test/java/gov/cms/qpp/conversion/api/exceptions/GlobalExceptionHandlerTest.java b/rest-api/src/test/java/gov/cms/qpp/conversion/api/exceptions/GlobalExceptionHandlerTest.java index 295ff4242..e8ada312f 100644 --- a/rest-api/src/test/java/gov/cms/qpp/conversion/api/exceptions/GlobalExceptionHandlerTest.java +++ b/rest-api/src/test/java/gov/cms/qpp/conversion/api/exceptions/GlobalExceptionHandlerTest.java @@ -213,7 +213,7 @@ void testHandleAmazonExceptionStatusCode() { ResponseEntity response = objectUnderTest.handleAmazonException(exception); - Truth.assertThat(response.getStatusCodeValue()).isEqualTo(404); + Truth.assertThat(response.getStatusCode().value()).isEqualTo(404); } @Test @@ -271,7 +271,7 @@ void testHandleNoResourceFoundExceptionReturnsPlain404() throws Exception { Mockito.mock(WebRequest.class) ); - Truth.assertThat(response.getStatusCodeValue()).isEqualTo(404); + Truth.assertThat(response.getStatusCode().value()).isEqualTo(404); Truth.assertThat(response.getHeaders().getContentType()) .isEquivalentAccordingToCompareTo(MediaType.TEXT_PLAIN); Truth.assertThat(response.getBody()).isEqualTo("Not found"); diff --git a/rest-api/src/test/java/gov/cms/qpp/conversion/api/helper/JwtTestHelper.java b/rest-api/src/test/java/gov/cms/qpp/conversion/api/helper/JwtTestHelper.java index 2918204c9..da2c97af1 100644 --- a/rest-api/src/test/java/gov/cms/qpp/conversion/api/helper/JwtTestHelper.java +++ b/rest-api/src/test/java/gov/cms/qpp/conversion/api/helper/JwtTestHelper.java @@ -10,13 +10,9 @@ import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; public class JwtTestHelper { - private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256; - public static String createJwt(JwtPayloadHelper payload) { Map claimMap = createClaimMap(payload); @@ -46,15 +42,15 @@ private static Map createClaimMap(JwtPayloadHelper payload) { } private static JwtBuilder createJwtBuilderWithClaimMap(Map claimMap) { - SecretKey signingKey = Keys.secretKeyFor(SIGNATURE_ALGORITHM); // TEST KEY + SecretKey signingKey = Jwts.SIG.HS256.key().build(); // TEST KEY LocalDate now = LocalDate.now(); - LocalDate expirationDate = LocalDate.of(2025, 12, 31); + LocalDate expirationDate = LocalDate.of(2030, 12, 31); return Jwts.builder() - .setIssuedAt(valueOf(now)) - .setClaims(claimMap) - .setIssuer("testing-org") - .setExpiration(valueOf(expirationDate)) - .signWith(signingKey, SIGNATURE_ALGORITHM); + .issuedAt(valueOf(now)) + .claims(claimMap) + .issuer("testing-org") + .expiration(valueOf(expirationDate)) + .signWith(signingKey); } } diff --git a/rest-api/src/test/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImplTest.java b/rest-api/src/test/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImplTest.java index a4330759b..efd78fd2a 100644 --- a/rest-api/src/test/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImplTest.java +++ b/rest-api/src/test/java/gov/cms/qpp/conversion/api/services/internal/ValidationServiceImplTest.java @@ -218,20 +218,18 @@ void testCheckForValidationUrlVariableLoggingIfAbsent() { } @Test - void testNoHandlingErrorHandlerDoesNothing() throws IOException { - try { - Class innerClass = Class.forName("gov.cms.qpp.conversion.api.services.internal.ValidationServiceImpl$NoHandlingErrorHandler"); - java.lang.reflect.Constructor constructor = innerClass.getDeclaredConstructor(); - constructor.setAccessible(true); - Object errorHandler = constructor.newInstance(); - - java.lang.reflect.Method method = innerClass.getDeclaredMethod("handleError", org.springframework.http.client.ClientHttpResponse.class); - method.setAccessible(true); - - // This should not throw an exception - method.invoke(errorHandler, mock(org.springframework.http.client.ClientHttpResponse.class)); - } catch (Exception e) { - throw new RuntimeException("Failed to test private NoHandlingErrorHandler", e); - } + void testNoOpResponseErrorHandlerDoesNothing() throws Exception { + // NoHandlingErrorHandler was replaced with Spring's built-in NoOpResponseErrorHandler. + // Verify it never flags a response as an error regardless of status code. + org.springframework.web.client.NoOpResponseErrorHandler handler = + new org.springframework.web.client.NoOpResponseErrorHandler(); + + org.springframework.http.client.ClientHttpResponse mockResponse = + mock(org.springframework.http.client.ClientHttpResponse.class); + when(mockResponse.getStatusCode()) + .thenReturn(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR); + + assertThat("NoOpResponseErrorHandler must not flag errors", + handler.hasError(mockResponse), is(false)); } } diff --git a/test-coverage/pom.xml b/test-coverage/pom.xml index b3e93cbc9..0226c624a 100644 --- a/test-coverage/pom.xml +++ b/test-coverage/pom.xml @@ -22,7 +22,6 @@ com.fasterxml.jackson.core jackson-core - 2.21.4