Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Engagement/jessica/15406 cli download report #15675

Merged
merged 41 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9dd54c3
Creating custom FHIR function that returns a random value for various…
JessicaWNava Aug 6, 2024
2b58146
Removing logging
JessicaWNava Aug 6, 2024
46bf5c4
Addressing PR feedbakc and doing some cleanup after further testing
JessicaWNava Aug 8, 2024
8cd7f3a
Adding in any missing types that were required per https://hl7-defini…
JessicaWNava Aug 12, 2024
94ca3dc
Starting work on using the fake values to remove PII froma bundle
JessicaWNava Aug 13, 2024
e07c4f7
PII removal commit 2
JessicaWNava Aug 14, 2024
a890872
pii removal number 3
JessicaWNava Aug 15, 2024
7064ee6
Moving PII removal over to code
JessicaWNava Aug 19, 2024
9c06530
Removing AOEs and updating condition filter to include RSV
JessicaWNava Aug 20, 2024
1dad44f
Merging changes
JessicaWNava Aug 20, 2024
51a3fd3
Changing to use the transform again instead of code for all but id
JessicaWNava Aug 22, 2024
d9a10fe
Merge branch 'master' of https://github.com/CDCgov/prime-reportstream
JessicaWNava Aug 22, 2024
cdf4f2d
Merging changes from master
JessicaWNava Aug 22, 2024
fc560af
Added ability to set properties with index
victor-chaparro Aug 22, 2024
4c9916c
PII removal combo of transform and code
JessicaWNava Aug 22, 2024
8abde73
create documentation for hl7-fhir transforms
GilmoreA6 Aug 22, 2024
5a6131b
Revert "create documentation for hl7-fhir transforms"
GilmoreA6 Aug 22, 2024
7bf0da2
Created a command that downloads files from a specified environment a…
JessicaWNava Aug 23, 2024
824f95f
Removing scratch file
JessicaWNava Aug 23, 2024
4b2cc1e
Merge branch 'engagement/jessica/15405-deidentification-enrichment' o…
JessicaWNava Aug 23, 2024
6c332da
Tests added for PII removal
JessicaWNava Aug 27, 2024
56a658e
Added a comment for each method. Removed unnecessary id PII replacements
JessicaWNava Aug 27, 2024
8187239
Merging in changes from the parent branch
JessicaWNava Aug 27, 2024
2f4a638
Added unit tests for the download report cli command
JessicaWNava Aug 27, 2024
2152c10
Merge branch 'master' of https://github.com/CDCgov/prime-reportstream
JessicaWNava Aug 28, 2024
3250078
Removing unnecessary file:
JessicaWNava Aug 29, 2024
9d35208
Updated the downlaod report to use Database Access instead of Workflo…
JessicaWNava Aug 29, 2024
f72074c
Merge branch 'master' of https://github.com/CDCgov/prime-reportstream
JessicaWNava Aug 29, 2024
2278f87
Merging changes from master
JessicaWNava Aug 29, 2024
561d63d
Moving download report over from being A CLI command to being an API
JessicaWNava Sep 3, 2024
b0656ac
Finished updating the tests
JessicaWNava Sep 3, 2024
5f0d760
Changing how ReportFunction is initialized in the tests
JessicaWNava Sep 4, 2024
46fbf8f
Updated how we do security for the endpoint and fixed a test
JessicaWNava Sep 4, 2024
3d414bb
Fixing tests
JessicaWNava Sep 4, 2024
994b97f
Fixing remove pii address
JessicaWNava Sep 4, 2024
f515b1b
Fixing a FHIR path
JessicaWNava Sep 4, 2024
e6cebab
Merge branch 'master' of https://github.com/CDCgov/prime-reportstream
JessicaWNava Sep 4, 2024
aee1237
Merge branch 'master' of https://github.com/CDCgov/prime-reportstream
JessicaWNava Sep 4, 2024
0f782ec
Merging changes from master
JessicaWNava Sep 4, 2024
9b664ae
Fixing function name of an API call accidentally duplicated
JessicaWNava Sep 6, 2024
a97f505
Merge branch 'master' into engagement/jessica/15406-cli-download-report
JessicaWNava Sep 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions prime-router/docs/api/reports.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,36 @@ paths:
$ref: '#/components/schemas/Report'
'500':
description: Internal Server Error
/reports/download:
get:
summary: Downloads a message based on the report id
security:
- OAuth2: [ system_admin ]
parameters:
- in: query
name: reportId
description: The report id to look for to download.
schema:
type: string
required: true
example: e491f4fb-f2c5-4473-8db2-206ea04991e8
- in: query
name: removePII
description: Boolean that determines if PII will be removed from the message. If missing will default to true.
Required to be true if prod env.
required: false
schema:
type: boolean
example: true
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Report'
'500':
description: Internal Server Error
# Building
components:
schemas:
Expand Down
69 changes: 69 additions & 0 deletions prime-router/src/main/kotlin/azure/ReportFunction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import gov.cdc.prime.router.ActionLogLevel
import gov.cdc.prime.router.InvalidParamMessage
import gov.cdc.prime.router.InvalidReportMessage
import gov.cdc.prime.router.Options
import gov.cdc.prime.router.ReportId
import gov.cdc.prime.router.Sender
import gov.cdc.prime.router.Sender.ProcessingType
import gov.cdc.prime.router.SubmissionReceiver
Expand All @@ -29,14 +30,18 @@ import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService
import gov.cdc.prime.router.azure.observability.event.ReportStreamEventName
import gov.cdc.prime.router.azure.observability.event.ReportStreamEventProperties
import gov.cdc.prime.router.azure.observability.event.ReportStreamEventService
import gov.cdc.prime.router.cli.PIIRemovalCommands
import gov.cdc.prime.router.common.AzureHttpUtils.getSenderIP
import gov.cdc.prime.router.common.Environment
import gov.cdc.prime.router.common.JacksonMapperUtilities
import gov.cdc.prime.router.fhirengine.utils.FhirTranscoder
import gov.cdc.prime.router.history.azure.SubmissionsFacade
import gov.cdc.prime.router.tokens.AuthenticatedClaims
import gov.cdc.prime.router.tokens.Scope
import gov.cdc.prime.router.tokens.authenticationFailure
import gov.cdc.prime.router.tokens.authorizationFailure
import org.apache.logging.log4j.kotlin.Logging
import java.util.UUID

