From a5f38b1cc602eb6ec4a9d5cf53d87c05a4e3a43a Mon Sep 17 00:00:00 2001 From: Brick Green Date: Mon, 23 Sep 2024 15:53:53 -0400 Subject: [PATCH 1/7] remove ReportRouteEvent.kt --- .../docs/observability/azure-events.md | 159 +++--------------- .../universal-pipeline/receiver-filter.md | 3 +- .../observability/event/ReportRouteEvent.kt | 20 --- 3 files changed, 20 insertions(+), 162 deletions(-) delete mode 100644 prime-router/src/main/kotlin/azure/observability/event/ReportRouteEvent.kt diff --git a/prime-router/docs/observability/azure-events.md b/prime-router/docs/observability/azure-events.md index 07fcbaaeb6a..d7b6b6a9bbb 100644 --- a/prime-router/docs/observability/azure-events.md +++ b/prime-router/docs/observability/azure-events.md @@ -45,115 +45,6 @@ class MyService( } ``` -Under the hood, it will serialize your event class and push the event -to the configured Microsoft AppInsights instance. - -## Event Glossery - -### ReportCreatedEvent -This event is emitted by the convert step when a report is successfully translated into a FHIR bundle. -- reportId - - The ID assigned to the created report -- topic - - The topic of the created report - - -### ReportAcceptedEvent -This event is emitted by the destination filter step, _before_ any filters are evaluated -- reportId - - The report ID from the preceding function (convert step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- observations - - A list of observations each containing a list of its mapped conditions -- bundleSize - - Length of the bundle JSON string -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - -### ReportNotRoutedEvent -This is event is emitted by the destination filter step if a bundle not routed to any receivers. - -- reportId - - The ID of the empty report that terminated this lineage -- parentReportId - - The report ID from the preceding function (convert step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- bundleSize - - Length of the bundle JSON string -- failingFilters - - A list of all the filters that failed causing this report not the be routed -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - -### ReportRouteEvent -This event is emitted by the receiver filter step, _after_ all filters have passed and a report has been -routed to a receiver. Many `ReportRouteEvent` can correspond to a `ReportAcceptedEvent` and can be "joined" on: - -`ReportAcceptedEvent.reportId == ReportRouteEvent.parentReportId` - -- reportId - - The ID of the report routed to the receiver -- parentReportId - - The report ID from the preceding function (destination filter step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- receiver - - The full receiver name. (deprecated: When a report does not get routed to a receiver this value will be `"null"`) -- observations - - A list of observations each containing a list of its mapped conditions -- (deprecated) originalObservations - - (deprecated) A list of observations in the originally submitted report, before any filters were run -- filteredObservations - - A list of observations that were filtered from the bundle during filtering -- bundleSize - - Length of the bundle JSON string -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - -### ReceiverFilterFailedEvent -This event is emitted by the receiver filter step if a bundle fails a receiver filter. - -- reportId - - The ID of the empty report that terminated this lineage -- parentReportId - - The report ID from the preceding function (destination filter step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- receiver - - The full receiver name. -- observations - - A list of observations each containing a list of its mapped conditions -- failingFilters - - A list of all the filters that failed for this report -- failingFilterType - - The type of filter that failed this report -- bundleSize - - Length of the bundle JSON string -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - ## How to query for events Events that are pushed to Azure can be found in the `customEvents` table in the log explorer. The properties defined in @@ -176,24 +67,24 @@ customEvents ### Distinct senders ``` customEvents -| where name == "ReportAcceptedEvent" -| extend sender = tostring(customDimensions.sender) -| distinct sender +| where name == "REPORT_RECEIVED" +| extend senderName = tostring(parse_json(tostring(customDimensions.params)).senderName) +| distinct senderName ``` ### Get report count sent by sender ``` customEvents -| where name == "ReportAcceptedEvent" -| extend sender = tostring(customDimensions.sender) -| summarize count() by sender +| where name == "REPORT_RECEIVED" +| extend senderName = tostring(parse_json(tostring(customDimensions.params)).senderName) +| summarize count() by senderName | order by count_ ``` ### Get report count sent by topic ``` customEvents -| where name == "ReportAcceptedEvent" +| where name == "REPORT_RECEIVED" | extend topic = tostring(customDimensions.topic) | summarize count() by topic | order by count_ @@ -202,12 +93,8 @@ customEvents ### Get reportable conditions count for all reports sent to Report Stream ``` customEvents -| where name == "ReportAcceptedEvent" -| extend observations = parse_json(tostring(customDimensions.observations)) -| mv-expand observations -| extend conditions = parse_json(tostring(observations.conditions)) -| mv-expand conditions -| extend conditionDisplay = tostring(conditions.display) +| where name == "ITEM_ACCEPTED" +| extend conditionDisplay = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(customDimensions.params)).bundleDigest)).observationSummaries))[0].testSummary))[0].conditions))[0].display) | summarize count() by conditionDisplay | order by count_ ``` @@ -215,28 +102,25 @@ customEvents ### Distinct receivers ``` customEvents -| where name == "ReportRouteEvent" -| extend receiver = tostring(customDimensions.receiver) -| where receiver != "null" -| distinct receiver +| where name == "ITEM_ROUTED" +| extend receiverName = tostring(parse_json(tostring(customDimensions.params)).receiverName) +| distinct receiverName ``` ### Get report count routed to a receiver ``` customEvents -| where name == "ReportRouteEvent" -| extend receiver = tostring(customDimensions.receiver) -| where receiver != "null" -| summarize count() by receiver +| where name == "ITEM_ROUTED" +| extend receiverName = tostring(parse_json(tostring(customDimensions.params)).receiverName) +| summarize count() by receiverName | order by count_ ``` ### Get report count routed by topic ``` customEvents -| where name == "ReportRouteEvent" -| extend topic = tostring(customDimensions.topic), receiver = tostring(customDimensions.receiver) -| where receiver != "null" +| where name == "ITEM_ROUTED" +| extend topic = tostring(customDimensions.topic) | summarize count() by topic | order by count_ ``` @@ -244,13 +128,8 @@ customEvents ### Get reportable conditions count for all reports routed to receivers ``` customEvents -| where name == "ReportRouteEvent" -| extend observations = parse_json(tostring(customDimensions.observations)), receiver = tostring(customDimensions.receiver) -| where receiver != "null" -| mv-expand observations -| extend conditions = parse_json(tostring(observations.conditions)) -| mv-expand conditions -| extend conditionDisplay = tostring(conditions.display) +| where name == "ITEM_ROUTED" +| extend conditionDisplay = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(customDimensions.params)).bundleDigest)).observationSummaries))[0].testSummary))[0].conditions))[0].display) | summarize count() by conditionDisplay | order by count_ ``` diff --git a/prime-router/docs/universal-pipeline/receiver-filter.md b/prime-router/docs/universal-pipeline/receiver-filter.md index 3c0de1e403d..e98957961f0 100644 --- a/prime-router/docs/universal-pipeline/receiver-filter.md +++ b/prime-router/docs/universal-pipeline/receiver-filter.md @@ -294,8 +294,7 @@ This step emits one of the below events _per receiver_ each time it runs. | Event | Trigger | |-----------------------------------------------------------------------------------------|------------------------------------------------------------| -| [ReportRouteEvent](../observability/azure-events.md#reportrouteevent) | When a report is routed to a receiver (all filters passed) | -| [ReceiverFilterFailedEvent](../observability/azure-events.md#receiverfilterfailedevent) | When a report fails receiver filters | +| ITEM_FILTER_FAILED | When a report fails receiver filters | ## Retries diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportRouteEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportRouteEvent.kt deleted file mode 100644 index 4479c753fa7..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportRouteEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report gets routed to a receiver - */ -data class ReportRouteEvent( - val reportId: ReportId, - val parentReportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val receiver: String?, // TODO: this should not be nullable anymore after #14450 - val observations: List, - val filteredObservations: List, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file From 39df98dbfebc88ddde0f32e4a34b12277333db24 Mon Sep 17 00:00:00 2001 From: Brick Green Date: Tue, 24 Sep 2024 09:30:46 -0400 Subject: [PATCH 2/7] remove events and update md --- .../universal-pipeline/destination-filter.md | 8 +++---- .../event/ReceiverFilterFailedEvent.kt | 23 ------------------- .../event/ReportAcceptedEvent.kt | 20 ---------------- .../observability/event/ReportCreatedEvent.kt | 12 ---------- .../event/ReportNotRoutedEvent.kt | 18 --------------- 5 files changed, 4 insertions(+), 77 deletions(-) delete mode 100644 prime-router/src/main/kotlin/azure/observability/event/ReceiverFilterFailedEvent.kt delete mode 100644 prime-router/src/main/kotlin/azure/observability/event/ReportAcceptedEvent.kt delete mode 100644 prime-router/src/main/kotlin/azure/observability/event/ReportCreatedEvent.kt delete mode 100644 prime-router/src/main/kotlin/azure/observability/event/ReportNotRoutedEvent.kt diff --git a/prime-router/docs/universal-pipeline/destination-filter.md b/prime-router/docs/universal-pipeline/destination-filter.md index fd9162c4d38..21fbe27e878 100644 --- a/prime-router/docs/universal-pipeline/destination-filter.md +++ b/prime-router/docs/universal-pipeline/destination-filter.md @@ -186,10 +186,10 @@ This filter will log messages to the console when: This step emits one of two events below _once_ each time it runs. -| Event | Trigger | -|-------------------------------------------------------------------------------|------------------------------------------------------------------| -| [ReportAcceptedEvent](../observability/azure-events.md#reportacceptedevent) | when a report is received by this step (precludes all filtering) | -| [ReportNotRoutedEvent](../observability/azure-events.md#reportnotroutedevent) | when a report is not valid for any receivers | +| Event | Trigger | +|-----------------|------------------------------------------------------------------| +| ITEM_ROUTED | when a report is received by this step (precludes all filtering) | +| ITEM_NOT_ROUTED | when a report is not valid for any receivers | ## Retries diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReceiverFilterFailedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReceiverFilterFailedEvent.kt deleted file mode 100644 index 6f915d64bdf..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReceiverFilterFailedEvent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.ReportStreamFilterType -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report fails a receiver's filters - */ - -data class ReceiverFilterFailedEvent( - val reportId: ReportId, - val parentReportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val receiver: String, - val observations: List, - val failingFilters: List, - val failingFilterType: ReportStreamFilterType, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportAcceptedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportAcceptedEvent.kt deleted file mode 100644 index 0eed89d94aa..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportAcceptedEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report is ready to be processed per receiver - * - * This event should contain all observations sent by the sender since no - * receiver specific filters have been run - */ -data class ReportAcceptedEvent( - val reportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val observations: List, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportCreatedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportCreatedEvent.kt deleted file mode 100644 index f6453dad064..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportCreatedEvent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * An event emitted during every report created - */ -data class ReportCreatedEvent( - val reportId: ReportId, - val topic: Topic, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportNotRoutedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportNotRoutedEvent.kt deleted file mode 100644 index 0280e12d912..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportNotRoutedEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report does not get routed to any receivers - */ - -data class ReportNotRoutedEvent( - val reportId: ReportId, - val parentReportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file From fa93fa0f7948eb3098edea3b3dd3110bf26eff46 Mon Sep 17 00:00:00 2001 From: Brick Green Date: Tue, 24 Sep 2024 11:50:48 -0400 Subject: [PATCH 3/7] update to include queryParameter --- .../submissions/SubmissionReceivedEvent.kt | 17 ++++++++++--- .../controllers/SubmissionController.kt | 25 +++++++++++++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt index 61c348c0aa6..c29579932db 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt @@ -1,5 +1,6 @@ package gov.cdc.prime.reportstream.submissions +import com.azure.core.http.HttpMethod import java.time.Instant import java.util.UUID @@ -8,9 +9,17 @@ data class SubmissionReceivedEvent( val reportId: UUID, val parentReportId: UUID, val rootReportId: UUID, - val headers: Map, - val sender: String, - val senderIP: String, - val fileSize: String, + val requestParameters: SubmissionDetails, + val method: String, + val url: String, + val senderName: String, + val senderIp: String, + val fileLength: String, val blobUrl: String, + val pipelineStepName: String +) + +data class SubmissionDetails( + val headers: Map, + val queryParameters: Map, ) \ No newline at end of file diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt index c92e98354eb..8cb088edbf3 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt @@ -8,8 +8,10 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import gov.cdc.prime.reportstream.shared.BlobUtils import gov.cdc.prime.reportstream.shared.QueueMessage import gov.cdc.prime.reportstream.shared.Submission +import gov.cdc.prime.reportstream.submissions.SubmissionDetails import gov.cdc.prime.reportstream.submissions.SubmissionReceivedEvent import gov.cdc.prime.reportstream.submissions.TelemetryService +import jakarta.servlet.http.HttpServletRequest import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -54,6 +56,7 @@ class SubmissionController( * @param contentType the content type of the report (must be "application/hl7-v2" or "application/fhir+ndjson") * @param clientId the ID of the client submitting the report. Should represent org.senderName * @param data the report data + * @param request gives access to request details * @return a ResponseEntity containing the reportID, status, and timestamp */ @PostMapping("/api/v1/reports", consumes = ["application/hl7-v2", "application/fhir+ndjson"]) @@ -65,6 +68,7 @@ class SubmissionController( @RequestHeader("x-azure-clientip") senderIp: String, @RequestHeader(value = "payloadName", required = false) payloadName: String?, @RequestBody data: String, + request: HttpServletRequest ): ResponseEntity<*> { val reportId = UUID.randomUUID() val reportReceivedTime = Instant.now() @@ -98,11 +102,17 @@ class SubmissionController( reportId = reportId, parentReportId = reportId, rootReportId = reportId, - headers = filterHeaders(headers), - sender = clientId, - senderIP = senderIp, - fileSize = contentLength, - blobUrl = blobClient.blobUrl + requestParameters = SubmissionDetails( + filterHeaders(headers), + filterQueryParameters(request.parameterMap.mapValues { it.value.joinToString(",") }) + ), + method = request.method, + url = request.requestURL.toString(), + senderName = clientId, + senderIp = senderIp, + fileLength = contentLength, + blobUrl = blobClient.blobUrl, + pipelineStepName = "submission" ) logger.debug("Created SUBMISSION_RECEIVED") @@ -237,6 +247,11 @@ class SubmissionController( return headers.filter { it.key.lowercase() in headersToInclude } } + private fun filterQueryParameters(queryParameters: Map): Map { + val headersToInclude = emptyList() // update this list as new QueryParameters are needed + return queryParameters.filter { it.key.lowercase() in headersToInclude } + } + private fun formBlobName( reportId: UUID, contentTypeMime: String, From 33ef01326ac01bb71422c6a9f52d87b5260274d9 Mon Sep 17 00:00:00 2001 From: Brick Green Date: Tue, 24 Sep 2024 15:10:42 -0400 Subject: [PATCH 4/7] update unit test --- submissions/build.gradle.kts | 1 + .../submissions/SubmissionReceivedEvent.kt | 3 +-- .../controllers/SubmissionController.kt | 2 +- .../SubmissionControllerIntegrationTest.kt | 6 +++++ .../test/kotlin/SubmissionControllerTest.kt | 22 ++++++++++++++++--- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/submissions/build.gradle.kts b/submissions/build.gradle.kts index 5c99ee9f63e..8c96e222de5 100644 --- a/submissions/build.gradle.kts +++ b/submissions/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") testImplementation("org.apache.commons:commons-compress:1.27.1") + testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.9.0") implementation(project(":shared")) diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt index c29579932db..7a44c0b4bde 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt @@ -1,6 +1,5 @@ package gov.cdc.prime.reportstream.submissions -import com.azure.core.http.HttpMethod import java.time.Instant import java.util.UUID @@ -16,7 +15,7 @@ data class SubmissionReceivedEvent( val senderIp: String, val fileLength: String, val blobUrl: String, - val pipelineStepName: String + val pipelineStepName: String, ) data class SubmissionDetails( diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt index 8cb088edbf3..ded68788477 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt @@ -68,7 +68,7 @@ class SubmissionController( @RequestHeader("x-azure-clientip") senderIp: String, @RequestHeader(value = "payloadName", required = false) payloadName: String?, @RequestBody data: String, - request: HttpServletRequest + request: HttpServletRequest, ): ResponseEntity<*> { val reportId = UUID.randomUUID() val reportReceivedTime = Instant.now() diff --git a/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt b/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt index 50255bb05e6..dbfcd9934dd 100644 --- a/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt +++ b/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt @@ -10,6 +10,8 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import gov.cdc.prime.reportstream.shared.QueueMessage import gov.cdc.prime.reportstream.shared.QueueMessage.ObjectMapperProvider +import gov.cdc.prime.reportstream.submissions.config.AzureConfig +import gov.cdc.prime.reportstream.submissions.config.SecurityConfig import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -17,7 +19,9 @@ 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.test.context.SpringBootTest +import org.springframework.context.annotation.Import import org.springframework.http.MediaType +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource @@ -31,6 +35,7 @@ import java.util.Base64 @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc +@Import(AzureConfig::class, SecurityConfig::class) class SubmissionControllerIntegrationTest { @Autowired @@ -92,6 +97,7 @@ class SubmissionControllerIntegrationTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("client_id", "testClient") diff --git a/submissions/src/test/kotlin/SubmissionControllerTest.kt b/submissions/src/test/kotlin/SubmissionControllerTest.kt index ab1dfd84120..cc4b07acf00 100644 --- a/submissions/src/test/kotlin/SubmissionControllerTest.kt +++ b/submissions/src/test/kotlin/SubmissionControllerTest.kt @@ -12,6 +12,7 @@ import gov.cdc.prime.reportstream.shared.QueueMessage import gov.cdc.prime.reportstream.shared.QueueMessage.ObjectMapperProvider import gov.cdc.prime.reportstream.submissions.TelemetryService import gov.cdc.prime.reportstream.submissions.config.AzureConfig +import gov.cdc.prime.reportstream.submissions.config.SecurityConfig import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -33,6 +34,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.context.annotation.Import import org.springframework.http.MediaType +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.result.MockMvcResultMatchers @@ -42,7 +44,7 @@ import java.util.Base64 import java.util.UUID @WebMvcTest(SubmissionController::class) -@Import(AzureConfig::class) +@Import(AzureConfig::class, SecurityConfig::class) class SubmissionControllerTest { @Autowired @@ -126,6 +128,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("client_id", "testClient") @@ -172,6 +175,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/fhir+ndjson")) .header("client_id", "testClient") @@ -203,6 +207,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .header("client_id", "testClient") @@ -219,6 +224,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("payloadname", "testPayload") @@ -241,6 +247,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.parseMediaType("application/hl7-v2")) .header("client_id", "testClient") @@ -263,6 +270,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.parseMediaType("application/hl7-v2")) .header("client_id", "testClient") @@ -301,6 +309,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("client_id", "testClient") @@ -321,12 +330,19 @@ class SubmissionControllerTest { val eventDetails = objectMapper.readValue(capturedProperties["event"], Map::class.java) assert(eventDetails["reportId"] == reportId.toString()) assert(eventDetails["blobUrl"] == expectedBlobUrl) - assert(eventDetails["senderIP"] == "127.0.0.1") - val headers = eventDetails["headers"] as Map<*, *> + assert(eventDetails["senderIp"] == "127.0.0.1") + assert(eventDetails["method"] == "POST") + assert(eventDetails["senderName"] == "testClient") + assert(eventDetails["pipelineStepName"] == "submission") + assert(eventDetails["url"] == "http://localhost/api/v1/reports") + val requestParameters = eventDetails["requestParameters"] as Map<*, *> + val headers = requestParameters["headers"] as Map<*, *> assert(headers["client_id"] == "testClient") assert(headers["Content-Type"] == "application/hl7-v2;charset=UTF-8") assert(headers["payloadname"] == "testPayload") assert(headers["x-azure-clientip"] == "127.0.0.1") + val queryParameters = requestParameters["queryParameters"] as Map<*, *> + assert(queryParameters.isEmpty()) uuidMockedStatic.close() } From 925c812d80b5281f7bc890f5b3ca0688772efa80 Mon Sep 17 00:00:00 2001 From: Brick Green Date: Wed, 25 Sep 2024 13:41:36 -0400 Subject: [PATCH 5/7] transition to config based filtering and allow one query parameter to have multiple values --- .../submissions/SubmissionReceivedEvent.kt | 2 +- .../config/AllowedParametersConfig.kt | 32 +++++++++++++ .../controllers/SubmissionController.kt | 46 +++++++++++++++---- .../src/main/resources/application.properties | 13 +++++- .../test/kotlin/SubmissionControllerTest.kt | 5 +- 5 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt index 7a44c0b4bde..b82c0993a31 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt @@ -20,5 +20,5 @@ data class SubmissionReceivedEvent( data class SubmissionDetails( val headers: Map, - val queryParameters: Map, + val queryParameters: Map>, ) \ No newline at end of file diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt new file mode 100644 index 00000000000..d9e87fae33b --- /dev/null +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt @@ -0,0 +1,32 @@ +package gov.cdc.prime.reportstream.submissions.config + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.stereotype.Component + +/** + * Configuration class to load allowed headers and query parameters from the application properties. + * + * This class is used to read properties prefixed with "allowed" from the `application.properties` or `application.yml` + * file and bind them to the `headers` and `queryParameters` fields. + * + * Example of properties in the `application.properties` file: + * + * ``` + * allowed.headers.client_id=client_id + * allowed.headers.content_type=content-type + * allowed.queryParameters.processing=processing + * allowed.queryParameters.another_param=another + * ``` + * + * These properties will be automatically injected into the `headers` and `queryParameters` maps when the + * Spring application context is initialized. + * + * @property headers A map of allowed HTTP header names that are expected in incoming requests. + * @property queryParameters A map of allowed query parameter names that can be used in incoming requests. + */ +@Component +@ConfigurationProperties(prefix = "allowed") +class AllowedParametersConfig { + lateinit var headers: Map + lateinit var queryParameters: Map +} \ No newline at end of file diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt index ded68788477..c66257853ea 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt @@ -11,6 +11,7 @@ import gov.cdc.prime.reportstream.shared.Submission import gov.cdc.prime.reportstream.submissions.SubmissionDetails import gov.cdc.prime.reportstream.submissions.SubmissionReceivedEvent import gov.cdc.prime.reportstream.submissions.TelemetryService +import gov.cdc.prime.reportstream.submissions.config.AllowedParametersConfig import jakarta.servlet.http.HttpServletRequest import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus @@ -44,6 +45,7 @@ class SubmissionController( private val queueClient: QueueClient, private val tableClient: TableClient, private val telemetryService: TelemetryService, + private val allowedParametersConfig: AllowedParametersConfig, ) { /** * Submits a report. @@ -75,6 +77,12 @@ class SubmissionController( val contentTypeMime = contentType.substringBefore(';') val status = "Received" val objectMapper = jacksonObjectMapper().registerModule(JavaTimeModule()) + // Filter request headers based on the allowed list + val filteredHeaders = filterHeaders(headers) + + // Filter query parameters based on the allowed list (only keep 'processing' or others defined in application.properties) + val filteredQueryParameters = filterQueryParameters(request) + logger.info( "Received report submission: reportId=$reportId, contentType=$contentTypeMime" + ", clientId=$clientId${payloadName?.let { ", payloadName=$it" } ?: ""}}" @@ -103,8 +111,8 @@ class SubmissionController( parentReportId = reportId, rootReportId = reportId, requestParameters = SubmissionDetails( - filterHeaders(headers), - filterQueryParameters(request.parameterMap.mapValues { it.value.joinToString(",") }) + filteredHeaders, + filteredQueryParameters ), method = request.method, url = request.requestURL.toString(), @@ -131,7 +139,7 @@ class SubmissionController( BlobUtils.digestToString(digest), clientId.lowercase(), reportId, - filterHeaders(headers).toMap(), + filterHeaders(headers), ).serialize() logger.debug("Created message for queue") @@ -241,15 +249,35 @@ class SubmissionController( } } + /** + * Filters the request headers based on the allowed headers configured in the application.properties. + */ private fun filterHeaders(headers: Map): Map { - val headersToInclude = - listOf("client_id", "content-type", "payloadname", "x-azure-clientip", "content-length") - return headers.filter { it.key.lowercase() in headersToInclude } + val allowedHeaders = allowedParametersConfig.headers + return headers.filterKeys { key -> + allowedHeaders.values.map { it.lowercase() }.contains(key.lowercase()) + } } - private fun filterQueryParameters(queryParameters: Map): Map { - val headersToInclude = emptyList() // update this list as new QueryParameters are needed - return queryParameters.filter { it.key.lowercase() in headersToInclude } + /** + * Filters the query parameters based on the allowed query parameters configured in the application.properties. + * Handles multiple values for the same query parameter from HttpServletRequest. + */ + private fun filterQueryParameters(request: HttpServletRequest): Map> { + val allowedQueryParams = allowedParametersConfig.queryParameters + + // Create a map to hold the filtered query parameters + val filteredParams = mutableMapOf>() + + // Loop over allowed parameters and get their values from the request + allowedQueryParams.forEach { (_, paramName) -> + val values = request.getParameterValues(paramName) + if (values != null) { + filteredParams[paramName] = values.toList() // Convert array to List + } + } + + return filteredParams } private fun formBlobName( diff --git a/submissions/src/main/resources/application.properties b/submissions/src/main/resources/application.properties index a8750014f5d..5abcb738e76 100644 --- a/submissions/src/main/resources/application.properties +++ b/submissions/src/main/resources/application.properties @@ -4,4 +4,15 @@ azure.storage.connection-string=${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpoin azure.storage.container-name=${AZURE_STORAGE_CONTAINER_NAME:reports} azure.storage.queue-name=${AZURE_STORAGE_QUEUE_NAME:elr-fhir-receive} azure.storage.table-name=${AZURE_STORAGE_TABLE_NAME:submission} -spring.security.oauth2.resourceserver.jwt.issuer-uri=https://reportstream.oktapreview.com/oauth2/ausekaai7gUuUtHda1d7 \ No newline at end of file +spring.security.oauth2.resourceserver.jwt.issuer-uri=https://reportstream.oktapreview.com/oauth2/ausekaai7gUuUtHda1d7 + +# Allowed headers +allowed.headers.client_id=client_id +allowed.headers.content_type=content-type +allowed.headers.payload_name=payloadname +allowed.headers.azure_clientip=x-azure-clientip +allowed.headers.content_length=content-length + +# Allowed query parameters +# example: +# allowed.queryParameters.test=test \ No newline at end of file diff --git a/submissions/src/test/kotlin/SubmissionControllerTest.kt b/submissions/src/test/kotlin/SubmissionControllerTest.kt index cc4b07acf00..2c552872834 100644 --- a/submissions/src/test/kotlin/SubmissionControllerTest.kt +++ b/submissions/src/test/kotlin/SubmissionControllerTest.kt @@ -11,6 +11,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import gov.cdc.prime.reportstream.shared.QueueMessage import gov.cdc.prime.reportstream.shared.QueueMessage.ObjectMapperProvider import gov.cdc.prime.reportstream.submissions.TelemetryService +import gov.cdc.prime.reportstream.submissions.config.AllowedParametersConfig import gov.cdc.prime.reportstream.submissions.config.AzureConfig import gov.cdc.prime.reportstream.submissions.config.SecurityConfig import org.junit.jupiter.api.AfterEach @@ -44,7 +45,7 @@ import java.util.Base64 import java.util.UUID @WebMvcTest(SubmissionController::class) -@Import(AzureConfig::class, SecurityConfig::class) +@Import(AzureConfig::class, SecurityConfig::class, AllowedParametersConfig::class) class SubmissionControllerTest { @Autowired @@ -315,6 +316,8 @@ class SubmissionControllerTest { .header("client_id", "testClient") .header("payloadname", "testPayload") .header("x-azure-clientip", "127.0.0.1") + .queryParam("processing", "test1", "test2") + .queryParam("test", "test2") ) .andExpect(MockMvcResultMatchers.status().isCreated) From 202aa7f9c68ff5593a50a4f67b44178bb2f5d4d7 Mon Sep 17 00:00:00 2001 From: Brick Green Date: Wed, 25 Sep 2024 13:56:33 -0400 Subject: [PATCH 6/7] account for empty parameter filter list --- .../config/AllowedParametersConfig.kt | 25 +++++++++++++++++-- .../src/main/resources/application.properties | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt index d9e87fae33b..7534557f577 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt @@ -27,6 +27,27 @@ import org.springframework.stereotype.Component @Component @ConfigurationProperties(prefix = "allowed") class AllowedParametersConfig { - lateinit var headers: Map - lateinit var queryParameters: Map + /** + * A map of allowed HTTP headers that can be accepted by the API. + * The keys in this map correspond to internal property names, and the values represent the actual header names + * expected in the incoming request. + * + * For example, if the property is defined as `allowed.headers.client_id=client_id`, it will expect a header with + * the name `client_id` in the incoming HTTP request. + * + * Defaults to an empty map if not provided in the configuration. + */ + var headers: Map = emptyMap() + + /** + * A map of allowed query parameters that the API can accept in incoming requests. + * The keys in this map correspond to internal property names, and the values represent the actual query parameter + * names expected in the incoming request. + * + * For example, if the property is defined as `allowed.queryParameters.param=test1`, it will allow the + * query parameter `param` to be used in the request, and multiple values can be passed (e.g., `?param=test1¶m=test2`). + * + * Defaults to an empty map if not provided in the configuration. + */ + var queryParameters: Map = emptyMap() } \ No newline at end of file diff --git a/submissions/src/main/resources/application.properties b/submissions/src/main/resources/application.properties index 5abcb738e76..cd78186c7fe 100644 --- a/submissions/src/main/resources/application.properties +++ b/submissions/src/main/resources/application.properties @@ -15,4 +15,4 @@ allowed.headers.content_length=content-length # Allowed query parameters # example: -# allowed.queryParameters.test=test \ No newline at end of file +# allowed.queryParameters.param=param \ No newline at end of file From 0111e614747bdecdc83d4cfe37dd4f60e2324ce3 Mon Sep 17 00:00:00 2001 From: Brick Green Date: Wed, 25 Sep 2024 16:13:19 -0400 Subject: [PATCH 7/7] transition to application.yml --- .../config/AllowedParametersConfig.kt | 30 ++++++------------- .../controllers/SubmissionController.kt | 14 +++++---- .../src/main/resources/application.properties | 18 ----------- .../src/main/resources/application.yml | 27 +++++++++++++++++ 4 files changed, 45 insertions(+), 44 deletions(-) delete mode 100644 submissions/src/main/resources/application.properties create mode 100644 submissions/src/main/resources/application.yml diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt index 7534557f577..d3dab4cdccf 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt @@ -18,36 +18,24 @@ import org.springframework.stereotype.Component * allowed.queryParameters.another_param=another * ``` * - * These properties will be automatically injected into the `headers` and `queryParameters` maps when the + * These properties will be automatically injected into the `headers` and `queryParameters` lists when the * Spring application context is initialized. * - * @property headers A map of allowed HTTP header names that are expected in incoming requests. - * @property queryParameters A map of allowed query parameter names that can be used in incoming requests. + * @property headers A list of allowed HTTP header names that are expected in incoming requests. + * @property queryParameters A list of allowed query parameter names that can be used in incoming requests. */ @Component @ConfigurationProperties(prefix = "allowed") class AllowedParametersConfig { /** - * A map of allowed HTTP headers that can be accepted by the API. - * The keys in this map correspond to internal property names, and the values represent the actual header names - * expected in the incoming request. - * - * For example, if the property is defined as `allowed.headers.client_id=client_id`, it will expect a header with - * the name `client_id` in the incoming HTTP request. - * - * Defaults to an empty map if not provided in the configuration. + * A list of allowed HTTP headers that can be accepted by the API. + * Each entry in the list represents a header name expected in the incoming request. */ - var headers: Map = emptyMap() + var headers: List = emptyList() /** - * A map of allowed query parameters that the API can accept in incoming requests. - * The keys in this map correspond to internal property names, and the values represent the actual query parameter - * names expected in the incoming request. - * - * For example, if the property is defined as `allowed.queryParameters.param=test1`, it will allow the - * query parameter `param` to be used in the request, and multiple values can be passed (e.g., `?param=test1¶m=test2`). - * - * Defaults to an empty map if not provided in the configuration. + * A list of allowed query parameters that the API can accept in incoming requests. + * Each entry in the list represents a query parameter name expected in the incoming request. */ - var queryParameters: Map = emptyMap() + var queryParameters: List = emptyList() } \ No newline at end of file diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt index c66257853ea..37d92c49a74 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt @@ -250,17 +250,20 @@ class SubmissionController( } /** - * Filters the request headers based on the allowed headers configured in the application.properties. + * Filters the request headers based on the allowed headers configured in the application.yml. + * Handles the case where allowed headers are defined as a list. */ private fun filterHeaders(headers: Map): Map { val allowedHeaders = allowedParametersConfig.headers + + // Filter the request headers to only include allowed headers return headers.filterKeys { key -> - allowedHeaders.values.map { it.lowercase() }.contains(key.lowercase()) + allowedHeaders.map { it.lowercase() }.contains(key.lowercase()) } } /** - * Filters the query parameters based on the allowed query parameters configured in the application.properties. + * Filters the query parameters based on the allowed query parameters configured in the application.yml. * Handles multiple values for the same query parameter from HttpServletRequest. */ private fun filterQueryParameters(request: HttpServletRequest): Map> { @@ -270,16 +273,17 @@ class SubmissionController( val filteredParams = mutableMapOf>() // Loop over allowed parameters and get their values from the request - allowedQueryParams.forEach { (_, paramName) -> + allowedQueryParams.forEach { paramName -> val values = request.getParameterValues(paramName) if (values != null) { - filteredParams[paramName] = values.toList() // Convert array to List + filteredParams[paramName] = values.toList() // Convert array to List } } return filteredParams } + private fun formBlobName( reportId: UUID, contentTypeMime: String, diff --git a/submissions/src/main/resources/application.properties b/submissions/src/main/resources/application.properties deleted file mode 100644 index cd78186c7fe..00000000000 --- a/submissions/src/main/resources/application.properties +++ /dev/null @@ -1,18 +0,0 @@ -spring.application.name=submissions -server.port=8880 -azure.storage.connection-string=${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;QueueEndpoint=http://localhost:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;} -azure.storage.container-name=${AZURE_STORAGE_CONTAINER_NAME:reports} -azure.storage.queue-name=${AZURE_STORAGE_QUEUE_NAME:elr-fhir-receive} -azure.storage.table-name=${AZURE_STORAGE_TABLE_NAME:submission} -spring.security.oauth2.resourceserver.jwt.issuer-uri=https://reportstream.oktapreview.com/oauth2/ausekaai7gUuUtHda1d7 - -# Allowed headers -allowed.headers.client_id=client_id -allowed.headers.content_type=content-type -allowed.headers.payload_name=payloadname -allowed.headers.azure_clientip=x-azure-clientip -allowed.headers.content_length=content-length - -# Allowed query parameters -# example: -# allowed.queryParameters.param=param \ No newline at end of file diff --git a/submissions/src/main/resources/application.yml b/submissions/src/main/resources/application.yml new file mode 100644 index 00000000000..c75d070b3fa --- /dev/null +++ b/submissions/src/main/resources/application.yml @@ -0,0 +1,27 @@ +spring: + application: + name: submissions + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://reportstream.oktapreview.com/oauth2/ausekaai7gUuUtHda1d7 + server: + port: 8880 + +azure: + storage: + connection-string: ${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;QueueEndpoint=http://localhost:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;} + container-name: ${AZURE_STORAGE_CONTAINER_NAME:reports} + queue-name: ${AZURE_STORAGE_QUEUE_NAME:elr-fhir-receive} + table-name: ${AZURE_STORAGE_TABLE_NAME:submission} + +allowed: + headers: + - client_id + - content-type + - payloadname + - x-azure-clientip + - content-length +# - queryParameters: +# - param \ No newline at end of file