diff --git a/src/main/java/gov/cms/mat/cql_elm_translation/config/security/SecurityConfig.java b/src/main/java/gov/cms/mat/cql_elm_translation/config/security/SecurityConfig.java index 4c8628c8..ff4c88f2 100644 --- a/src/main/java/gov/cms/mat/cql_elm_translation/config/security/SecurityConfig.java +++ b/src/main/java/gov/cms/mat/cql_elm_translation/config/security/SecurityConfig.java @@ -1,6 +1,7 @@ package gov.cms.mat.cql_elm_translation.config.security; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; @@ -9,15 +10,26 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private static final String[] AUTH_WHITELIST = { - "/v3/api-docs/**", "/swagger/**", "/swagger-ui/**", "/actuator/**" + "/v3/api-docs/**", + "/swagger/**", + "/swagger-ui/**", + "/actuator/**", + "/mat/translator/cqlToElm/**" // other public endpoints of your API may be appended to this array }; + private static final String[] CSRF_WHITELIST = {"/mat/translator/cqlToElm/**"}; + @Override protected void configure(HttpSecurity http) throws Exception { http.cors() + .and() + .csrf() + .ignoringAntMatchers(CSRF_WHITELIST) .and() .authorizeRequests() + .antMatchers(HttpMethod.PUT, "/mat/translator/cqlToElm/**") + .permitAll() .antMatchers(AUTH_WHITELIST) .permitAll() .and() diff --git a/src/main/java/gov/cms/mat/cql_elm_translation/controllers/MatMeasureController.java b/src/main/java/gov/cms/mat/cql_elm_translation/controllers/MatMeasureController.java new file mode 100644 index 00000000..a1cf6de1 --- /dev/null +++ b/src/main/java/gov/cms/mat/cql_elm_translation/controllers/MatMeasureController.java @@ -0,0 +1,84 @@ +package gov.cms.mat.cql_elm_translation.controllers; + +import javax.servlet.http.HttpServletRequest; + +import org.cqframework.cql.cql2elm.LibraryBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gov.cms.mat.cql.dto.CqlConversionPayload; +import gov.cms.mat.cql_elm_translation.controllers.CqlConversionController.TranslatorOptionsRemover; +import gov.cms.mat.cql_elm_translation.data.RequestData; +import gov.cms.mat.cql_elm_translation.service.CqlConversionService; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequestMapping(path = "/mat/translator") +@Tag(name = "Transfer-Translator", description = "API for translating MAT transferred measure CQL to ELM.") +@Slf4j +@RequiredArgsConstructor +public class MatMeasureController { + + private final CqlConversionService cqlConversionService; + + @PutMapping(path = "/cqlToElm", consumes = "text/plain", produces = "application/elm+json") + @PreAuthorize("#request.getHeader('api-key') == #apiKey") + public CqlConversionPayload cqlToElmJsonForMatTransferredMeasure( + @RequestBody String cqlData, + @RequestParam(required = false) LibraryBuilder.SignatureLevel signatures, + @RequestParam(defaultValue = "false") Boolean showWarnings, + @RequestParam(defaultValue = "true") Boolean annotations, + @RequestParam(defaultValue = "true") Boolean locators, + @RequestParam(value = "disable-list-demotion", defaultValue = "true") + Boolean disableListDemotion, + @RequestParam(value = "disable-list-promotion", defaultValue = "true") + Boolean disableListPromotion, + @RequestParam(value = "disable-method-invocation", defaultValue = "false") + Boolean disableMethodInvocation, + @RequestParam(value = "validate-units", defaultValue = "true") Boolean validateUnits, + @RequestParam(value = "result-types", defaultValue = "true") Boolean resultTypes, + HttpServletRequest request, + @Value("${lambda-api-key}") String apiKey) { + + log.debug("Entering cqlToElmJsonForMatTransferredMeasure()"); + String apikey = request.getHeader("api-key"); + String harpid = request.getHeader("harp-id"); + if (apikey == null || harpid == null || !apiKey.equals(request.getHeader("api-key"))) { + return CqlConversionPayload.builder() + .json("{\"errorExceptions\": [{\"Error\":\"UNAUTHORIZED\"}]}") + .build(); + } + + RequestData requestData = + RequestData.builder() + .cqlData(cqlData) + .showWarnings(showWarnings) + .signatures(signatures) + .annotations(annotations) + .locators(locators) + .disableListDemotion(disableListDemotion) + .disableListPromotion(disableListPromotion) + .disableMethodInvocation(disableMethodInvocation) + .validateUnits(validateUnits) + .resultTypes(resultTypes) + .build(); + + cqlConversionService.setUpLibrarySourceProvider(cqlData, apiKey.concat("-" + harpid)); + + CqlConversionPayload cqlConversionPayload = + cqlConversionService.processCqlDataWithErrors(requestData); + // Todo Do we need to remove empty annotations from library object, Also why are we removing + // translatorOptions from annotations, Could be MAT specific. + TranslatorOptionsRemover remover = new TranslatorOptionsRemover(cqlConversionPayload.getJson()); + String cleanedJson = remover.clean(); + cqlConversionPayload.setJson(cleanedJson); + return cqlConversionPayload; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index cb2db89f..fac0160d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -44,3 +44,5 @@ okta: oauth2: issuer: ${OKTA_ISSUER:https://dev-18092578.okta.com/oauth2/default} audience: ${OKTA_AUDIENCE:api://default} + +lambda-api-key: ${LAMBDA_API_KEY:9202c9fa} \ No newline at end of file diff --git a/src/test/java/gov/cms/mat/cql_elm_translation/controllers/MatMeasureControllerTest.java b/src/test/java/gov/cms/mat/cql_elm_translation/controllers/MatMeasureControllerTest.java new file mode 100644 index 00000000..c81d2d4c --- /dev/null +++ b/src/test/java/gov/cms/mat/cql_elm_translation/controllers/MatMeasureControllerTest.java @@ -0,0 +1,63 @@ +package gov.cms.mat.cql_elm_translation.controllers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import javax.servlet.http.HttpServletRequest; + +import org.cqframework.cql.cql2elm.CqlTranslator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import gov.cms.mat.cql.dto.CqlConversionPayload; +import gov.cms.mat.cql_elm_translation.ResourceFileUtil; +import gov.cms.mat.cql_elm_translation.data.RequestData; +import gov.cms.mat.cql_elm_translation.service.CqlConversionService; + +@ExtendWith(MockitoExtension.class) +public class MatMeasureControllerTest implements ResourceFileUtil { + + @Mock private CqlConversionService cqlConversionService; + @Mock private CqlTranslator cqlTranslator; + @Mock private HttpServletRequest request; + @InjectMocks private MatMeasureController matMeasureController; + + @Test + public void testCqlToElmJsonForMatTransferredMeasureSuccess() { + String cqlData = getData("/cv_populations.cql"); + String result = getData("/cv_populations.json"); + CqlConversionPayload payload = CqlConversionPayload.builder().json(result).build(); + when(cqlConversionService.processCqlDataWithErrors(any(RequestData.class))).thenReturn(payload); + + when(request.getHeader("api-key")).thenReturn("key4api"); + when(request.getHeader("harp-id")).thenReturn("test"); + CqlConversionPayload cqlConversionPayload = + matMeasureController.cqlToElmJsonForMatTransferredMeasure( + cqlData, null, true, true, true, true, true, true, true, true, request, "key4api"); + + assertEquals(result, cqlConversionPayload.getJson()); + verify(cqlConversionService).processCqlDataWithErrors(any()); + } + + @Test + public void testCqlToElmJsonForMatTransferredMeasureFail() { + String cqlData = getData("/cv_populations.cql"); + String result = getData("/cv_populations.json"); + + when(request.getHeader("api-key")).thenReturn("key4api"); + when(request.getHeader("harp-id")).thenReturn("test2"); + CqlConversionPayload cqlConversionPayload = + matMeasureController.cqlToElmJsonForMatTransferredMeasure( + cqlData, null, true, true, true, true, true, true, true, true, request, "test3"); + + assertNotEquals(result, cqlConversionPayload.getJson()); + assertTrue(cqlConversionPayload.getJson().contains("UNAUTHORIZED")); + } +}