private const val PROCESSING_TYPE_PARAMETER = "processing"

Expand Down Expand Up @@ -155,6 +160,70 @@ class ReportFunction(
var reportBody: String,
)

/**
* GET report to download
*
* @see ../../../docs/api/reports.yml
*/
@FunctionName("downloadReport")
fun downloadReport(
@HttpTrigger(
name = "downloadReport",
methods = [HttpMethod.GET],
authLevel = AuthorizationLevel.FUNCTION,
route = "reports/download"
) request: HttpRequestMessage<String?>,
): HttpResponseMessage {
val reportId = request.queryParameters[REPORT_ID_PARAMETER]
val removePIIRaw = request.queryParameters[REMOVE_PII]
var removePII = false
if (removePIIRaw.isNullOrBlank() || removePIIRaw.toBoolean()) {
removePII = true
}
if (reportId.isNullOrBlank()) {
return HttpUtilities.badRequestResponse(request, "Must provide a reportId.")
}
return processDownloadReport(
request,
ReportId.fromString(reportId),
removePII,
Environment.get().envName
)
}

fun processDownloadReport(
request: HttpRequestMessage<String?>,
reportId: UUID,
removePII: Boolean?,
envName: String,
databaseAccess: DatabaseAccess = DatabaseAccess(),
piiRemovalCommands: PIIRemovalCommands = PIIRemovalCommands(),
): HttpResponseMessage {
val requestedReport = databaseAccess.fetchReportFile(reportId)

return if (requestedReport.bodyUrl != null && requestedReport.bodyUrl.toString().lowercase().endsWith("fhir")) {
val contents = BlobAccess.downloadBlobAsByteArray(requestedReport.bodyUrl)

val content = if (removePII == null || removePII) {
piiRemovalCommands.removePii(FhirTranscoder.decode(contents.toString(Charsets.UTF_8)))
} else {
if (envName == "prod") {
return HttpUtilities.badRequestResponse(request, "Must remove PII for messages from prod.")
}

val jsonObject = JacksonMapperUtilities.defaultMapper
.readValue(contents.toString(Charsets.UTF_8), Any::class.java)
JacksonMapperUtilities.defaultMapper.writeValueAsString(jsonObject)
}

HttpUtilities.okJSONResponse(request, content)
} else if (requestedReport.bodyUrl == null) {
HttpUtilities.badRequestResponse(request, "The requested report does not exist.")
} else {
HttpUtilities.badRequestResponse(request, "The requested report is not fhir.")
}
}

/**
* The Waters API, in memory of Dr. Michael Waters
* (The older version of this API is "/api/reports")
Expand Down
2 changes: 2 additions & 0 deletions prime-router/src/main/kotlin/azure/RequestFunction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const val ALLOW_DUPLICATES_PARAMETER = "allowDuplicate"
const val TOPIC_PARAMETER = "topic"
const val SCHEMA_PARAMETER = "schema"
const val FORMAT_PARAMETER = "format"
const val REPORT_ID_PARAMETER = "reportId"
const val REMOVE_PII = "removePII"

/**
* Base class for ReportFunction and ValidateFunction
Expand Down
50 changes: 28 additions & 22 deletions prime-router/src/main/kotlin/cli/PIIRemovalCommands.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,16 @@ class PIIRemovalCommands : CliktCommand(
if (inputFile.extension.uppercase() != "FHIR") {
throw CliktError("File ${inputFile.absolutePath} is not a FHIR file.")
}
var bundle = FhirTranscoder.decode(contents)
val bundle = FhirTranscoder.decode(contents)

// Write the output to the screen or a file.
if (outputFile != null) {
outputFile!!.writeText(removePii(bundle), Charsets.UTF_8)
}
echo("Wrote output to ${outputFile!!.absolutePath}")
}

fun removePii(bundle: Bundle): String {
bundle.entry.map { it.resource }.filterIsInstance<Patient>()
.forEach { patient ->
patient.name.forEach { name ->
Expand All @@ -76,16 +84,16 @@ class PIIRemovalCommands : CliktCommand(

bundle.entry.map { it.resource }.filterIsInstance<Organization>()
.forEach { organization ->
organization.address.forEach { address ->
address.line = mutableListOf(StringType(getFakeValueForElementCall("STREET")))
}
organization.telecom.forEach { telecom ->
handleTelecom(telecom)
}
organization.contact.forEach { contact ->
handleOrganizationalContact(contact)
organization.address.forEach { address ->
address.line = mutableListOf(StringType(getFakeValueForElementCall("STREET")))
}
organization.telecom.forEach { telecom ->
handleTelecom(telecom)
}
organization.contact.forEach { contact ->
handleOrganizationalContact(contact)
}
}
}

bundle.entry.map { it.resource }.filterIsInstance<Practitioner>()
.forEach { practitioner ->
Expand All @@ -103,18 +111,14 @@ class PIIRemovalCommands : CliktCommand(
}
}

bundle = FhirTransformer("classpath:/metadata/fhir_transforms/common/remove-pii-enrichment.yml").process(bundle)
val bundleAfterTransform = FhirTransformer(
"classpath:/metadata/fhir_transforms/common/remove-pii-enrichment.yml"
).process(bundle)

val jsonObject = JacksonMapperUtilities.defaultMapper
.readValue(FhirTranscoder.encode(bundle), Any::class.java)
var prettyText = JacksonMapperUtilities.defaultMapper.writeValueAsString(jsonObject)
prettyText = replaceIds(bundle, prettyText)

// Write the output to the screen or a file.
if (outputFile != null) {
outputFile!!.writeText(prettyText, Charsets.UTF_8)
}
echo("Wrote output to ${outputFile!!.absolutePath}")
.readValue(FhirTranscoder.encode(bundleAfterTransform), Any::class.java)
val prettyText = JacksonMapperUtilities.defaultMapper.writeValueAsString(jsonObject)
return replaceIds(bundleAfterTransform, prettyText)
}

/**
Expand Down Expand Up @@ -185,8 +189,10 @@ class PIIRemovalCommands : CliktCommand(
bundle,
path
).forEach { resourceId ->
val newIdentifier = getFakeValueForElementCall("UUID")
return prettyText.replace(resourceId.primitiveValue(), newIdentifier, true)
if (resourceId.primitiveValue() != null) {
val newIdentifier = getFakeValueForElementCall("UUID")
return prettyText.replace(resourceId.primitiveValue(), newIdentifier, true)
}
}
return prettyText
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ elements:
# removing the street address is more complicated because it is a list so we will do this in code

- name: pii-removal-street-address2
condition: '%resource.extension("https://reportstream.cdc.gov/fhir/StructureDefinition/xad-address").extension.where(url = "XAD.2")'
value: [ 'getFakeValueForElement("STREET_ADDRESS_2")' ]
bundleProperty: '%resource.extension(%`rsext-xad-address`).extension.where(url = "XAD.2").value'
bundleProperty: '%resource.extension("https://reportstream.cdc.gov/fhir/StructureDefinition/xad-address").extension.where(url = "XAD.2").value'

- name: pii-removal-city
value: [ 'getFakeValueForElement("CITY",%resource.state)' ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ elements:
# removing a given name is more complicated because it is a list so we will do this in code

- name: pii-removal-middle-name
condition: '%resource.extension("https://reportstream.cdc.gov/fhir/StructureDefinition/xpn-human-name").exists()'
value: [ 'getFakeValueForElement("PERSON_GIVEN_NAME")' ]
bundleProperty: '%resource.extension(%`rsext-xpn-human-name`).extension.where(url="XPN.3").value'
bundleProperty: '%resource.extension("https://reportstream.cdc.gov/fhir/StructureDefinition/xpn-human-name").extension("XPN.2").value[x]'
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ elements:
- name: pii-removal-phone-area-code
condition: "%resource.where(system = 'phone')"
value: [ 'getFakeValueForElement("TELEPHONE").substring(0,3)' ]
bundleProperty: '%resource.extension(%`ext-contactpoint-area`).value'
bundleProperty: '%resource.extension(`https://reportstream.cdc.gov/fhir/StructureDefinition/contactpoint-area`).value[x]'

- name: pii-removal-local-phone
condition: "%resource.where(system = 'phone')"
Expand Down
Loading
Loading