├── .gitignore ├── CODE_OF_CONDUCT.md ├── DICOM ├── CustomAnalysisRules.ruleset ├── Dicom.Anonymizer.sln ├── Directory.Build.props ├── Sample-Configuration-WithNote.md ├── samples │ ├── I290.dcm │ ├── I341.dcm │ └── lung.dcm ├── src │ ├── Microsoft.Health.Anonymizer.Common.UnitTests │ │ ├── CryptoHashTests.cs │ │ ├── DateShiftTests.cs │ │ ├── EncryptTests.cs │ │ ├── Microsoft.Health.Anonymizer.Common.UnitTests.csproj │ │ ├── PerturbTests.cs │ │ └── RedactTests.cs │ ├── Microsoft.Health.Anonymizer.Common │ │ ├── CryptoHashFunction.cs │ │ ├── DateShiftFunction.cs │ │ ├── EncryptFunction.cs │ │ ├── Exceptions │ │ │ ├── AnonymizerErrorCode.cs │ │ │ └── AnonymizerException.cs │ │ ├── Microsoft.Health.Anonymizer.Common.csproj │ │ ├── Models │ │ │ ├── AgeObject.cs │ │ │ ├── AgeType.cs │ │ │ ├── AnonymizerValueTypes.cs │ │ │ └── DateTimeObject.cs │ │ ├── PerturbFunction.cs │ │ ├── RedactFunction.cs │ │ ├── Settings │ │ │ ├── CryptoHashSetting.cs │ │ │ ├── DateShiftSetting.cs │ │ │ ├── DateTimeGlobalSettings.cs │ │ │ ├── EncryptSetting.cs │ │ │ ├── PerturbRangeType.cs │ │ │ ├── PerturbSetting.cs │ │ │ └── RedactSetting.cs │ │ └── Utilities │ │ │ └── DateTimeUtility.cs │ ├── Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests │ │ ├── AnonymizerToolUnitTests.cs │ │ ├── DicomFiles │ │ │ ├── I290.dcm │ │ │ └── I341.dcm │ │ ├── DicomResults │ │ │ ├── I290.dcm │ │ │ ├── I341-newConfig.dcm │ │ │ └── I341.dcm │ │ ├── Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests.csproj │ │ └── TestConfigs │ │ │ ├── invalidOutputConfig.json │ │ │ └── newConfig.json │ ├── Microsoft.Health.Dicom.Anonymizer.CommandLineTool │ │ ├── AnonymizerCliTool.cs │ │ ├── AnonymizerLogic.cs │ │ ├── AnonymizerOptions.cs │ │ ├── Microsoft.Health.Dicom.Anonymizer.CommandLineTool.csproj │ │ └── configuration.json │ ├── Microsoft.Health.Dicom.Anonymizer.Core.UnitTests │ │ ├── AnonymizerEngineTests.cs │ │ ├── DicomResults │ │ │ ├── Anonymized.dcm │ │ │ ├── Invalid-String-Format.dcm │ │ │ ├── UnchangedValue.dcm │ │ │ └── custom.dcm │ │ ├── DicomUtilityTests.cs │ │ ├── MaskProcessor.cs │ │ ├── Microsoft.Health.Dicom.Anonymizer.Core.UnitTests.csproj │ │ ├── Processors │ │ │ ├── CryptoHashProcessorUnitTests.cs │ │ │ ├── CustomProcessorFactoryUnitTests.cs │ │ │ ├── DateshiftProcessorUnitTests.cs │ │ │ ├── DicomProcessorFactoryUnitTests.cs │ │ │ ├── EncryptProcessorUnitTests.cs │ │ │ ├── MockAnonymizerProcessor.cs │ │ │ ├── PerturbProcessorUnitTests.cs │ │ │ ├── RedactProcessorUnitTests.cs │ │ │ ├── RefreshUIDProccessorUnitTests.cs │ │ │ ├── RemoveProcessorUnitTests.cs │ │ │ └── SubstituteProcessorUnitTests.cs │ │ ├── Rules │ │ │ ├── AnonymizerMaskedTagRuleTests.cs │ │ │ ├── AnonymizerRuleFactoryTests.cs │ │ │ ├── AnonymizerTagRuleTests.cs │ │ │ ├── AnonymizerVRRuleTests.cs │ │ │ └── settings.json │ │ └── TestConfigurations │ │ │ ├── configuration-custom.json │ │ │ ├── configuration-invalid-DicomTag.json │ │ │ ├── configuration-invalid-parameters.json │ │ │ ├── configuration-invalid-string-output.json │ │ │ ├── configuration-miss-rules.json │ │ │ ├── configuration-miss-tag.json │ │ │ ├── configuration-test-engine.json │ │ │ ├── configuration-test-sample.json │ │ │ └── configuration-unsupported-method.json │ └── Microsoft.Health.Dicom.Anonymizer.Core │ │ ├── AnonymizerConfigurationManager.cs │ │ ├── AnonymizerEngine.cs │ │ ├── AnonymizerLogging.cs │ │ ├── Constants.cs │ │ ├── DicomUtility.cs │ │ ├── Exceptions │ │ ├── AddCustomProcessorException.cs │ │ ├── AnonymizerConfigurationException.cs │ │ ├── AnonymizerOperationException.cs │ │ ├── DicomAnonymizationErrorCode.cs │ │ └── DicomAnonymizationException.cs │ │ ├── Microsoft.Health.Dicom.Anonymizer.Core.csproj │ │ ├── Models │ │ ├── AnonymizerConfiguration.cs │ │ ├── AnonymizerDefaultSettings.cs │ │ ├── AnonymizerEngineOptions.cs │ │ ├── AnonymizerMethod.cs │ │ └── ProcessContext.cs │ │ ├── Processors │ │ ├── CryptoHashProcessor.cs │ │ ├── DateShiftProcessor.cs │ │ ├── EncryptProcessor.cs │ │ ├── Factories │ │ │ ├── AnonymizerSettingsFactory.cs │ │ │ ├── CustomProcessorFactory.cs │ │ │ ├── DicomProcessorFactory.cs │ │ │ └── IAnonymizerProcessorFactory.cs │ │ ├── IAnonymizerProcessor.cs │ │ ├── KeepProcessor.cs │ │ ├── Models │ │ │ ├── DateShiftScope.cs │ │ │ └── DicomDataModel.cs │ │ ├── PerturbProcessor.cs │ │ ├── RedactProcessor.cs │ │ ├── RefreshUIDProcessor.cs │ │ ├── RemoveProcessor.cs │ │ └── SubstituteProcessor.cs │ │ └── Rules │ │ ├── AnonymizerMaskedTagRule.cs │ │ ├── AnonymizerRule.cs │ │ ├── AnonymizerRuleFactory.cs │ │ ├── AnonymizerTagRule.cs │ │ ├── AnonymizerVRRule.cs │ │ └── IAnonymizerRuleFactory.cs └── stylecop.json ├── FHIR ├── Fhir.Anonymizer.sln ├── samples │ ├── fhir-r4-files │ │ ├── Antonia30_Ruelas156_fe07e69a-4d6d-4e11-a675-de69f62de5f3.json │ │ ├── Carolee951_Sanford861_d88f994a-5efa-421d-9c40-5f116d2114a6.json │ │ ├── Johnetta529_Marquardt819_abb133fd-5683-45cd-a5c8-137ca2807fbe.json │ │ ├── Lindsey52_O'Reilly797_092afec8-ffca-4793-a91b-40f2055f9d89.json │ │ ├── Luisa710_Carranza218_2feb4fc9-0b59-4d72-bc87-aafcf11083d9.json │ │ ├── Lynwood354_Powlowski563_edca2a50-d3e4-4663-a104-6c226d5a8fcd.json │ │ ├── Paris331_Lindgren255_ef2185a2-4460-49bb-9604-a36ed52c2ef4.json │ │ ├── Sunny31_Lakin515_4da5c8a3-847e-4fd8-a21f-f5ab37f6662b.json │ │ ├── Terrie907_Skiles927_1c5a49ea-fc04-456b-9d86-82d3e8f25ab1.json │ │ ├── Todd315_Stamm704_0ce18c7d-fd78-46d9-81b5-8bed231c337a.json │ │ ├── Vanesa40_Grant908_c79d46db-3ba8-406a-8bc3-201509059978.json │ │ ├── hospitalInformation1583281853074.json │ │ └── practitionerInformation1583281853074.json │ └── fhir-stu3-files │ │ ├── Andreas188_Hoeger474_cc146f25-f6bf-40c4-98fd-b89cc8e0a757.json │ │ ├── Bong390_Harvey63_b5fba949-c0b2-4277-99c2-67abf43e40e0.json │ │ ├── Cassandra224_Hilll811_c790d598-778a-4a44-bded-5a444dff4b27.json │ │ ├── Georgetta870_Larkin917_a1bf5859-43a3-4b8e-a1d1-30dc732e6423.json │ │ ├── Hyon784_Marvin195_31db0dc6-d5f0-4ecf-b715-cc105b87957d.json │ │ ├── Kristie55_Fay398_71d099b3-3e07-4da7-bcc3-4331607fe0a7.json │ │ ├── Marco578_Williamson769_e33ad153-3319-48c5-aacb-600cdb212a46.json │ │ ├── Queenie922_Gleason633_c99e64e7-3f31-4b7f-a81a-db34fead98e5.json │ │ ├── Scottie437_Heller342_dfc2bcb0-ae02-4ce6-a1d8-216da26c2d20.json │ │ ├── Vernie449_Von197_9c508457-5256-4a34-8903-5ff4d4866313.json │ │ └── Wally311_Koepp521_276ba071-be58-40c6-a8c2-d245da2da7fb.json └── src │ ├── Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline.UnitTests │ └── Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline.UnitTests.csproj │ ├── Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline │ ├── AzureDataFactorySettings.json │ ├── DeployAzureDataFactoryPipeline.ps1 │ └── Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline.csproj │ ├── Microsoft.Health.Fhir.Anonymizer.R4.CommandLineTool │ ├── Microsoft.Health.Fhir.Anonymizer.R4.CommandLineTool.csproj │ ├── configuration-sample-with-dateshiftfixedoffset.json │ └── configuration-sample.json │ ├── Microsoft.Health.Fhir.Anonymizer.R4.Core.UnitTests │ ├── AnonymizerConfigurationValidatorTests.cs │ └── Microsoft.Health.Fhir.Anonymizer.R4.Core.UnitTests.csproj │ ├── Microsoft.Health.Fhir.Anonymizer.R4.Core │ ├── Constants.cs │ ├── Microsoft.Health.Fhir.Anonymizer.R4.Core.csproj │ └── Processors │ │ └── PerturbProcessor.cs │ ├── Microsoft.Health.Fhir.Anonymizer.R4.FunctionalTests │ ├── Microsoft.Health.Fhir.Anonymizer.R4.FunctionalTests.csproj │ ├── VersionSpecificTests.cs │ └── r4-configuration-sample.json │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests │ ├── FhirBlobConsumerTests.cs │ ├── FhirBlobStreamTests.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests.projitems │ └── Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests.shproj │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.projitems │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.shproj │ ├── scripts │ │ ├── ArmTemplate │ │ │ ├── arm_template.json │ │ │ └── arm_template_parameters.json │ │ ├── AzureDataFactoryPipelineUtility.ps1 │ │ └── CustomActivity.ps1 │ └── src │ │ ├── ActivityInputData.cs │ │ ├── DataFactoryCustomActivity.cs │ │ ├── FhirAzureConstants.cs │ │ ├── FhirBlobConsumer.cs │ │ ├── FhirBlobDataStream.cs │ │ ├── OperationExecutionHelper.cs │ │ └── Program.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool │ ├── AnonymizationLogic.cs │ ├── AnonymizationToolOptions.cs │ ├── FilesAnonymizerForJsonFormatResource.cs │ ├── FilesAnonymizerForNdJsonFormatResource.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool.projitems │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool.shproj │ └── Program.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests │ ├── AnonymizerConfigurations │ │ ├── AnonymizationFhirPathRuleTests.cs │ │ ├── AnonymizerConfigurationManagerTests.cs │ │ ├── AnonymizerConfigurationTests.cs │ │ └── AnonymizerConfigurationValidatorTests.cs │ ├── AnonymizerEngineTests.cs │ ├── EmptyElementTest.cs │ ├── Extensions │ │ ├── ElementNodeOperationExtensionsTests.cs │ │ ├── ElementNodeVisitorExtensionsTests.cs │ │ ├── FhirPathSymbolExtensionsTests.cs │ │ └── TypedElementNavExtensionsTests.cs │ ├── MaskProcessor.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests.projitems │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests.shproj │ ├── MockAnonymizerProcessor.cs │ ├── PartitionedExecution │ │ ├── FhirEnumerableReaderTests.cs │ │ ├── FhirPartitionedExecutionTests.cs │ │ ├── FhirStreamConsumerTests.cs │ │ └── FhirStreamReaderTests.cs │ ├── Processors │ │ ├── CryptoHashProcessorTests.cs │ │ ├── CustomProcessorFactoryUnitTests.cs │ │ ├── DateShiftProcessorTests.cs │ │ ├── EncryptProcessorTests.cs │ │ ├── GeneralizeProcessorTests.cs │ │ ├── PerturbProcessorTests.cs │ │ ├── RedactProcessorTests.cs │ │ ├── ResourceProcessorTests.cs │ │ ├── Settings │ │ │ ├── GeneralizeSettingTests.cs │ │ │ ├── PerturbSettingTests.cs │ │ │ └── SubstituteSettingTests.cs │ │ └── SubstituteProcessorTests.cs │ ├── TestConfigurations │ │ ├── configuration-custom-processor.json │ │ ├── configuration-generalize-fail-compiled-expression.json │ │ ├── configuration-generalize-invalid-othervalues.json │ │ ├── configuration-generalize-miss-cases.json │ │ ├── configuration-invalid-encryptkey.json │ │ ├── configuration-invalid-fhirpath.json │ │ ├── configuration-miss-replacement.json │ │ ├── configuration-miss-rules.json │ │ ├── configuration-perturb-exceed-28-roundTo.json │ │ ├── configuration-perturb-miss-span.json │ │ ├── configuration-perturb-negative-roundTo.json │ │ ├── configuration-perturb-negative-span.json │ │ ├── configuration-perturb-wrong-rangetype.json │ │ ├── configuration-perturb-wrong-roundTo.json │ │ ├── configuration-raise-processing-error.json │ │ ├── configuration-test-sample.json │ │ ├── configuration-unsupported-method.json │ │ └── configuration-without-processing-error.json │ ├── TestConfigurationsVersion │ │ ├── configuration-R4-version.json │ │ ├── configuration-Stu3-version.json │ │ ├── configuration-empty-version.json │ │ ├── configuration-invalid-version.json │ │ └── configuration-null-version.json │ ├── TestResources │ │ ├── bundle-basic.json │ │ ├── bundle-empty.json │ │ ├── condition-empty.json │ │ ├── contained-basic.json │ │ └── patient-empty.json │ ├── Utility │ │ ├── CryptoHashUtilityTests.cs │ │ ├── DateTimeUtilityTests.cs │ │ ├── EncryptUtilityTests.cs │ │ ├── PostalCodeUtilityTests.cs │ │ └── ReferenceUtilityTests.cs │ ├── Validation │ │ └── AttributeValidatorTests.cs │ └── Visitors │ │ └── AnonymizationVisitorTests.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.Core │ ├── AnonymizerConfigurationManager.cs │ ├── AnonymizerConfigurations │ │ ├── AnonymizationFhirPathRule.cs │ │ ├── AnonymizerConfiguration.cs │ │ ├── AnonymizerConfigurationValidator.cs │ │ ├── AnonymizerMethod.cs │ │ ├── AnonymizerRule.cs │ │ ├── AnonymizerRuleType.cs │ │ ├── AnonymizerSettings.cs │ │ ├── DateShiftScope.cs │ │ ├── ParameterConfiguration.cs │ │ └── ProcessingErrorsOption.cs │ ├── AnonymizerEngine.cs │ ├── AnonymizerLogging.cs │ ├── Constants.cs │ ├── Exceptions │ │ ├── AddCustomProcessorException.cs │ │ ├── AnonymizerConfigurationException.cs │ │ ├── AnonymizerProcessingException.cs │ │ ├── AnonymizerRuleNotApplicableException.cs │ │ └── InvalidInputException.cs │ ├── Extensions │ │ ├── ElementNodeExtensions.cs │ │ ├── ElementNodeOperationExtensions.cs │ │ ├── ElementNodeVisitorExtensions.cs │ │ ├── FhirPathSymbolExtensions.cs │ │ ├── TypedElementExtensions.cs │ │ └── TypedElementNavExtensions.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.Core.projitems │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.Core.shproj │ ├── Models │ │ ├── EmptyElement.cs │ │ ├── ProcessContext.cs │ │ ├── ProcessResult.cs │ │ └── SecurityLabels.cs │ ├── PartitionedExecution │ │ ├── BatchAnonymizeProgressDetail.cs │ │ ├── FhirEnumerableReader.cs │ │ ├── FhirPartitionedExecutor.cs │ │ ├── FhirStreamConsumer.cs │ │ ├── FhirStreamReader.cs │ │ ├── IFhirDataConsumer.cs │ │ └── IFhirDataReader.cs │ ├── Processors │ │ ├── AnonymizationOperations.cs │ │ ├── CryptoHashProcessor.cs │ │ ├── DateShiftProcessor.cs │ │ ├── EncryptProcessor.cs │ │ ├── Factory │ │ │ ├── CustomProcessorFactory.cs │ │ │ └── IAnonymizerProcessorFactory.cs │ │ ├── GeneralizeProcessor.cs │ │ ├── IAnonymizerProcessor.cs │ │ ├── KeepProcessor.cs │ │ ├── PerturbProcessor.cs │ │ ├── RedactProcessor.cs │ │ ├── ResourceProcessor.cs │ │ ├── Settings │ │ │ ├── GeneralizeOtherValuesOperation.cs │ │ │ ├── GeneralizeSetting.cs │ │ │ ├── PerturbRangeType.cs │ │ │ ├── PerturbSetting.cs │ │ │ ├── RuleKeys.cs │ │ │ └── SubstituteSetting.cs │ │ └── SubstituteProcessor.cs │ ├── Utility │ │ ├── CryptoHashUtility.cs │ │ ├── DateTimeUtility.cs │ │ ├── EncryptUtility.cs │ │ ├── PostalCodeUtility.cs │ │ └── ReferenceUtility.cs │ ├── Validation │ │ ├── AttributeValidator.cs │ │ ├── ResourceNotValidException.cs │ │ └── ResourceValidator.cs │ └── Visitors │ │ ├── AbstractElementNodeVisitor.cs │ │ └── AnonymizationVisitor.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests │ ├── CollectionResourceTests.cs │ ├── Configurations │ │ ├── common-config.json │ │ ├── common-no-partial-config.json │ │ ├── configuration-raise-processing-error.json │ │ ├── configuration-skip-processing-error.json │ │ ├── configuration-without-processing-error.json │ │ ├── generalize-patient-config.json │ │ ├── redact-all-config.json │ │ ├── substitute-complex.json │ │ ├── substitute-conflict-rules.json │ │ ├── substitute-multiple-2.json │ │ ├── substitute-multiple.json │ │ └── substitute-primitive.json │ ├── FunctionalTestUtility.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests.projitems │ ├── Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests.shproj │ ├── Patient-1.ndjson │ ├── ResourceTests.cs │ └── TestResources │ │ ├── R4OnlyResource │ │ ├── Account-R4-target.json │ │ ├── Account-R4.json │ │ ├── Claim-R4-target.json │ │ ├── Claim-R4.json │ │ ├── Contract-R4-target.json │ │ ├── Contract-R4.json │ │ ├── MedicinalProduct-target.json │ │ ├── MedicinalProduct.json │ │ ├── OrganizationAffiliation-target.json │ │ ├── OrganizationAffiliation.json │ │ ├── ServiceRequest-target.json │ │ └── ServiceRequest.json │ │ ├── Stu3OnlyResource │ │ ├── Account-Stu3-target.json │ │ ├── Account-Stu3.json │ │ ├── Claim-Stu3-target.json │ │ ├── Claim-Stu3.json │ │ ├── Contract-Stu3-target.json │ │ ├── Contract-Stu3.json │ │ ├── DeviceComponent-target.json │ │ ├── DeviceComponent.json │ │ ├── ProcessRequest-target.json │ │ ├── ProcessRequest.json │ │ ├── ProcessResponse-target.json │ │ └── ProcessResponse.json │ │ ├── bundle-basic-target.json │ │ ├── bundle-basic.json │ │ ├── bundle-empty.json │ │ ├── bundle-redact-all-target.json │ │ ├── bundle-substitute-target.json │ │ ├── bundle-substitute.json │ │ ├── bundle-without-entry-resource-target.json │ │ ├── bundle-without-entry-resource.json │ │ ├── condition-empty.json │ │ ├── contained-basic-target.json │ │ ├── contained-basic.json │ │ ├── contained-in-bundle-redact-all-target.json │ │ ├── contained-in-bundle-target.json │ │ ├── contained-in-bundle.json │ │ ├── contained-redact-all-target.json │ │ ├── contained-substitute-target.json │ │ ├── contained-substitute.json │ │ ├── patient-basic-target.json │ │ ├── patient-basic.json │ │ ├── patient-empty.json │ │ ├── patient-generalize-target.json │ │ ├── patient-generalize.json │ │ ├── patient-no-partial-target.json │ │ ├── patient-no-partial.json │ │ ├── patient-null-date-target.json │ │ ├── patient-null-date.json │ │ ├── patient-redact-all-target.json │ │ ├── patient-special-content-target.json │ │ ├── patient-special-content.json │ │ ├── patient-substitute-complex-target.json │ │ ├── patient-substitute-complex.json │ │ ├── patient-substitute-conflict-rules-target.json │ │ ├── patient-substitute-conflict-rules.json │ │ ├── patient-substitute-multiple-2-target.json │ │ ├── patient-substitute-multiple-2.json │ │ ├── patient-substitute-multiple-target.json │ │ ├── patient-substitute-multiple.json │ │ ├── patient-substitute-primitive-target.json │ │ └── patient-substitute-primitive.json │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline.UnitTests │ └── Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline.UnitTests.csproj │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline │ ├── AzureDataFactorySettings.json │ ├── DeployAzureDataFactoryPipeline.ps1 │ └── Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline.csproj │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.CommandLineTool │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.CommandLineTool.csproj │ └── configuration-sample.json │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.Core.UnitTests │ ├── AnonymizerConfigurationValidatorTests.cs │ └── Microsoft.Health.Fhir.Anonymizer.Stu3.Core.UnitTests.csproj │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.Core │ ├── Constants.cs │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.Core.csproj │ └── Processors │ │ └── PerturbProcessor.cs │ └── Microsoft.Health.Fhir.Anonymizer.Stu3.FunctionalTests │ ├── Microsoft.Health.Fhir.Anonymizer.Stu3.FunctionalTests.csproj │ ├── VersionSpecificTests.cs │ └── stu3-configuration-sample.json ├── GeoPol.xml ├── LICENSE ├── README.md ├── SECURITY.md ├── docs ├── DICOM-anonymization.md └── FHIR-anonymization.md ├── nuget.config └── release.yml /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /DICOM/CustomAnalysisRules.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /DICOM/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildThisFileDirectory)\CustomAnalysisRules.ruleset 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /DICOM/samples/I290.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/samples/I290.dcm -------------------------------------------------------------------------------- /DICOM/samples/I341.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/samples/I341.dcm -------------------------------------------------------------------------------- /DICOM/samples/lung.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/samples/lung.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common.UnitTests/Microsoft.Health.Anonymizer.Common.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Exceptions/AnonymizerErrorCode.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Anonymizer.Common.Exceptions 7 | { 8 | public enum AnonymizerErrorCode 9 | { 10 | InvalidInputValue, 11 | InvalidAnonymizerSettings, 12 | 13 | DateShiftFailed, 14 | EncryptFailed, 15 | CryptoHashFailed, 16 | PerturbFailed, 17 | RedactFailed, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Exceptions/AnonymizerException.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Anonymizer.Common.Exceptions 9 | { 10 | public class AnonymizerException : Exception 11 | { 12 | public AnonymizerException(AnonymizerErrorCode errorCode, string message) 13 | : base(message) 14 | { 15 | AnonymizerErrorCode = errorCode; 16 | } 17 | 18 | public AnonymizerException(AnonymizerErrorCode errorCode, string message, Exception innerException) 19 | : base(message, innerException) 20 | { 21 | AnonymizerErrorCode = errorCode; 22 | } 23 | 24 | public AnonymizerErrorCode AnonymizerErrorCode { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Microsoft.Health.Anonymizer.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net6.0 6 | MIT 7 | Copyright © Microsoft Corporation. All rights reserved. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Models/AgeObject.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Anonymizer.Common.Models 7 | { 8 | public class AgeObject 9 | { 10 | public AgeObject(uint value, AgeType ageType) 11 | { 12 | Value = value; 13 | AgeType = ageType; 14 | } 15 | 16 | public uint Value { get; } = 0; 17 | 18 | public AgeType AgeType { get; } 19 | 20 | public uint AgeInYears() 21 | { 22 | return AgeType switch 23 | { 24 | AgeType.Year => Value, 25 | AgeType.Month => Value / 12, 26 | AgeType.Week => Value / 52, 27 | AgeType.Day => Value / 365, 28 | _ => Value, 29 | }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Models/AgeType.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Anonymizer.Common.Models 7 | { 8 | public enum AgeType 9 | { 10 | Year, 11 | Month, 12 | Week, 13 | Day, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Models/AnonymizerValueTypes.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Anonymizer.Common.Models 7 | { 8 | /// 9 | /// Indicates types for the input value 10 | /// Only Date and DateTime can be used for dateshift. 11 | /// Partial redact is applicable on Age, Date, DateTime and PostalCode. 12 | /// Any types could be used for cryptoHash, encryption, perturb and redact methods. 13 | /// 14 | public enum AnonymizerValueTypes 15 | { 16 | Age, 17 | Date, 18 | DateTime, 19 | PostalCode, 20 | Others, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Models/DateTimeObject.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Anonymizer.Common.Models 9 | { 10 | public class DateTimeObject 11 | { 12 | public DateTimeOffset DateValue { get; set; } 13 | 14 | public bool? HasTimeZone { get; set; } = null; 15 | 16 | public bool? HasMilliSecond { get; set; } = null; 17 | 18 | public bool? HasSecond { get; set; } = null; 19 | 20 | public bool? HasHour { get; set; } = null; 21 | 22 | public bool? HasDay { get; set; } = null; 23 | 24 | public bool? HasMonth { get; set; } = null; 25 | 26 | public bool? HasYear { get; set; } = null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Settings/CryptoHashSetting.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Security.Authentication; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | 10 | namespace Microsoft.Health.Anonymizer.Common.Settings 11 | { 12 | public class CryptoHashSetting 13 | { 14 | public string CryptoHashKey { private get; set; } 15 | 16 | public HashAlgorithmType CryptoHashType { get; set; } = HashAlgorithmType.Sha256; 17 | 18 | public byte[] GetCryptoHashByteKey() 19 | { 20 | return CryptoHashKey == null ? Aes.Create().Key : Encoding.UTF8.GetBytes(CryptoHashKey); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Settings/DateShiftSetting.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Anonymizer.Common.Settings 7 | { 8 | public class DateShiftSetting 9 | { 10 | public uint DateShiftRange { get; set; } = 50; 11 | 12 | public string DateShiftKey { get; set; } = string.Empty; 13 | 14 | public string DateShiftKeyPrefix { get; set; } = string.Empty; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Settings/DateTimeGlobalSettings.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Anonymizer.Common.Settings 7 | { 8 | public static class DateTimeGlobalSettings 9 | { 10 | public static string DateFormat { get; set; } = "yyyy-MM-dd"; 11 | 12 | public static string DateTimeFormat { get; set; } = "yyyy-MM-ddTHH:mm:ss"; 13 | 14 | public static string YearFormat { get; set; } = "yyyy"; 15 | 16 | // Refer to HIPPA standard https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/index.html 17 | public static int AgeThreshold { get; set; } = 89; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Settings/EncryptSetting.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using Microsoft.Health.Anonymizer.Common.Exceptions; 9 | 10 | namespace Microsoft.Health.Anonymizer.Common.Settings 11 | { 12 | public class EncryptSetting 13 | { 14 | public string EncryptKey { private get; set; } 15 | 16 | public void Validate() 17 | { 18 | var encryptKeySize = GetEncryptByteKey().Length * 8; 19 | if (encryptKeySize != 128 && encryptKeySize != 192 && encryptKeySize != 256) 20 | { 21 | throw new AnonymizerException( 22 | AnonymizerErrorCode.InvalidAnonymizerSettings, 23 | $"Invalid encrypt key size : {encryptKeySize} bits! Please provide key sizes of 128, 192 or 256 bits."); 24 | } 25 | } 26 | 27 | public byte[] GetEncryptByteKey() 28 | { 29 | return EncryptKey == null ? Aes.Create().Key : Encoding.UTF8.GetBytes(EncryptKey); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Settings/PerturbRangeType.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Anonymizer.Common.Settings 7 | { 8 | public enum PerturbRangeType 9 | { 10 | Fixed, 11 | Proportional, 12 | } 13 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Settings/PerturbSetting.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using MathNet.Numerics.Distributions; 8 | using Microsoft.Health.Anonymizer.Common.Exceptions; 9 | 10 | namespace Microsoft.Health.Anonymizer.Common.Settings 11 | { 12 | public class PerturbSetting 13 | { 14 | private const int MaxRoundToValue = 28; 15 | 16 | public double Span { get; set; } = 1; 17 | 18 | public PerturbRangeType RangeType { get; set; } = PerturbRangeType.Proportional; 19 | 20 | public int RoundTo { get; set; } = 2; 21 | 22 | public Func NoiseFunction { get; set; } = ContinuousUniform.Sample; 23 | 24 | public void Validate() 25 | { 26 | if (Span < 0) 27 | { 28 | throw new AnonymizerException( 29 | AnonymizerErrorCode.InvalidAnonymizerSettings, 30 | "Perturb setting is invalid: Span value must be greater than 0."); 31 | } 32 | 33 | if (RoundTo > MaxRoundToValue || RoundTo < 0) 34 | { 35 | throw new AnonymizerException( 36 | AnonymizerErrorCode.InvalidAnonymizerSettings, 37 | "Perturb setting is invalid: RoundTo value must be in range [0, 28]."); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Settings/RedactSetting.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | 8 | namespace Microsoft.Health.Anonymizer.Common.Settings 9 | { 10 | public class RedactSetting 11 | { 12 | public bool EnablePartialDatesForRedact { get; set; } = false; 13 | 14 | public bool EnablePartialZipCodesForRedact { get; set; } = false; 15 | 16 | public bool EnablePartialAgesForRedact { get; set; } = false; 17 | 18 | public List RestrictedZipCodeTabulationAreas { get; set; } = new List(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Anonymizer.Common/Utilities/DateTimeUtility.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Globalization; 8 | using Microsoft.Health.Anonymizer.Common.Exceptions; 9 | using Microsoft.Health.Anonymizer.Common.Settings; 10 | 11 | namespace Microsoft.Health.Anonymizer.Common.Utilities 12 | { 13 | public class DateTimeUtility 14 | { 15 | public static DateTimeOffset ParseDateTimeString(string inputString, string globalFormat = null) 16 | { 17 | if (DateTimeOffset.TryParse(inputString, out DateTimeOffset defaultResult)) 18 | { 19 | return defaultResult; 20 | } 21 | 22 | if (DateTimeOffset.TryParseExact(inputString, globalFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset globalResult)) 23 | { 24 | return globalResult; 25 | } 26 | 27 | if (DateTimeOffset.TryParseExact(inputString, DateTimeGlobalSettings.YearFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset yearResult)) 28 | { 29 | return yearResult; 30 | } 31 | 32 | throw new AnonymizerException(AnonymizerErrorCode.InvalidInputValue, $"Failed to parse input date value: [{inputString}]."); 33 | } 34 | 35 | public static bool IsAgeOverThreshold(DateTimeOffset birthDate) 36 | { 37 | return birthDate.AddYears(DateTimeGlobalSettings.AgeThreshold) < DateTimeOffset.Now; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomFiles/I290.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomFiles/I290.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomFiles/I341.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomFiles/I341.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomResults/I290.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomResults/I290.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomResults/I341-newConfig.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomResults/I341-newConfig.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomResults/I341.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/DicomResults/I341.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/TestConfigs/invalidOutputConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | {"tag":"US", "method":"encrypt"} 4 | ], 5 | "defaultSettings": { 6 | "perturb": { 7 | "span": 1, 8 | "roundTo": 2, 9 | "rangeType": "Proportional" 10 | }, 11 | "dateShift": { 12 | "dateShiftKey": "123", 13 | "dateShiftScope": "SeriesInstance", 14 | "dateShiftRange": 50 15 | }, 16 | "cryptoHash": { 17 | "cryptoHashKey": "123" 18 | }, 19 | "redact": { 20 | "enablePartialAgesForRedact": false, 21 | "enablePartialDatesForRedact": false 22 | }, 23 | "encrypt": { 24 | "encryptKey": "123456781234567812345678" 25 | }, 26 | "substitute": { 27 | "replaceWith": "ANONYMOUS" 28 | } 29 | }, 30 | "customSettings": { 31 | "perturbCustomerSetting": { 32 | "span": 1, 33 | "roundTo": 2, 34 | "rangeType": "Proportional" 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.UnitTests/TestConfigs/newConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | {"tag":"(0008,xxxx)", "method":"redact"} 4 | ], 5 | "defaultSettings": { 6 | "perturb": { 7 | "span": 1, 8 | "roundTo": 2, 9 | "rangeType": "Proportional" 10 | }, 11 | "dateShift": { 12 | "dateShiftKey": "123", 13 | "dateShiftScope": "SeriesInstance", 14 | "dateShiftRange": 50 15 | }, 16 | "cryptoHash": { 17 | "cryptoHashKey": "123" 18 | }, 19 | "redact": { 20 | "enablePartialAgesForRedact": false, 21 | "enablePartialDatesForRedact": false 22 | }, 23 | "encrypt": { 24 | "encryptKey": "123456781234567812345678" 25 | }, 26 | "substitute": { 27 | "replaceWith": "ANONYMOUS" 28 | } 29 | }, 30 | "customSettings": { 31 | "perturbCustomerSetting": { 32 | "span": 1, 33 | "roundTo": 2, 34 | "rangeType": "Proportional" 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool/AnonymizerCliTool.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Threading.Tasks; 8 | using CommandLine; 9 | 10 | namespace Microsoft.Health.Dicom.Anonymizer.CommandLineTool 11 | { 12 | public class AnonymizerCliTool 13 | { 14 | public static async Task Main(string[] args) 15 | { 16 | try 17 | { 18 | await ExecuteCommandsAsync(args); 19 | return 0; 20 | } 21 | catch (Exception ex) 22 | { 23 | Console.Error.WriteLine($"Process failed: {ex.Message}"); 24 | return -1; 25 | } 26 | } 27 | 28 | public static async Task ExecuteCommandsAsync(string[] args) 29 | { 30 | await Parser.Default.ParseArguments(args) 31 | .MapResult(async options => await AnonymizerLogic.AnonymizeAsync(options).ConfigureAwait(false), _ => Task.FromResult(1)).ConfigureAwait(false); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool/AnonymizerOptions.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using CommandLine; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.CommandLineTool 9 | { 10 | public class AnonymizerOptions 11 | { 12 | [Option('i', "inputFile", Required = false, HelpText = "Input DICOM file")] 13 | public string InputFile { get; set; } 14 | 15 | [Option('o', "outputFile", Required = false, HelpText = "Output DICOM file")] 16 | public string OutputFile { get; set; } 17 | 18 | [Option('c', "configFile", Required = false, Default = "configuration.json", HelpText = "Anonymization configuration file path.")] 19 | public string ConfigurationFilePath { get; set; } 20 | 21 | [Option('I', "inputFolder", Required = false, HelpText = "Input folder")] 22 | public string InputFolder { get; set; } 23 | 24 | [Option('O', "outputFolder", Required = false, HelpText = "Output folder")] 25 | public string OutputFolder { get; set; } 26 | 27 | [Option("validateInput", Required = false, Default = false, HelpText = "Validate input DICOM data items.")] 28 | public bool ValidateInput { get; set; } 29 | 30 | [Option("validateOutput", Required = false, Default = false, HelpText = "Validate output DICOM data items.")] 31 | public bool ValidateOutput { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.CommandLineTool/Microsoft.Health.Dicom.Anonymizer.CommandLineTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/Anonymized.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/Anonymized.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/Invalid-String-Format.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/Invalid-String-Format.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/UnchangedValue.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/UnchangedValue.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/custom.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Tools-for-Health-Data-Anonymization/d18516d2eb723e7514703e16fe73cdd0b3c0f613/DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/DicomResults/custom.dcm -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/Processors/MockAnonymizerProcessor.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using FellowOakDicom; 8 | using Microsoft.Health.Dicom.Anonymizer.Core.Models; 9 | using Microsoft.Health.Dicom.Anonymizer.Core.Processors; 10 | using Newtonsoft.Json.Linq; 11 | 12 | namespace Microsoft.Health.Dicom.Anonymizer.Core.UnitTests.Processors 13 | { 14 | public class MockAnonymizerProcessor : IAnonymizerProcessor 15 | { 16 | public MockAnonymizerProcessor(JObject settintgs) 17 | { 18 | } 19 | 20 | public bool IsSupported(DicomItem item) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public void Process(DicomDataset dicomDataset, DicomItem item, ProcessContext context) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/Rules/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSettings": { 3 | "perturb": { 4 | "span": "1", 5 | "roundTo": 2, 6 | "rangeType": "Proportional" 7 | }, 8 | "dateShift": { 9 | "dateShiftKey": "123", 10 | "dateShiftScope": "SeriesInstance", 11 | "dateShiftRange": "50" 12 | }, 13 | "cryptoHash": { 14 | "cryptoHashKey": "123" 15 | }, 16 | "redact": { 17 | "enablePartialAgesForRedact": false, 18 | "enablePartialDatesForRedact": false 19 | }, 20 | "encrypt": { 21 | "encryptKey": "123456781234567812345678" 22 | }, 23 | "substitute": { 24 | "replaceWith": "ANONYMOUS" 25 | } 26 | }, 27 | "customSettings": { 28 | "perturbCustomerSetting": { 29 | "span": "1", 30 | "roundTo": 2, 31 | "rangeType": "Fixed" 32 | }, 33 | "redactCustomerSetting": { 34 | "enablePartialAgesForRedact": true, 35 | "enablePartialDatesForRedact": false 36 | }, 37 | "dateShiftCustomerSetting": { 38 | "dateShiftKey": "123", 39 | "dateShiftScope": "SOPInstance", 40 | "dateShiftRange": "100" 41 | }, 42 | "cryptoHashCustomerSetting": { 43 | "cryptoHashKey": "456" 44 | }, 45 | "substituteCustomerSetting": { 46 | "replaceWith": "customized" 47 | }, 48 | "encryptCustomerSetting": { 49 | "encryptKey": "0000000000000000" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "tag": "RetrieveAETitle", 5 | "method": "mask", 6 | "params": { "maskedLength": "3" } 7 | } 8 | ], 9 | "defaultSettings": { 10 | "perturb": { 11 | "span": 1, 12 | "roundTo": 2, 13 | "rangeType": "Proportional" 14 | }, 15 | "dateShift": { 16 | "dateShiftKey": "123", 17 | "dateShiftScope": "SeriesInstance", 18 | "dateShiftRange": 50 19 | }, 20 | "cryptoHash": { 21 | "cryptoHashKey": "123" 22 | }, 23 | "redact": { 24 | "enablePartialAgesForRedact": false, 25 | "enablePartialDatesForRedact": false 26 | }, 27 | "encrypt": { 28 | "encryptKey": "123456781234567812345678" 29 | }, 30 | "substitute": { 31 | "replaceWith": "ANONYMOUS" 32 | } 33 | }, 34 | "customSettings": { 35 | "perturbCustomerSetting": { 36 | "span": 1, 37 | "roundTo": 2, 38 | "rangeType": "Proportional" 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-invalid-DicomTag.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "tag": "(0040)", 5 | "method": "perturb" 6 | }, 7 | { 8 | "tag": "DA", 9 | "method": "dateshift" 10 | }, 11 | { 12 | "tag": "DA", 13 | "method": "dateshift", 14 | "params":"3" 15 | } 16 | ], 17 | "defaultSettings": { 18 | "perturb": { 19 | "span": 1, 20 | "roundTo": 2, 21 | "rangeType": "Proportional" 22 | }, 23 | "dateShift": { 24 | "dateShiftKey": "123", 25 | "dateShiftScope": "SeriesInstance", 26 | "dateShiftRange": 50 27 | }, 28 | "cryptoHash": { 29 | "cryptoHashKey": "123" 30 | }, 31 | "redact": { 32 | "enablePartialAgesForRedact": false, 33 | "enablePartialDatesForRedact": false 34 | }, 35 | "encrypt": { 36 | "encryptKey": "123456781234567812345678" 37 | }, 38 | "substitute": { 39 | "replaceWith": "ANONYMOUS" 40 | } 41 | }, 42 | "customSettings": { 43 | "perturbCustomerSetting": { 44 | "span": "1", 45 | "roundTo": 2, 46 | "rangeType": "Proportional" 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-invalid-parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "tag": "DA", 5 | "method": "dateshift" 6 | }, 7 | { 8 | "tag": "DA", 9 | "method": "dateshift", 10 | "params": [ 3 ] 11 | } 12 | ], 13 | "defaultSettings": { 14 | "perturb": { 15 | "span": 1, 16 | "roundTo": 2, 17 | "rangeType": "Proportional" 18 | }, 19 | "dateShift": { 20 | "dateShiftKey": "123", 21 | "dateShiftScope": "SeriesInstance", 22 | "dateShiftRange": 50 23 | }, 24 | "cryptoHash": { 25 | "cryptoHashKey": "123" 26 | }, 27 | "redact": { 28 | "enablePartialAgesForRedact": false, 29 | "enablePartialDatesForRedact": false 30 | }, 31 | "encrypt": { 32 | "encryptKey": "123456781234567812345678" 33 | }, 34 | "substitute": { 35 | "replaceWith": "ANONYMOUS" 36 | } 37 | }, 38 | "customSettings": { 39 | "perturbCustomerSetting": { 40 | "span": 1, 41 | "roundTo": 2, 42 | "rangeType": "Proportional" 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-invalid-string-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "tag": "AE", 5 | "method": "cryptohash" 6 | }, 7 | { 8 | "tag": "AS", 9 | "method": "cryptohash" 10 | }, 11 | { 12 | "tag": "CS", 13 | "method": "cryptohash" 14 | }, 15 | { 16 | "tag": "DS", 17 | "method": "cryptohash" 18 | }, 19 | { 20 | "tag": "RetrieveAETitle", 21 | "method": "substitute", 22 | "params": { "replaceWith": "AnonymousAnonymous" } 23 | }, 24 | { 25 | "tag": "DA", 26 | "method": "cryptohash" 27 | } 28 | ], 29 | "defaultSettings": { 30 | "perturb": { 31 | "span": 1, 32 | "roundTo": 2, 33 | "rangeType": "Proportional" 34 | }, 35 | "dateShift": { 36 | "dateShiftKey": "123", 37 | "dateShiftScope": "SeriesInstance", 38 | "dateShiftRange": 50 39 | }, 40 | "cryptoHash": { 41 | "cryptoHashKey": "123" 42 | }, 43 | "redact": { 44 | "enablePartialAgesForRedact": false, 45 | "enablePartialDatesForRedact": false 46 | }, 47 | "encrypt": { 48 | "encryptKey": "123456781234567812345678" 49 | }, 50 | "substitute": { 51 | "replaceWith": "ANONYMOUS" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-miss-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSettings": { 3 | "perturb": { 4 | "span": 1, 5 | "roundTo": 2, 6 | "rangeType": "Proportional" 7 | }, 8 | "dateShift": { 9 | "dateShiftKey": "123", 10 | "dateShiftScope": "SeriesInstance", 11 | "dateShiftRange": 50 12 | }, 13 | "cryptoHash": { 14 | "cryptoHashKey": "123" 15 | }, 16 | "redact": { 17 | "enablePartialAgesForRedact": false, 18 | "enablePartialDatesForRedact": false 19 | }, 20 | "encrypt": { 21 | "encryptKey": "123456781234567812345678" 22 | }, 23 | "substitute": { 24 | "replaceWith": "ANONYMOUS" 25 | } 26 | }, 27 | "customSettings": { 28 | "perturbCustomerSetting": { 29 | "span": 1, 30 | "roundTo": 2, 31 | "rangeType": "Proportional" 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-miss-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "method": "dateshift" 5 | } 6 | ], 7 | "defaultSettings": { 8 | "perturb": { 9 | "span": 1, 10 | "roundTo": 2, 11 | "rangeType": "Proportional" 12 | }, 13 | "dateShift": { 14 | "dateShiftKey": "123", 15 | "dateShiftScope": "SeriesInstance", 16 | "dateShiftRange": 50 17 | }, 18 | "cryptoHash": { 19 | "cryptoHashKey": "123" 20 | }, 21 | "redact": { 22 | "enablePartialAgesForRedact": false, 23 | "enablePartialDatesForRedact": false 24 | }, 25 | "encrypt": { 26 | "encryptKey": "123456781234567812345678" 27 | }, 28 | "substitute": { 29 | "replaceWith": "ANONYMOUS" 30 | } 31 | }, 32 | "customSettings": { 33 | "perturbCustomerSetting": { 34 | "span": 1, 35 | "roundTo": 2, 36 | "rangeType": "Proportional" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-test-engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "tag": "RetrieveAETitle", 5 | "method": "substitute", 6 | "params": { "replaceWith": "newValue" } 7 | }, 8 | { 9 | "tag": "PatientAge", 10 | "method": "substitute", 11 | "params": { "replaceWith": "050Y" } 12 | }, 13 | { 14 | "tag": "LongCodeValue", 15 | "method": "cryptoHash" 16 | } 17 | ], 18 | "defaultSettings": { 19 | "perturb": { 20 | "span": 1, 21 | "roundTo": 2, 22 | "rangeType": "Proportional" 23 | }, 24 | "dateShift": { 25 | "dateShiftKey": "123", 26 | "dateShiftScope": "SeriesInstance", 27 | "dateShiftRange": 50 28 | }, 29 | "cryptoHash": { 30 | "cryptoHashKey": "123" 31 | }, 32 | "redact": { 33 | "enablePartialAgesForRedact": false, 34 | "enablePartialDatesForRedact": false 35 | }, 36 | "encrypt": { 37 | "encryptKey": "123456781234567812345678" 38 | }, 39 | "substitute": { 40 | "replaceWith": "ANONYMOUS" 41 | } 42 | }, 43 | "customSettings": { 44 | "perturbCustomerSetting": { 45 | "span": 1, 46 | "roundTo": 2, 47 | "rangeType": "Proportional" 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-test-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "tag": "(0008,0050)", 5 | "method": "redact" 6 | }, 7 | { 8 | "tag": "DA", 9 | "method": "dateshift" 10 | } 11 | ], 12 | "defaultSettings": { 13 | "perturb": { 14 | "span": 1, 15 | "roundTo": 2, 16 | "rangeType": "Proportional" 17 | }, 18 | "dateShift": { 19 | "dateShiftKey": "123", 20 | "dateShiftScope": "SeriesInstance", 21 | "dateShiftRange": 50 22 | }, 23 | "cryptoHash": { 24 | "cryptoHashKey": "123" 25 | }, 26 | "redact": { 27 | "enablePartialAgesForRedact": false, 28 | "enablePartialDatesForRedact": false 29 | }, 30 | "encrypt": { 31 | "encryptKey": "123456781234567812345678" 32 | }, 33 | "substitute": { 34 | "replaceWith": "ANONYMOUS" 35 | } 36 | }, 37 | "customSettings": { 38 | "perturbCustomerSetting": { 39 | "span": "1", 40 | "roundTo": 2, 41 | "rangeType": "Proportional" 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core.UnitTests/TestConfigurations/configuration-unsupported-method.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "tag": "(0008,0050)", 5 | "method": "invalid" 6 | } 7 | ], 8 | "defaultSettings": { 9 | "perturb": { 10 | "span": 1, 11 | "roundTo": 2, 12 | "rangeType": "Proportional" 13 | }, 14 | "dateShift": { 15 | "dateShiftKey": "123", 16 | "dateShiftScope": "SeriesInstance", 17 | "dateShiftRange": 50 18 | }, 19 | "cryptoHash": { 20 | "cryptoHashKey": "123" 21 | }, 22 | "redact": { 23 | "enablePartialAgesForRedact": false, 24 | "enablePartialDatesForRedact": false 25 | }, 26 | "encrypt": { 27 | "encryptKey": "123456781234567812345678" 28 | }, 29 | "substitute": { 30 | "replaceWith": "ANONYMOUS" 31 | } 32 | }, 33 | "customSettings": { 34 | "perturbCustomerSetting": { 35 | "span": 1, 36 | "roundTo": 2, 37 | "rangeType": "Proportional" 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/AnonymizerLogging.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.Core 9 | { 10 | public static class AnonymizerLogging 11 | { 12 | public static ILoggerFactory LoggerFactory { get; set; } = new LoggerFactory(); 13 | 14 | public static ILogger CreateLogger() => LoggerFactory.CreateLogger(); 15 | } 16 | } -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Constants.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using Microsoft.Health.Dicom.Anonymizer.Core.Models; 10 | 11 | namespace Microsoft.Health.Dicom.Anonymizer.Core 12 | { 13 | internal static class Constants 14 | { 15 | internal const string TagKey = "tag"; 16 | internal const string MethodKey = "method"; 17 | internal const string Parameters = "params"; 18 | internal const string RuleSetting = "setting"; 19 | internal static readonly HashSet BuiltInMethods = Enum.GetNames(typeof(AnonymizerMethod)).ToHashSet(StringComparer.InvariantCultureIgnoreCase); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Exceptions/AddCustomProcessorException.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Exceptions 9 | { 10 | public class AddCustomProcessorException : DicomAnonymizationException 11 | { 12 | public AddCustomProcessorException(DicomAnonymizationErrorCode errorCode, string message) 13 | : base(errorCode, message) 14 | { 15 | } 16 | 17 | public AddCustomProcessorException(DicomAnonymizationErrorCode errorCode, string message, Exception innerException) 18 | : base(errorCode, message, innerException) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Exceptions/AnonymizerConfigurationException.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Exceptions 9 | { 10 | public class AnonymizerConfigurationException : DicomAnonymizationException 11 | { 12 | public AnonymizerConfigurationException(DicomAnonymizationErrorCode errorCode, string message) 13 | : base(errorCode, message) 14 | { 15 | } 16 | 17 | public AnonymizerConfigurationException(DicomAnonymizationErrorCode errorCode, string message, Exception innerException) 18 | : base(errorCode, message, innerException) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Exceptions/AnonymizerOperationException.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Exceptions 9 | { 10 | public class AnonymizerOperationException : DicomAnonymizationException 11 | { 12 | public AnonymizerOperationException(DicomAnonymizationErrorCode errorCode, string message) 13 | : base(errorCode, message) 14 | { 15 | } 16 | 17 | public AnonymizerOperationException(DicomAnonymizationErrorCode errorCode, string message, Exception innerException) 18 | : base(errorCode, message, innerException) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Exceptions/DicomAnonymizationErrorCode.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Exceptions 7 | { 8 | public enum DicomAnonymizationErrorCode 9 | { 10 | ParsingJsonConfigurationFailed = 1001, 11 | MissingConfigurationFields = 1002, 12 | InvalidConfigurationValues = 1003, 13 | UnsupportedAnonymizationRule = 1004, 14 | MissingRuleSettings = 1005, 15 | InvalidRuleSettings = 1006, 16 | 17 | UnsupportedAnonymizationMethod = 1101, 18 | AddCustomProcessorFailed = 1102, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Exceptions/DicomAnonymizationException.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Exceptions 9 | { 10 | public class DicomAnonymizationException : Exception 11 | { 12 | public DicomAnonymizationException(DicomAnonymizationErrorCode errorCode, string message) 13 | : base(message) 14 | { 15 | DicomAnonymizerErrorCode = errorCode; 16 | } 17 | 18 | public DicomAnonymizationException(DicomAnonymizationErrorCode errorCode, string message, Exception innerException) 19 | : base(message, innerException) 20 | { 21 | DicomAnonymizerErrorCode = errorCode; 22 | } 23 | 24 | public DicomAnonymizationErrorCode DicomAnonymizerErrorCode { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Microsoft.Health.Dicom.Anonymizer.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net6.0 6 | Microsoft.Health.Dicom.Anonymizer.Core 7 | MIT 8 | Copyright © Microsoft Corporation. All rights reserved. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Models/AnonymizerConfiguration.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Runtime.Serialization; 8 | using Newtonsoft.Json.Linq; 9 | 10 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Models 11 | { 12 | [DataContract] 13 | public class AnonymizerConfiguration 14 | { 15 | [DataMember(Name = "rules")] 16 | public JObject[] RuleContent { get; set; } 17 | 18 | [DataMember(Name = "defaultSettings")] 19 | public AnonymizerDefaultSettings DefaultSettings { get; set; } 20 | 21 | [DataMember(Name = "customSettings")] 22 | public Dictionary CustomSettings { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Models/AnonymizerDefaultSettings.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Runtime.Serialization; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Models 10 | { 11 | [DataContract] 12 | public class AnonymizerDefaultSettings 13 | { 14 | [DataMember(Name = "perturb")] 15 | public JObject PerturbDefaultSetting { get; set; } 16 | 17 | [DataMember(Name = "substitute")] 18 | public JObject SubstituteDefaultSetting { get; set; } 19 | 20 | [DataMember(Name = "dateshift")] 21 | public JObject DateShiftDefaultSetting { get; set; } 22 | 23 | [DataMember(Name = "encrypt")] 24 | public JObject EncryptDefaultSetting { get; set; } 25 | 26 | [DataMember(Name = "cryptoHash")] 27 | public JObject CryptoHashDefaultSetting { get; set; } 28 | 29 | [DataMember(Name = "redact")] 30 | public JObject RedactDefaultSetting { get; set; } 31 | 32 | public JObject GetDefaultSetting(string method) 33 | { 34 | return method.ToLower() switch 35 | { 36 | "perturb" => PerturbDefaultSetting, 37 | "substitute" => SubstituteDefaultSetting, 38 | "dateshift" => DateShiftDefaultSetting, 39 | "encrypt" => EncryptDefaultSetting, 40 | "cryptohash" => CryptoHashDefaultSetting, 41 | "redact" => RedactDefaultSetting, 42 | _ => null, 43 | }; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Models/AnonymizerEngineOptions.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Models 7 | { 8 | public class AnonymizerEngineOptions 9 | { 10 | public AnonymizerEngineOptions(bool validateInput = false, bool validateOutput = false) 11 | { 12 | ValidateInput = validateInput; 13 | ValidateOutput = validateOutput; 14 | } 15 | 16 | public bool ValidateInput { get; } 17 | 18 | public bool ValidateOutput { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Models/AnonymizerMethod.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Models 7 | { 8 | public enum AnonymizerMethod 9 | { 10 | Redact, 11 | DateShift, 12 | CryptoHash, 13 | Keep, 14 | Perturb, 15 | Encrypt, 16 | Remove, 17 | RefreshUID, 18 | Substitute, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Models/ProcessContext.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using FellowOakDicom; 8 | 9 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Models 10 | { 11 | public class ProcessContext 12 | { 13 | public HashSet VisitedNodes { get; set; } = new HashSet(); 14 | 15 | public string StudyInstanceUID { get; set; } 16 | 17 | public string SeriesInstanceUID { get; set; } 18 | 19 | public string SopInstanceUID { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Processors/Factories/AnonymizerSettingsFactory.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using EnsureThat; 8 | using Microsoft.Health.Dicom.Anonymizer.Core.Exceptions; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors 12 | { 13 | public class AnonymizerSettingsFactory 14 | { 15 | public T CreateAnonymizerSetting(JObject settings) 16 | { 17 | EnsureArg.IsNotNull(settings, nameof(settings)); 18 | 19 | try 20 | { 21 | return settings.ToObject(); 22 | } 23 | catch (Exception ex) 24 | { 25 | throw new AnonymizerConfigurationException(DicomAnonymizationErrorCode.InvalidRuleSettings, $"Failed to parse rule setting: [{settings}]", ex); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Processors/Factories/DicomProcessorFactory.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using EnsureThat; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors 10 | { 11 | public class DicomProcessorFactory : IAnonymizerProcessorFactory 12 | { 13 | public virtual IAnonymizerProcessor CreateProcessor(string method, JObject settingObject = null) 14 | { 15 | EnsureArg.IsNotNullOrEmpty(method, nameof(method)); 16 | 17 | return method.ToLower() switch 18 | { 19 | "perturb" => new PerturbProcessor(settingObject), 20 | "substitute" => new SubstituteProcessor(settingObject), 21 | "dateshift" => new DateShiftProcessor(settingObject), 22 | "encrypt" => new EncryptProcessor(settingObject), 23 | "cryptohash" => new CryptoHashProcessor(settingObject), 24 | "redact" => new RedactProcessor(settingObject), 25 | "remove" => new RemoveProcessor(), 26 | "refreshuid" => new RefreshUIDProcessor(), 27 | "keep" => new KeepProcessor(), 28 | _ => null, 29 | }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Processors/Factories/IAnonymizerProcessorFactory.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors 9 | { 10 | public interface IAnonymizerProcessorFactory 11 | { 12 | public IAnonymizerProcessor CreateProcessor(string anonymizeMethod, JObject ruleSetting = null); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Processors/IAnonymizerProcessor.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using FellowOakDicom; 7 | using Microsoft.Health.Dicom.Anonymizer.Core.Models; 8 | 9 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors 10 | { 11 | public interface IAnonymizerProcessor 12 | { 13 | public void Process(DicomDataset dicomDataset, DicomItem item, ProcessContext context); 14 | 15 | bool IsSupported(DicomItem item); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Processors/KeepProcessor.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using FellowOakDicom; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Health.Dicom.Anonymizer.Core.Models; 9 | 10 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors 11 | { 12 | /// 13 | /// Keep the original value. 14 | /// 15 | public class KeepProcessor : IAnonymizerProcessor 16 | { 17 | private readonly ILogger _logger = AnonymizerLogging.CreateLogger(); 18 | 19 | public void Process(DicomDataset dicomDataset, DicomItem item, ProcessContext context = null) 20 | { 21 | _logger.LogDebug($"The value of DICOM item '{item}' is kept."); 22 | } 23 | 24 | public bool IsSupported(DicomItem item) 25 | { 26 | return true; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Processors/Models/DateShiftScope.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Runtime.Serialization; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Converters; 9 | 10 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors 11 | { 12 | [JsonConverter(typeof(StringEnumConverter))] 13 | public enum DateShiftScope 14 | { 15 | [EnumMember(Value = "StudyInstance")] 16 | StudyInstance, 17 | [EnumMember(Value = "SeriesInstance")] 18 | SeriesInstance, 19 | [EnumMember(Value = "SopInstance")] 20 | SopInstance, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Processors/RemoveProcessor.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using FellowOakDicom; 7 | using EnsureThat; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Health.Dicom.Anonymizer.Core.Models; 10 | 11 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors 12 | { 13 | /// 14 | /// Used to remove the entire DICOM item. 15 | /// 16 | public class RemoveProcessor : IAnonymizerProcessor 17 | { 18 | private readonly ILogger _logger = AnonymizerLogging.CreateLogger(); 19 | 20 | public void Process(DicomDataset dicomDataset, DicomItem item, ProcessContext context = null) 21 | { 22 | EnsureArg.IsNotNull(dicomDataset, nameof(dicomDataset)); 23 | EnsureArg.IsNotNull(item, nameof(item)); 24 | 25 | dicomDataset.Remove(item.Tag); 26 | 27 | _logger.LogDebug($"The DICOM item '{item}' is removed."); 28 | } 29 | 30 | public bool IsSupported(DicomItem item) 31 | { 32 | return true; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Rules/AnonymizerTagRule.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using FellowOakDicom; 8 | using EnsureThat; 9 | using Microsoft.Health.Dicom.Anonymizer.Core.Models; 10 | using Microsoft.Health.Dicom.Anonymizer.Core.Processors; 11 | using Newtonsoft.Json.Linq; 12 | 13 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Rules 14 | { 15 | public class AnonymizerTagRule : AnonymizerRule 16 | { 17 | public AnonymizerTagRule(DicomTag tag, string method, string description, IAnonymizerProcessorFactory processorFactory, JObject ruleSetting = null) 18 | : base(method, description, processorFactory, ruleSetting) 19 | { 20 | EnsureArg.IsNotNull(tag, nameof(tag)); 21 | 22 | Tag = tag; 23 | } 24 | 25 | public DicomTag Tag { get; set; } 26 | 27 | public override List LocateDicomTag(DicomDataset dataset, ProcessContext context) 28 | { 29 | EnsureArg.IsNotNull(dataset, nameof(dataset)); 30 | EnsureArg.IsNotNull(context, nameof(context)); 31 | 32 | var item = dataset.GetDicomItem(Tag); 33 | var result = new List(); 34 | if (item != null && !context.VisitedNodes.Contains(item.ToString())) 35 | { 36 | result.Add(item); 37 | } 38 | 39 | return result; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Rules/AnonymizerVRRule.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using FellowOakDicom; 9 | using EnsureThat; 10 | using Microsoft.Health.Dicom.Anonymizer.Core.Models; 11 | using Microsoft.Health.Dicom.Anonymizer.Core.Processors; 12 | using Newtonsoft.Json.Linq; 13 | 14 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Rules 15 | { 16 | public class AnonymizerVRRule : AnonymizerRule 17 | { 18 | public AnonymizerVRRule(DicomVR vr, string method, string description, IAnonymizerProcessorFactory processorFactory, JObject ruleSetting = null) 19 | : base(method, description, processorFactory, ruleSetting) 20 | { 21 | EnsureArg.IsNotNull(vr, nameof(vr)); 22 | 23 | VR = vr; 24 | } 25 | 26 | public DicomVR VR { get; set; } 27 | 28 | public override List LocateDicomTag(DicomDataset dataset, ProcessContext context) 29 | { 30 | EnsureArg.IsNotNull(dataset, nameof(dataset)); 31 | EnsureArg.IsNotNull(context, nameof(context)); 32 | 33 | var locatedItems = new List(); 34 | foreach (var item in dataset) 35 | { 36 | if (string.Equals(item.ValueRepresentation.Code, VR?.Code)) 37 | { 38 | locatedItems.Add(item); 39 | } 40 | } 41 | 42 | return locatedItems.Where(x => !context.VisitedNodes.Contains(x.ToString())).ToList(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Rules/IAnonymizerRuleFactory.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace Microsoft.Health.Dicom.Anonymizer.Core.Rules 9 | { 10 | public interface IAnonymizerRuleFactory 11 | { 12 | AnonymizerRule CreateDicomAnonymizationRule(JObject rule); 13 | 14 | AnonymizerRule[] CreateDicomAnonymizationRules(JObject[] rule); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DICOM/stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "documentationRules": { 5 | "companyName": "Microsoft Corporation", 6 | "copyrightText": "-------------------------------------------------------------------------------------------------\nCopyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} License ({licenseName}). See {licenseFile} in the repo root for license information.\n-------------------------------------------------------------------------------------------------", 7 | "variables": { 8 | "licenseName": "MIT", 9 | "licenseFile": "LICENSE" 10 | }, 11 | "xmlHeader": false, 12 | "headerDecoration": "-------------------------------------------------------------------------------------------------" 13 | }, 14 | "orderingRules": { 15 | "systemUsingDirectivesFirst": true, 16 | "usingDirectivesPlacement": "outsideNamespace", 17 | "elementOrder": [ 18 | "kind" 19 | ] 20 | }, 21 | "indentation": { 22 | "useTabs": false, 23 | "indentationSize": 4, 24 | "tabSize": 4 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline.UnitTests/Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline/AzureDataFactorySettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataFactoryName": "DataFactoryName", 3 | "resourceLocation": "WestUS", 4 | "sourceStorageAccountName": "sourceStorageAccountName", 5 | "sourceStorageAccountKey": "sourceStorageAccountKey", 6 | "destinationStorageAccountName": "destinationStorageAccountName", 7 | "destinationStorageAccountKey": "destinationStorageAccountKey", 8 | "sourceStorageContainerName": "sourceStorageContainerName", 9 | "destinationStorageContainerName": "destinationStorageContainerName", 10 | "sourceContainerFolderPath": "", 11 | "destinationContainerFolderPath": "", 12 | "activityContainerName": "customactivityContainerName" 13 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline/Microsoft.Health.Fhir.Anonymizer.R4.AzureDataFactoryPipeline.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | Auto 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.CommandLineTool/Microsoft.Health.Fhir.Anonymizer.R4.CommandLineTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.Core.UnitTests/Microsoft.Health.Fhir.Anonymizer.R4.Core.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.Core/Constants.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core 4 | { 5 | internal static partial class Constants 6 | { 7 | // InstanceType constants 8 | internal const string SupportedVersion = "R4"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.Core/Microsoft.Health.Fhir.Anonymizer.R4.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net6.0 6 | true 7 | MIT 8 | Copyright © Microsoft Corporation. All rights reserved. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.Core/Processors/PerturbProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hl7.Fhir.Model; 3 | 4 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors 5 | { 6 | public partial class PerturbProcessor : IAnonymizerProcessor 7 | { 8 | private static readonly HashSet s_quantityTypeNames = new HashSet 9 | { 10 | FHIRAllTypes.Age.ToString(), 11 | FHIRAllTypes.Count.ToString(), 12 | FHIRAllTypes.Duration.ToString(), 13 | FHIRAllTypes.Distance.ToString(), 14 | FHIRAllTypes.Money.ToString(), 15 | FHIRAllTypes.MoneyQuantity.ToString(), 16 | FHIRAllTypes.Quantity.ToString(), 17 | FHIRAllTypes.SimpleQuantity.ToString() 18 | }; 19 | } 20 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.R4.FunctionalTests/Microsoft.Health.Fhir.Anonymizer.R4.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 24ca7a36-95ff-4e86-9ed8-2c073576db7b 7 | 8 | 9 | Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.UnitTests.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24ca7a36-95ff-4e86-9ed8-2c073576db7b 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | b1cc2f6c-f581-4495-8b38-adbfbb1a6c29 7 | 8 | 9 | Fhir.Anonymizer.Shared.AzureDataFactoryPipeline 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b1cc2f6c-f581-4495-8b38-adbfbb1a6c29 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline/scripts/ArmTemplate/arm_template_parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "value": "" 7 | }, 8 | "factoryName": { 9 | "value": "" 10 | }, 11 | "sourceStorageLinkedService_connectionString": { 12 | "value": "" 13 | }, 14 | "sourceStorageActivityApplicationContainer": { 15 | "value": "" 16 | }, 17 | "sourceStorageContainerName": { 18 | "value": "" 19 | }, 20 | "sourceContainerFolderPath": { 21 | "value": "" 22 | }, 23 | "destinationStorageLinkedService_connectionString": { 24 | "value": "" 25 | }, 26 | "destinationStorageContainerName": { 27 | "value": "" 28 | }, 29 | "destinationContainerFolderPath": { 30 | "value": "" 31 | }, 32 | "azureBatchLinkedService_accessKey": { 33 | "value": "" 34 | }, 35 | "azureBatchLinkedService_poolName": { 36 | "value": "" 37 | }, 38 | "azureBatchLinkedService_properties_typeProperties_accountName": { 39 | "value": "" 40 | }, 41 | "azureBatchLinkedService_properties_typeProperties_batchUri": { 42 | "value": "" 43 | }, 44 | "fhirVersion": { 45 | "value": "" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline/scripts/CustomActivity.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .PARAMETER FhirVersion 3 | Specify FHIR version. 4 | #> 5 | 6 | [cmdletbinding()] 7 | param( 8 | [string]$FhirVersion 9 | ) 10 | 11 | # Script to run custom activity executable 12 | $AppFolder = "$FhirVersion.AdfApplication" 13 | Expand-Archive -Path "$AppFolder.zip" -DestinationPath . 14 | &".\Microsoft.Health.Fhir.Anonymizer.$FhirVersion.AzureDataFactoryPipeline.exe" -f 15 | if ($LastExitCode -ne 0) 16 | { 17 | Write-Host "An error occurred." 18 | exit 1 19 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline/src/ActivityInputData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.DataFactoryTool 6 | { 7 | public class ActivityInputData 8 | { 9 | public string SourceStorageConnectionString { get; set; } 10 | public string DestinationStorageConnectionString { get; set; } 11 | public string SourceContainerName { get; set; } 12 | public string DestinationContainerName { get; set; } 13 | public string SourceFolderPath { get; set; } 14 | public string DestinationFolderPath { get; set; } 15 | public bool SkipExistedFile { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline/src/FhirAzureConstants.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.AzureDataFactoryPipeline.src 2 | { 3 | public static class FhirAzureConstants 4 | { 5 | public const int KB = 1024; 6 | public const int MB = KB * 1024; 7 | public const int BlockBufferSize = 4 * MB; 8 | public const int DefaultConcurrentCount = 3; 9 | public const int DefaultBlockDownloadTimeoutInSeconds = 5 * 60; 10 | public const int DefaultBlockDownloadTimeoutRetryCount = 3; 11 | public const int DefaultUploadBlockThreshold = 32 * MB; 12 | public const int DefaultBlockUploadTimeoutInSeconds = 10 * 60; 13 | public const int DefaultBlockUploadTimeoutRetryCount = 3; 14 | public const int StorageOperationRetryDelayInSeconds = 30; 15 | public const int StorageOperationRetryMaxDelayInSeconds = 120; 16 | public const int StorageOperationRetryCount = 3; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.AzureDataFactoryPipeline/src/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CommandLine; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.DataFactoryTool 6 | { 7 | public class Options 8 | { 9 | [Option('f', "force", Required = false, HelpText = "Force overwrite the exist blob files in the output container.")] 10 | public bool Force { get; set; } 11 | } 12 | 13 | class Program 14 | { 15 | static async Task Main(string[] args) 16 | { 17 | await Parser.Default.ParseArguments(args) 18 | .MapResult(async option => await new DataFactoryCustomActivity().Run(option.Force).ConfigureAwait(false), _ => Task.FromResult(1)).ConfigureAwait(false); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool/AnonymizationToolOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Tool 6 | { 7 | public class AnonymizationToolOptions 8 | { 9 | public bool IsRecursive { get; set; } 10 | public bool ValidateInput { get; set; } 11 | public bool ValidateOutput { get; set; } 12 | public bool SkipExistedFile { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool/Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 140255c7-6c80-4a3c-a248-ae8cdfdcf907 7 | 8 | 9 | Fhir.Anonymizer.Shared.CommandLineTool 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool/Microsoft.Health.Fhir.Anonymizer.Shared.CommandLineTool.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 140255c7-6c80-4a3c-a248-ae8cdfdcf907 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/MaskProcessor.cs: -------------------------------------------------------------------------------- 1 | using Hl7.Fhir.ElementModel; 2 | using Microsoft.Health.Fhir.Anonymizer.Core.Models; 3 | using Microsoft.Health.Fhir.Anonymizer.Core.Processors; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace Microsoft.Health.Fhir.Anonymizer.Core.UnitTests 9 | { 10 | internal class MaskProcessor : IAnonymizerProcessor 11 | { 12 | private int _maskedLength; 13 | 14 | public MaskProcessor(JObject setting) 15 | { 16 | _maskedLength = int.Parse(setting.GetValue("maskedLength", StringComparison.OrdinalIgnoreCase).ToString()); 17 | } 18 | 19 | public ProcessResult Process(ElementNode node, ProcessContext context = null, Dictionary settings = null) 20 | { 21 | 22 | var result = new ProcessResult(); 23 | if (node.Value == null) 24 | { 25 | return result; 26 | } 27 | 28 | var mask = new string('*', this._maskedLength); 29 | node.Value = node.Value.ToString().Length > _maskedLength ? mask + node.Value.ToString()[this._maskedLength..] : mask; 30 | return result; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | f7a47fd1-bd24-41a6-b3c6-2dd062523271 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/MockAnonymizerProcessor.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using Hl7.Fhir.ElementModel; 9 | using Microsoft.Health.Fhir.Anonymizer.Core.Models; 10 | using Microsoft.Health.Fhir.Anonymizer.Core.Processors; 11 | using Newtonsoft.Json.Linq; 12 | 13 | namespace Microsoft.Health.Fhir.Anonymizer.Core.UnitTests 14 | { 15 | public class MockAnonymizerProcessor : IAnonymizerProcessor 16 | { 17 | public MockAnonymizerProcessor(JObject settintgs) 18 | { 19 | } 20 | 21 | public ProcessResult Process(ElementNode node, ProcessContext context = null, Dictionary settings = null) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/PartitionedExecution/FhirEnumerableReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Health.Fhir.Anonymizer.Core.PartitionedExecution; 7 | using Xunit; 8 | 9 | namespace Microsoft.Health.Fhir.Anonymizer.Core.UnitTests.PartitionedExecution 10 | { 11 | public class FhirEnumerableReaderTests 12 | { 13 | [Fact] 14 | public async Task GivenAFhirEnumerableReader_ReadData_ResultShouldBeReturnedInOrder() 15 | { 16 | int end = 987; 17 | var reader = new FhirEnumerableReader(Enumerable.Range(0, end).Select(i => i.ToString())); 18 | 19 | int i = 0; 20 | string nextLine = null; 21 | while ((nextLine = await reader.NextAsync()) != null) 22 | { 23 | Assert.Equal(nextLine, i.ToString()); 24 | i++; 25 | } 26 | 27 | Assert.Equal(i, end); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/PartitionedExecution/FhirStreamConsumerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.Health.Fhir.Anonymizer.Core.PartitionedExecution; 5 | using Xunit; 6 | 7 | namespace Microsoft.Health.Fhir.Anonymizer.Core.UnitTests.PartitionedExecution 8 | { 9 | public class FhirStreamConsumerTests 10 | { 11 | [Fact] 12 | public async Task GivenAFhirStreamConsumer_WhenConsumeData_ShouldReadAllDataFromStream() 13 | { 14 | using MemoryStream outputStream = new MemoryStream(); 15 | using FhirStreamConsumer consumer = new FhirStreamConsumer(outputStream); 16 | 17 | int count = await consumer.ConsumeAsync(new List() { "abc", "bcd", ""}); 18 | Assert.Equal(3, count); 19 | 20 | await consumer.CompleteAsync(); 21 | 22 | outputStream.Position = 0; 23 | using StreamReader reader = new StreamReader(outputStream); 24 | Assert.Equal("abc", await reader.ReadLineAsync()); 25 | Assert.Equal("bcd", await reader.ReadLineAsync()); 26 | Assert.Equal("", await reader.ReadLineAsync()); 27 | Assert.Null(await reader.ReadLineAsync()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/PartitionedExecution/FhirStreamReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.UnitTests.PartitionedExecution 6 | { 7 | public class FhirStreamReaderTests 8 | { 9 | [Fact] 10 | public async Task GivenAFhirStreamReader_WhenLoadData_ShouldLoadAllDataFromStream() 11 | { 12 | using MemoryStream inputStream = new MemoryStream(); 13 | using StreamWriter writer = new StreamWriter(inputStream); 14 | await writer.WriteLineAsync("abc"); 15 | await writer.WriteLineAsync("bcd"); 16 | await writer.WriteLineAsync(""); 17 | writer.Flush(); 18 | 19 | inputStream.Position = 0; 20 | using FhirStreamReader reader = new FhirStreamReader(inputStream); 21 | 22 | Assert.Equal("abc", await reader.NextAsync()); 23 | Assert.Equal("bcd", await reader.NextAsync()); 24 | Assert.Equal("", await reader.NextAsync()); 25 | Assert.Null(await reader.NextAsync()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/Processors/CustomProcessorFactoryUnitTests.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using Microsoft.Health.Fhir.Anonymizer.Core.Exceptions; 7 | using Microsoft.Health.Fhir.Anonymizer.Core.Processors; 8 | using Newtonsoft.Json.Linq; 9 | using Xunit; 10 | 11 | namespace Microsoft.Health.Fhir.Anonymizer.Core.UnitTests.Processors 12 | { 13 | public class CustomProcessorFactoryUnitTests 14 | { 15 | [Fact] 16 | public void GivenAFhirProcessorFactory_AddingCustomProcessor_GivenMethod_CorrectProcessorWillBeReturned() 17 | { 18 | var factory = new CustomProcessorFactory(); 19 | factory.RegisterProcessors(typeof(MaskProcessor), typeof(MockAnonymizerProcessor)); 20 | Assert.Equal(typeof(MaskProcessor), factory.CreateProcessor("mask", JObject.Parse("{\"maskedLength\":\"3\"}")).GetType()); 21 | Assert.Equal(typeof(MockAnonymizerProcessor), factory.CreateProcessor("mockanonymizer", null).GetType()); 22 | } 23 | 24 | [Fact] 25 | public void GivenAFhirProcessorFactory_AddingBuildInProcessor_ExceptionWillBeThrown() 26 | { 27 | var factory = new CustomProcessorFactory(); 28 | Assert.Throws(() => factory.RegisterProcessors(typeof(RedactProcessor))); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-custom-processor.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "processingErrors": "raise", 4 | "fhirPathRules": [ 5 | { 6 | "path": "Patient.nodesByType('HumanName')", 7 | "method": "mask" 8 | }, 9 | { 10 | "path": "TestResource", 11 | "method": "redact" 12 | }, 13 | { 14 | "path": "nodesByType('HumanName')", 15 | "method": "redact" 16 | }, 17 | { 18 | "path": "Resource", 19 | "method": "keep" 20 | }, 21 | { 22 | "path": "Device.where(id.exists()).id", 23 | "method": "keep" 24 | } 25 | ], 26 | "parameters": { 27 | "customSettings": { 28 | "maskedLength": 3 29 | }, 30 | "dateShiftKey": "", 31 | "cryptoHashKey": "", 32 | "enablePartialAgesForRedact": true, 33 | "enablePartialDatesForRedact": true, 34 | "enablePartialZipCodesForRedact": true, 35 | "restrictedZipCodeTabulationAreas": [ 36 | "036", 37 | "059", 38 | "102", 39 | "203", 40 | "205", 41 | "369", 42 | "556", 43 | "692", 44 | "821", 45 | "823", 46 | "878", 47 | "879", 48 | "884", 49 | "893" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-generalize-fail-compiled-expression.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "generalize", 6 | "cases": { 7 | "$this>=0 && $this<10": "10" 8 | }, 9 | "otherValues": "keep" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-generalize-invalid-othervalues.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "generalize", 6 | "cases": { 7 | "$this>=0 and $this<10": "10" 8 | }, 9 | "otherValues": "unknown" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-generalize-miss-cases.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "generalize", 6 | "otherValues": "keep" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-invalid-encryptkey.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Patient.nodesByType('HumanName')", 6 | "method": "redact" 7 | }, 8 | { 9 | "path": "TestResource", 10 | "method": "redact" 11 | }, 12 | { 13 | "path": "nodesByType('HumanName')", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "Resource", 18 | "method": "keep" 19 | } 20 | ], 21 | "parameters": { 22 | "dateShiftKey": "", 23 | "encryptKey": "01234567890123456789", 24 | "enablePartialAgesForRedact": true, 25 | "enablePartialDatesForRedact": true, 26 | "enablePartialZipCodesForRedact": true, 27 | "restrictedZipCodeTabulationAreas": [ 28 | "036", 29 | "059", 30 | "102", 31 | "203", 32 | "205", 33 | "369", 34 | "556", 35 | "692", 36 | "821", 37 | "823", 38 | "878", 39 | "879", 40 | "884", 41 | "893" 42 | ] 43 | } 44 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-invalid-fhirpath.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "fhirPathRules": [ 4 | { 5 | "path": "invalid()", 6 | "method": "redact" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-miss-replacement.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Patient", 5 | "method": "substitute" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-miss-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "abc": "123" 4 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-perturb-exceed-28-roundTo.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "perturb", 6 | "span": 3, 7 | "roundTo": "29" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-perturb-miss-span.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "perturb" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-perturb-negative-roundTo.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "perturb", 6 | "span": 3, 7 | "roundTo": -1 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-perturb-negative-span.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "perturb", 6 | "span": "-1" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-perturb-wrong-rangetype.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "perturb", 6 | "span": 3, 7 | "rangeType": "unknown" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-perturb-wrong-roundTo.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Condition.onset as Age", 5 | "method": "perturb", 6 | "span": 3, 7 | "roundTo": "abc" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-raise-processing-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "processingErrors": "raise", 4 | "fhirPathRules": [ 5 | { 6 | "path": "Patient.nodesByType('HumanName')", 7 | "method": "redact" 8 | }, 9 | { 10 | "path": "TestResource", 11 | "method": "redact" 12 | }, 13 | { 14 | "path": "nodesByType('HumanName')", 15 | "method": "redact" 16 | }, 17 | { 18 | "path": "Resource", 19 | "method": "keep" 20 | }, 21 | { 22 | "path": "Device.where(id.exists()).id", 23 | "method": "keep" 24 | } 25 | ], 26 | "parameters": { 27 | "dateShiftKey": "", 28 | "cryptoHashKey": "", 29 | "enablePartialAgesForRedact": true, 30 | "enablePartialDatesForRedact": true, 31 | "enablePartialZipCodesForRedact": true, 32 | "restrictedZipCodeTabulationAreas": [ 33 | "036", 34 | "059", 35 | "102", 36 | "203", 37 | "205", 38 | "369", 39 | "556", 40 | "692", 41 | "821", 42 | "823", 43 | "878", 44 | "879", 45 | "884", 46 | "893" 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-test-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "processingErrors": "raise", 4 | "fhirPathRules": [ 5 | { 6 | "path": "Patient.nodesByType('HumanName')", 7 | "method": "redact" 8 | }, 9 | { 10 | "path": "TestResource", 11 | "method": "redact" 12 | }, 13 | { 14 | "path": "nodesByType('HumanName')", 15 | "method": "redact" 16 | }, 17 | { 18 | "path": "Resource", 19 | "method": "keep" 20 | }, 21 | { 22 | "path": "Device.where(id.exists()).id", 23 | "method": "keep" 24 | } 25 | ], 26 | "parameters": { 27 | "dateShiftKey": "", 28 | "cryptoHashKey": "", 29 | "enablePartialAgesForRedact": true, 30 | "enablePartialDatesForRedact": true, 31 | "enablePartialZipCodesForRedact": true, 32 | "restrictedZipCodeTabulationAreas": [ 33 | "036", 34 | "059", 35 | "102", 36 | "203", 37 | "205", 38 | "369", 39 | "556", 40 | "692", 41 | "821", 42 | "823", 43 | "878", 44 | "879", 45 | "884", 46 | "893" 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-unsupported-method.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Patient", 6 | "method": "skip" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurations/configuration-without-processing-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Patient.nodesByType('HumanName')", 6 | "method": "redact" 7 | }, 8 | { 9 | "path": "TestResource", 10 | "method": "redact" 11 | }, 12 | { 13 | "path": "nodesByType('HumanName')", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "Resource", 18 | "method": "keep" 19 | }, 20 | { 21 | "path": "Device.where(id.exists()).id", 22 | "method": "keep" 23 | } 24 | ], 25 | "parameters": { 26 | "dateShiftKey": "", 27 | "cryptoHashKey": "", 28 | "enablePartialAgesForRedact": true, 29 | "enablePartialDatesForRedact": true, 30 | "enablePartialZipCodesForRedact": true, 31 | "restrictedZipCodeTabulationAreas": [ 32 | "036", 33 | "059", 34 | "102", 35 | "203", 36 | "205", 37 | "369", 38 | "556", 39 | "692", 40 | "821", 41 | "823", 42 | "878", 43 | "879", 44 | "884", 45 | "893" 46 | ] 47 | } 48 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurationsVersion/configuration-R4-version.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "R4", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Patient.nodesByType('HumanName')", 6 | "method": "redact" 7 | }, 8 | { 9 | "path": "TestResource", 10 | "method": "redact" 11 | }, 12 | { 13 | "path": "nodesByType('HumanName')", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "Resource", 18 | "method": "keep" 19 | }, 20 | { 21 | "path": "Device.where(id.exists()).id", 22 | "method": "keep" 23 | } 24 | 25 | ], 26 | "parameters": { 27 | "dateShiftKey": "", 28 | "cryptoHashKey": "", 29 | "enablePartialAgesForRedact": true, 30 | "enablePartialDatesForRedact": true, 31 | "enablePartialZipCodesForRedact": true, 32 | "restrictedZipCodeTabulationAreas": [ 33 | "036", 34 | "059", 35 | "102", 36 | "203", 37 | "205", 38 | "369", 39 | "556", 40 | "692", 41 | "821", 42 | "823", 43 | "878", 44 | "879", 45 | "884", 46 | "893" 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurationsVersion/configuration-Stu3-version.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "Stu3", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Patient.nodesByType('HumanName')", 6 | "method": "redact" 7 | }, 8 | { 9 | "path": "TestResource", 10 | "method": "redact" 11 | }, 12 | { 13 | "path": "nodesByType('HumanName')", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "Resource", 18 | "method": "keep" 19 | }, 20 | { 21 | "path": "Device.where(id.exists()).id", 22 | "method": "keep" 23 | } 24 | 25 | ], 26 | "parameters": { 27 | "dateShiftKey": "", 28 | "cryptoHashKey": "", 29 | "enablePartialAgesForRedact": true, 30 | "enablePartialDatesForRedact": true, 31 | "enablePartialZipCodesForRedact": true, 32 | "restrictedZipCodeTabulationAreas": [ 33 | "036", 34 | "059", 35 | "102", 36 | "203", 37 | "205", 38 | "369", 39 | "556", 40 | "692", 41 | "821", 42 | "823", 43 | "878", 44 | "879", 45 | "884", 46 | "893" 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurationsVersion/configuration-empty-version.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Patient.nodesByType('HumanName')", 6 | "method": "redact" 7 | }, 8 | { 9 | "path": "TestResource", 10 | "method": "redact" 11 | }, 12 | { 13 | "path": "nodesByType('HumanName')", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "Resource", 18 | "method": "keep" 19 | }, 20 | { 21 | "path": "Device.where(id.exists()).id", 22 | "method": "keep" 23 | } 24 | 25 | ], 26 | "parameters": { 27 | "dateShiftKey": "", 28 | "cryptoHashKey": "", 29 | "enablePartialAgesForRedact": true, 30 | "enablePartialDatesForRedact": true, 31 | "enablePartialZipCodesForRedact": true, 32 | "restrictedZipCodeTabulationAreas": [ 33 | "036", 34 | "059", 35 | "102", 36 | "203", 37 | "205", 38 | "369", 39 | "556", 40 | "692", 41 | "821", 42 | "823", 43 | "878", 44 | "879", 45 | "884", 46 | "893" 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurationsVersion/configuration-invalid-version.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "r3", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Patient.nodesByType('HumanName')", 6 | "method": "redact" 7 | }, 8 | { 9 | "path": "TestResource", 10 | "method": "redact" 11 | }, 12 | { 13 | "path": "nodesByType('HumanName')", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "Resource", 18 | "method": "keep" 19 | }, 20 | { 21 | "path": "Device.where(id.exists()).id", 22 | "method": "keep" 23 | } 24 | 25 | ], 26 | "parameters": { 27 | "dateShiftKey": "", 28 | "cryptoHashKey": "", 29 | "enablePartialAgesForRedact": true, 30 | "enablePartialDatesForRedact": true, 31 | "enablePartialZipCodesForRedact": true, 32 | "restrictedZipCodeTabulationAreas": [ 33 | "036", 34 | "059", 35 | "102", 36 | "203", 37 | "205", 38 | "369", 39 | "556", 40 | "692", 41 | "821", 42 | "823", 43 | "878", 44 | "879", 45 | "884", 46 | "893" 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestConfigurationsVersion/configuration-null-version.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Patient.nodesByType('HumanName')", 5 | "method": "redact" 6 | }, 7 | { 8 | "path": "TestResource", 9 | "method": "redact" 10 | }, 11 | { 12 | "path": "nodesByType('HumanName')", 13 | "method": "redact" 14 | }, 15 | { 16 | "path": "Resource", 17 | "method": "keep" 18 | }, 19 | { 20 | "path": "Device.where(id.exists()).id", 21 | "method": "keep" 22 | }, 23 | 24 | ], 25 | "parameters": { 26 | "dateShiftKey": "", 27 | "cryptoHashKey": "", 28 | "enablePartialAgesForRedact": true, 29 | "enablePartialDatesForRedact": true, 30 | "enablePartialZipCodesForRedact": true, 31 | "restrictedZipCodeTabulationAreas": [ 32 | "036", 33 | "059", 34 | "102", 35 | "203", 36 | "205", 37 | "369", 38 | "556", 39 | "692", 40 | "821", 41 | "823", 42 | "878", 43 | "879", 44 | "884", 45 | "893" 46 | ] 47 | } 48 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestResources/bundle-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestResources/condition-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestResources/contained-basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "contained": [ 4 | { 5 | "resourceType": "Practitioner", 6 | "id": "p1", 7 | "name": [ 8 | { 9 | "family": "Person", 10 | "given": [ "Patricia" ] 11 | } 12 | ] 13 | } 14 | ], 15 | "subject": { 16 | "reference": "#p1" 17 | } 18 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/TestResources/patient-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core.UnitTests/Utility/CryptoHashUtilityTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Health.Fhir.Anonymizer.Core.Utility; 3 | using Xunit; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.UnitTests.Utility 6 | { 7 | public class CryptoHashUtilityTests 8 | { 9 | private const string TestHashKey = "123"; 10 | public static IEnumerable GetHmacHashData() 11 | { 12 | yield return new object[] { null, null }; 13 | yield return new object[] { string.Empty, string.Empty }; 14 | yield return new object[] { "abc", "8f16771f9f8851b26f4d460fa17de93e2711c7e51337cb8a608a0f81e1c1b6ae" }; 15 | yield return new object[] { "&*^%$@()=-,/", "33f6f7d6b3602bf5354dcb4b8d988982602349355f50f86798d8ce1ffd61521b" }; 16 | yield return new object[] { "ÆŊŋßſ♫∅", "1a94823f0a0f00a4b1ca771c3446dc5e17958f4dae3588ace2bca8a843eb63d9" }; 17 | yield return new object[] { "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()=-", 18 | "352b5a4af5adb81fa616c2a5b5c492d0b0b544c188a9aa003767a2b5efbd1478" }; 19 | } 20 | 21 | [Theory] 22 | [MemberData(nameof(GetHmacHashData))] 23 | public void GivenAString_WhenComputeHmac_CorrectHashShouldBeReturned(string input, string expectedHash) 24 | { 25 | string hash = CryptoHashUtility.ComputeHmacSHA256Hash(input, TestHashKey); 26 | Assert.Equal(expectedHash, hash); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerConfigurations/AnonymizerMethod.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations 2 | { 3 | public enum AnonymizerMethod 4 | { 5 | Redact, 6 | DateShift, 7 | CryptoHash, 8 | Substitute, 9 | Encrypt, 10 | Perturb, 11 | Keep, 12 | Generalize 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerConfigurations/AnonymizerRule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations 4 | { 5 | public class AnonymizerRule 6 | { 7 | public string Path { get; set; } 8 | 9 | public string Method { get; set; } 10 | 11 | public AnonymizerRuleType Type { get; set; } 12 | 13 | public string Source { get; set; } 14 | 15 | public Dictionary RuleSettings { get; set; } 16 | 17 | public AnonymizerRule(string path, string method, AnonymizerRuleType type, string source) 18 | { 19 | Path = path; 20 | Method = method; 21 | Type = type; 22 | Source = source; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerConfigurations/AnonymizerRuleType.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations 2 | { 3 | public enum AnonymizerRuleType 4 | { 5 | FhirPathRule 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerConfigurations/AnonymizerSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations 2 | { 3 | public class AnonymizerSettings 4 | { 5 | public bool IsPrettyOutput { get; set; } 6 | 7 | public bool ValidateInput { get; set; } 8 | 9 | public bool ValidateOutput { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerConfigurations/DateShiftScope.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations 6 | { 7 | [JsonConverter(typeof(StringEnumConverter))] 8 | public enum DateShiftScope 9 | { 10 | [EnumMember(Value = "resource")] 11 | Resource, 12 | [EnumMember(Value = "file")] 13 | File, 14 | [EnumMember(Value = "folder")] 15 | Folder 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerConfigurations/ParameterConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations 6 | { 7 | [DataContract] 8 | public class ParameterConfiguration 9 | { 10 | [DataMember(Name = "dateShiftKey")] 11 | public string DateShiftKey { get; set; } 12 | 13 | [DataMember(Name = "dateShiftScope")] 14 | public DateShiftScope DateShiftScope { get; set; } 15 | 16 | [DataMember(Name = "dateShiftFixedOffsetInDays")] 17 | public int? DateShiftFixedOffsetInDays { get; set; } 18 | 19 | [DataMember(Name = "cryptoHashKey")] 20 | public string CryptoHashKey { get; set; } 21 | 22 | [DataMember(Name = "encryptKey")] 23 | public string EncryptKey { get; set; } 24 | 25 | [DataMember(Name = "enablePartialAgesForRedact")] 26 | public bool EnablePartialAgesForRedact { get; set; } 27 | 28 | [DataMember(Name = "enablePartialDatesForRedact")] 29 | public bool EnablePartialDatesForRedact { get; set; } 30 | 31 | [DataMember(Name = "enablePartialZipCodesForRedact")] 32 | public bool EnablePartialZipCodesForRedact { get; set; } 33 | 34 | [DataMember(Name = "restrictedZipCodeTabulationAreas")] 35 | public List RestrictedZipCodeTabulationAreas { get; set; } 36 | 37 | [DataMember(Name = "customSettings")] 38 | public JObject CustomSettings { get; set; } 39 | 40 | public string DateShiftKeyPrefix { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerConfigurations/ProcessingErrorsOption.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations 2 | { 3 | public enum ProcessingErrorsOption 4 | { 5 | Raise, // Invalid processing will raise an exception. 6 | Skip, // Invalid processing will return empty element. 7 | // Ignore Invalid processing will return input. 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/AnonymizerLogging.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core 4 | { 5 | public static class AnonymizerLogging 6 | { 7 | public static ILoggerFactory LoggerFactory { get; set; } = new LoggerFactory(); 8 | public static ILogger CreateLogger() => LoggerFactory.CreateLogger(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Constants.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Health.Fhir.Anonymizer.Core.AnonymizerConfigurations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Microsoft.Health.Fhir.Anonymizer.Core 7 | { 8 | internal static partial class Constants 9 | { 10 | // InstanceType constants 11 | internal const string DateTypeName = "date"; 12 | internal const string DateTimeTypeName = "dateTime"; 13 | internal const string DecimalTypeName = "decimal"; 14 | internal const string InstantTypeName = "instant"; 15 | internal const string AgeTypeName = "Age"; 16 | internal const string BundleTypeName = "Bundle"; 17 | internal const string ReferenceTypeName = "Reference"; 18 | 19 | // NodeName constants 20 | internal const string PostalCodeNodeName = "postalCode"; 21 | internal const string ReferenceStringNodeName = "reference"; 22 | internal const string ContainedNodeName = "contained"; 23 | internal const string EntryNodeName = "entry"; 24 | internal const string EntryResourceNodeName = "resource"; 25 | internal const string ValueNodeName = "value"; 26 | 27 | // Rule constants 28 | internal const string PathKey = "path"; 29 | internal const string MethodKey = "method"; 30 | 31 | internal const int DefaultPartitionedExecutionCount = 4; 32 | internal const int DefaultPartitionedExecutionBatchSize = 1000; 33 | 34 | internal const string GeneralResourceType = "Resource"; 35 | internal const string GeneralDomainResourceType = "DomainResource"; 36 | 37 | internal static readonly HashSet BuiltInMethods = Enum.GetNames(typeof(AnonymizerMethod)).ToHashSet(StringComparer.InvariantCultureIgnoreCase); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Exceptions/AddCustomProcessorException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Exceptions 4 | { 5 | public class AddCustomProcessorException : Exception 6 | { 7 | public AddCustomProcessorException(string message) : base(message) 8 | { 9 | } 10 | 11 | public AddCustomProcessorException(string message, Exception innerException) 12 | : base(message, innerException) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Exceptions/AnonymizerConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Exceptions 4 | { 5 | public class AnonymizerConfigurationException : Exception 6 | { 7 | public AnonymizerConfigurationException(string message) : base(message) 8 | { 9 | } 10 | 11 | public AnonymizerConfigurationException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Exceptions/AnonymizerProcessingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Exceptions 4 | { 5 | // Processing exception. A runtime exception thrown during anonymization process. 6 | // Customers can set the parameter in configuration file to skip processing the resource if this exception is thrown. 7 | public class AnonymizerProcessingException : Exception 8 | { 9 | public AnonymizerProcessingException(string message) : base(message) 10 | { 11 | } 12 | 13 | public AnonymizerProcessingException(string message, Exception innerException) : base(message, innerException) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Exceptions/AnonymizerRuleNotApplicableException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Exceptions 4 | { 5 | public class AnonymizerRuleNotApplicableException : AnonymizerConfigurationException 6 | { 7 | public AnonymizerRuleNotApplicableException(string message) : base(message) 8 | { 9 | } 10 | 11 | public AnonymizerRuleNotApplicableException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Exceptions/InvalidInputException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Exceptions 4 | { 5 | public class InvalidInputException : Exception 6 | { 7 | public InvalidInputException(string message, Exception innerException) 8 | : base(message, innerException) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Extensions/ElementNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Hl7.Fhir.ElementModel; 3 | 4 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Extensions 5 | { 6 | public static class ElementNodeExtensions 7 | { 8 | public static bool IsAgeDecimalNode(this ElementNode node) 9 | { 10 | return node != null && 11 | node.Parent.IsAgeNode() && 12 | string.Equals(node.InstanceType, Constants.DecimalTypeName, StringComparison.InvariantCultureIgnoreCase); 13 | } 14 | 15 | public static bool IsReferenceStringNode(this ElementNode node) 16 | { 17 | return node != null && 18 | node.Parent.IsReferenceNode() && 19 | string.Equals(node.Name, Constants.ReferenceStringNodeName, StringComparison.InvariantCultureIgnoreCase); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Extensions/ElementNodeVisitorExtensions.cs: -------------------------------------------------------------------------------- 1 | using Hl7.Fhir.ElementModel; 2 | using Microsoft.Health.Fhir.Anonymizer.Core.Visitors; 3 | 4 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Extensions 5 | { 6 | public static class ElementNodeVisitorExtensions 7 | { 8 | public static void Accept(this ElementNode node, AbstractElementNodeVisitor visitor) 9 | { 10 | bool shouldVisitChild = visitor.Visit(node); 11 | 12 | if (shouldVisitChild) 13 | { 14 | foreach (var child in node.Children()) 15 | { 16 | ((ElementNode)child).Accept(visitor); 17 | } 18 | } 19 | 20 | visitor.EndVisit(node); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Extensions/FhirPathSymbolExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Hl7.Fhir.ElementModel; 4 | using Hl7.FhirPath.Expressions; 5 | 6 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Extensions 7 | { 8 | public static class FhirPathSymbolExtensions 9 | { 10 | private static object _lock = new object(); 11 | public static SymbolTable AddExtensionSymbols(this SymbolTable t) 12 | { 13 | // Add lock here to ensure thread safety when modifying a symbol table 14 | lock (_lock) 15 | { 16 | // Check whether extension method already exists 17 | if (t.Filter("nodesByType", 2).Count() == 0) 18 | { 19 | t.Add("nodesByType", (IEnumerable f, string typeName) => NodesByType(f, typeName), doNullProp: true); 20 | } 21 | 22 | if (t.Filter("nodesByName", 2).Count() == 0) 23 | { 24 | t.Add("nodesByName", (IEnumerable f, string name) => NodesByName(f, name), doNullProp: true); 25 | } 26 | } 27 | 28 | return t; 29 | } 30 | 31 | public static IEnumerable NodesByType(IEnumerable nodes, string typeName) 32 | { 33 | return nodes.SelfAndDescendantsWithoutSubResource().Where(n => typeName.Equals(n.InstanceType)); 34 | } 35 | 36 | public static IEnumerable NodesByName(IEnumerable nodes, string name) 37 | { 38 | return nodes.SelfAndDescendantsWithoutSubResource().Where(n => name.Equals(n.Name)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Microsoft.Health.Fhir.Anonymizer.Shared.Core.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8918ce75-f36e-4cb4-8232-12024a1e41ac 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Models/ProcessContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hl7.Fhir.ElementModel; 3 | 4 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Models 5 | { 6 | public class ProcessContext 7 | { 8 | public HashSet VisitedNodes { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Models/SecurityLabels.cs: -------------------------------------------------------------------------------- 1 | using Hl7.Fhir.Model; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Models 4 | { 5 | public static class SecurityLabels 6 | { 7 | public static readonly Coding REDACT = new Coding() 8 | { 9 | System = "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 10 | Code = "REDACTED", 11 | Display = "redacted" 12 | }; 13 | 14 | public static readonly Coding ABSTRED = new Coding() 15 | { 16 | System = "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 17 | Code = "ABSTRED", 18 | Display = "abstracted" 19 | }; 20 | 21 | public static readonly Coding CRYTOHASH = new Coding() 22 | { 23 | System = "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 24 | Code = "CRYTOHASH", 25 | Display = "cryptographic hash function" 26 | }; 27 | 28 | public static readonly Coding MASKED = new Coding() 29 | { 30 | System = "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 31 | Code = "MASKED", 32 | Display = "masked" 33 | }; 34 | 35 | public static readonly Coding PERTURBED = new Coding() 36 | { 37 | Code = "PERTURBED", 38 | Display = "exact value is replaced with another exact value" 39 | }; 40 | 41 | public static readonly Coding SUBSTITUTED = new Coding() 42 | { 43 | Code = "SUBSTITUTED", 44 | Display = "exact value is replaced with a predefined value" 45 | }; 46 | 47 | public static readonly Coding GENERALIZED = new Coding() 48 | { 49 | Code = "GENERALIZED", 50 | Display = "exact value is replaced with a general value" 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/PartitionedExecution/BatchAnonymizeProgressDetail.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.Core.PartitionedExecution 2 | { 3 | public class BatchAnonymizeProgressDetail 4 | { 5 | public int CurrentThreadId { get; set; } 6 | 7 | // The number of anonymization completed resources. 8 | public int ProcessCompleted { get; set; } 9 | 10 | // The number of skipped resources when skipping AnonymizerProcessingException enabled. 11 | public int ProcessSkipped { get; set; } 12 | 13 | // Todo : this property will be removed since exception will be thrown once consuming failed. 14 | public int ConsumeCompleted { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/PartitionedExecution/FhirEnumerableReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.Health.Fhir.Anonymizer.Core.PartitionedExecution 5 | { 6 | public class FhirEnumerableReader : IFhirDataReader 7 | { 8 | private IEnumerator _enumerator; 9 | 10 | public FhirEnumerableReader(IEnumerable data) 11 | { 12 | _enumerator = data.GetEnumerator(); 13 | } 14 | 15 | public Task NextAsync() 16 | { 17 | if (_enumerator.MoveNext()) 18 | { 19 | return Task.FromResult(_enumerator.Current); 20 | } 21 | else 22 | { 23 | return Task.FromResult(default(T)); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/PartitionedExecution/FhirStreamConsumer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Health.Fhir.Anonymizer.Core.PartitionedExecution 8 | { 9 | public class FhirStreamConsumer : IFhirDataConsumer, IDisposable 10 | { 11 | private StreamWriter _writer; 12 | 13 | public FhirStreamConsumer(Stream stream) 14 | { 15 | _writer = new StreamWriter(stream); 16 | } 17 | 18 | public async Task CompleteAsync() 19 | { 20 | await _writer.FlushAsync().ConfigureAwait(false); 21 | } 22 | 23 | public async Task ConsumeAsync(IEnumerable data) 24 | { 25 | int result = 0; 26 | foreach (string content in data) 27 | { 28 | await _writer.WriteLineAsync(content).ConfigureAwait(false); 29 | result++; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | #region IDisposable Support 36 | private bool disposedValue = false; 37 | 38 | protected virtual void Dispose(bool disposing) 39 | { 40 | if (!disposedValue) 41 | { 42 | if (disposing) 43 | { 44 | _writer?.Dispose(); 45 | } 46 | 47 | disposedValue = true; 48 | } 49 | } 50 | 51 | public void Dispose() 52 | { 53 | Dispose(true); 54 | GC.SuppressFinalize(this); 55 | } 56 | #endregion 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/PartitionedExecution/FhirStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core 6 | { 7 | public class FhirStreamReader : IFhirDataReader, IDisposable 8 | { 9 | private StreamReader _reader; 10 | 11 | public FhirStreamReader(Stream stream) 12 | { 13 | _reader = new StreamReader(stream); 14 | } 15 | 16 | public async Task NextAsync() 17 | { 18 | return await _reader.ReadLineAsync().ConfigureAwait(false); 19 | } 20 | 21 | #region IDisposable Support 22 | private bool disposedValue = false; 23 | 24 | protected virtual void Dispose(bool disposing) 25 | { 26 | if (!disposedValue) 27 | { 28 | if (disposing) 29 | { 30 | _reader?.Dispose(); 31 | } 32 | 33 | disposedValue = true; 34 | } 35 | } 36 | 37 | public void Dispose() 38 | { 39 | Dispose(true); 40 | GC.SuppressFinalize(this); 41 | } 42 | #endregion 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/PartitionedExecution/IFhirDataConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.Health.Fhir.Anonymizer.Core 5 | { 6 | public interface IFhirDataConsumer 7 | { 8 | Task ConsumeAsync(IEnumerable data); 9 | 10 | Task CompleteAsync(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/PartitionedExecution/IFhirDataReader.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core 4 | { 5 | public interface IFhirDataReader 6 | { 7 | Task NextAsync(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/AnonymizationOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors 6 | { 7 | public static class AnonymizationOperations 8 | { 9 | public const string Redact = "REDACT"; 10 | public const string Abstract = "ABSTRACT"; 11 | public const string Perturb = "PERTURB"; 12 | public const string CryptoHash = "CRYPTOHASH"; 13 | public const string Encrypt = "ENCRYPT"; 14 | public const string Substitute = "SUBSTITUTE"; 15 | public const string Generalize = "GENERALIZE"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/EncryptProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using Hl7.Fhir.ElementModel; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Health.Fhir.Anonymizer.Core.Models; 6 | using Microsoft.Health.Fhir.Anonymizer.Core.Utility; 7 | 8 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors 9 | { 10 | public class EncryptProcessor: IAnonymizerProcessor 11 | { 12 | private readonly byte[] _key; 13 | private readonly ILogger _logger = AnonymizerLogging.CreateLogger(); 14 | 15 | public EncryptProcessor(string encryptKey) 16 | { 17 | _key = Encoding.UTF8.GetBytes(encryptKey); 18 | } 19 | 20 | public ProcessResult Process(ElementNode node, ProcessContext context = null, Dictionary settings = null) 21 | { 22 | var processResult = new ProcessResult(); 23 | if (string.IsNullOrEmpty(node?.Value?.ToString())) 24 | { 25 | return processResult; 26 | } 27 | 28 | var input = node.Value.ToString(); 29 | node.Value = EncryptUtility.EncryptTextToBase64WithAes(input, _key); 30 | _logger.LogDebug($"Fhir value '{input}' at '{node.Location}' is encrypted to '{node.Value}'."); 31 | 32 | processResult.AddProcessRecord(AnonymizationOperations.Encrypt, node); 33 | return processResult; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/Factory/IAnonymizerProcessorFactory.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors 11 | { 12 | public interface IAnonymizerProcessorFactory 13 | { 14 | public IAnonymizerProcessor CreateProcessor(string anonymizeMethod, JObject ruleSetting = null); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/IAnonymizerProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hl7.Fhir.ElementModel; 3 | using Microsoft.Health.Fhir.Anonymizer.Core.Models; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors 6 | { 7 | public interface IAnonymizerProcessor 8 | { 9 | public ProcessResult Process(ElementNode node, ProcessContext context = null, Dictionary settings = null); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/KeepProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hl7.Fhir.ElementModel; 3 | using Microsoft.Health.Fhir.Anonymizer.Core.Models; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors 6 | { 7 | public class KeepProcessor: IAnonymizerProcessor 8 | { 9 | public ProcessResult Process(ElementNode node, ProcessContext context = null, Dictionary settings = null) 10 | { 11 | return new ProcessResult(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/Settings/GeneralizeOtherValuesOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors.Settings 4 | { 5 | public enum GeneralizationOtherValuesOperation 6 | { 7 | Redact, 8 | Keep 9 | }; 10 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/Settings/PerturbRangeType.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors.Settings 2 | { 3 | public enum PerturbRangeType 4 | { 5 | Fixed, 6 | Proportional 7 | } 8 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/Settings/RuleKeys.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors.Settings 2 | { 3 | internal static class RuleKeys 4 | { 5 | //perturb 6 | internal const string ReplaceWith = "replaceWith"; 7 | internal const string RangeType = "rangeType"; 8 | internal const string RoundTo = "roundTo"; 9 | internal const string Span = "span"; 10 | 11 | //generalize 12 | internal const string Cases = "cases"; 13 | internal const string OtherValues = "otherValues"; 14 | } 15 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Processors/Settings/SubstituteSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EnsureThat; 3 | using Microsoft.Health.Fhir.Anonymizer.Core.Exceptions; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors.Settings 6 | { 7 | public class SubstituteSetting 8 | { 9 | public string ReplaceWith { get; set; } 10 | 11 | public static SubstituteSetting CreateFromRuleSettings(Dictionary ruleSettings) 12 | { 13 | EnsureArg.IsNotNull(ruleSettings); 14 | 15 | string replaceWith = ruleSettings.GetValueOrDefault(RuleKeys.ReplaceWith)?.ToString(); 16 | return new SubstituteSetting 17 | { 18 | ReplaceWith = replaceWith 19 | }; 20 | } 21 | 22 | public static void ValidateRuleSettings(Dictionary ruleSettings) 23 | { 24 | if (ruleSettings == null) 25 | { 26 | throw new AnonymizerConfigurationException("Substitute rule should not be null."); 27 | } 28 | 29 | if (!ruleSettings.ContainsKey(Constants.PathKey)) 30 | { 31 | throw new AnonymizerConfigurationException("Missing path in FHIR path rule config."); 32 | } 33 | 34 | if (!ruleSettings.ContainsKey(RuleKeys.ReplaceWith)) 35 | { 36 | throw new AnonymizerConfigurationException($"Missing replaceWith value in substitution rule at {ruleSettings[Constants.PathKey]}."); 37 | } 38 | 39 | return; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Utility/CryptoHashUtility.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Utility 6 | { 7 | public class CryptoHashUtility 8 | { 9 | public static string ComputeHmacSHA256Hash(string input, string hashKey) 10 | { 11 | if (string.IsNullOrEmpty(input)) 12 | { 13 | return input; 14 | } 15 | 16 | var key = Encoding.UTF8.GetBytes(hashKey); 17 | using var hmac = new HMACSHA256(key); 18 | var plainData = Encoding.UTF8.GetBytes(input); 19 | var hashData = hmac.ComputeHash(plainData); 20 | 21 | return string.Concat(hashData.Select(b => b.ToString("x2"))); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Validation/AttributeValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Hl7.Fhir.Model; 4 | using Hl7.Fhir.Validation; 5 | 6 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Validation 7 | { 8 | public class AttributeValidator 9 | { 10 | public IEnumerable Validate(Resource resource) 11 | { 12 | var result = new List(); 13 | DotNetAttributeValidation.TryValidate(resource, result, true); 14 | return result; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Validation/ResourceNotValidException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Validation 4 | { 5 | public class ResourceNotValidException : Exception 6 | { 7 | public ResourceNotValidException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.Core/Visitors/AbstractElementNodeVisitor.cs: -------------------------------------------------------------------------------- 1 | using Hl7.Fhir.ElementModel; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Visitors 4 | { 5 | public abstract class AbstractElementNodeVisitor 6 | { 7 | public virtual bool Visit(ElementNode node) 8 | { 9 | return true; 10 | } 11 | 12 | public virtual void EndVisit(ElementNode node) 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/configuration-raise-processing-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "processingErrors": "raise", 4 | "fhirPathRules": [ 5 | { 6 | "path": "nodesByType('HumanName').family", 7 | "method": "generalize", 8 | "cases": { 9 | "$this>=0 and $this<20": "20" 10 | } 11 | }, 12 | { 13 | "path": "TestResource", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "nodesByType('HumanName')", 18 | "method": "redact" 19 | }, 20 | { 21 | "path": "Resource", 22 | "method": "keep" 23 | }, 24 | { 25 | "path": "Device.where(id.exists()).id", 26 | "method": "keep" 27 | } 28 | ], 29 | "parameters": { 30 | "dateShiftKey": "", 31 | "cryptoHashKey": "", 32 | "enablePartialAgesForRedact": true, 33 | "enablePartialDatesForRedact": true, 34 | "enablePartialZipCodesForRedact": true, 35 | "restrictedZipCodeTabulationAreas": [ 36 | "036", 37 | "059", 38 | "102", 39 | "203", 40 | "205", 41 | "369", 42 | "556", 43 | "692", 44 | "821", 45 | "823", 46 | "878", 47 | "879", 48 | "884", 49 | "893" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/configuration-skip-processing-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "processingErrors": "skip", 4 | "fhirPathRules": [ 5 | { 6 | "path": "nodesByType('HumanName').family", 7 | "method": "generalize", 8 | "cases": { 9 | "$this>=0 and $this<20": "20" 10 | } 11 | }, 12 | { 13 | "path": "TestResource", 14 | "method": "redact" 15 | }, 16 | { 17 | "path": "nodesByType('HumanName')", 18 | "method": "redact" 19 | }, 20 | { 21 | "path": "Resource", 22 | "method": "keep" 23 | }, 24 | { 25 | "path": "Device.where(id.exists()).id", 26 | "method": "keep" 27 | } 28 | ], 29 | "parameters": { 30 | "dateShiftKey": "", 31 | "cryptoHashKey": "", 32 | "enablePartialAgesForRedact": true, 33 | "enablePartialDatesForRedact": true, 34 | "enablePartialZipCodesForRedact": true, 35 | "restrictedZipCodeTabulationAreas": [ 36 | "036", 37 | "059", 38 | "102", 39 | "203", 40 | "205", 41 | "369", 42 | "556", 43 | "692", 44 | "821", 45 | "823", 46 | "878", 47 | "879", 48 | "884", 49 | "893" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/configuration-without-processing-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "fhirPathRules": [ 4 | { 5 | "path": "nodesByType('HumanName').family", 6 | "method": "generalize", 7 | "cases": { 8 | "$this>=0 and $this<20": "20" 9 | } 10 | }, 11 | { 12 | "path": "TestResource", 13 | "method": "redact" 14 | }, 15 | { 16 | "path": "nodesByType('HumanName')", 17 | "method": "redact" 18 | }, 19 | { 20 | "path": "Resource", 21 | "method": "keep" 22 | }, 23 | { 24 | "path": "Device.where(id.exists()).id", 25 | "method": "keep" 26 | } 27 | ], 28 | "parameters": { 29 | "dateShiftKey": "", 30 | "cryptoHashKey": "", 31 | "enablePartialAgesForRedact": true, 32 | "enablePartialDatesForRedact": true, 33 | "enablePartialZipCodesForRedact": true, 34 | "restrictedZipCodeTabulationAreas": [ 35 | "036", 36 | "059", 37 | "102", 38 | "203", 39 | "205", 40 | "369", 41 | "556", 42 | "692", 43 | "821", 44 | "823", 45 | "878", 46 | "879", 47 | "884", 48 | "893" 49 | ] 50 | } 51 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/generalize-patient-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "nodesByType('Extension')", 5 | "method": "redact" 6 | }, 7 | { 8 | "path": "Patient.communication.language.coding.code", 9 | "method": "generalize", 10 | "cases": { 11 | "$this in ('en-AU' | 'en-CA' | 'en-GB' | 'en-IN' | 'en-NZ' | 'en-SG' | 'en-US')": "'en'", 12 | "('es-AR' | 'es-ES' | 'es-UY') contains $this": "'es'" 13 | }, 14 | "otherValues": "keep" 15 | }, 16 | { 17 | "path": "nodesByType('date')", 18 | "method": "generalize", 19 | "cases": { 20 | "$this <= @2020-01-01 and $this >= @1990-01-01": "@2000-01-01" 21 | }, 22 | "otherValues": "redact" 23 | }, 24 | { 25 | "path": "nodesByType('dateTime')", 26 | "method": "generalize", 27 | "cases": { 28 | "$this <= @2020-01-01T00:00:00Z and $this >= @1990-01-01T00:00:00Z": "@2000-01-01T00:00:00Z" 29 | } 30 | }, 31 | { 32 | "path": "nodesByType('integer')", 33 | "method": "generalize", 34 | "cases": { 35 | "true": "1" 36 | }, 37 | "otherValues": "keep" 38 | }, 39 | { 40 | "path": "Patient.nodesByType('Address').postalCode", 41 | "method": "generalize", 42 | "cases": { 43 | "$this.startsWith('123')": "$this.substring(0,3)+'****'" 44 | }, 45 | "otherValues": "redact" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/redact-all-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirVersion": "", 3 | "fhirPathRules": [ 4 | { 5 | "path": "Resource", 6 | "method": "redact" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/substitute-complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Patient.name", 5 | "method": "substitute", 6 | "replaceWith": { 7 | "family": "example", 8 | "given": [ 9 | "test", 10 | "testing" 11 | ] 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/substitute-conflict-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Patient.name.period.start", 5 | "method": "redact" 6 | }, 7 | { 8 | "path": "Patient.name", 9 | "method": "substitute", 10 | "replaceWith": { 11 | "family": "example", 12 | "given": [ 13 | "test", 14 | "testing" 15 | ] 16 | } 17 | } 18 | ], 19 | "parameters": { 20 | "enablePartialDatesForRedact": true 21 | } 22 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/substitute-multiple-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Patient.name", 5 | "method": "substitute", 6 | "replaceWith": { 7 | "family": "example", 8 | "given": [ 9 | "test", 10 | "testing" 11 | ] 12 | } 13 | }, 14 | { 15 | "path": "Patient.name.family", 16 | "method": "substitute", 17 | "replaceWith": "familttest" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/substitute-multiple.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Patient.name.period", 5 | "method": "substitute", 6 | "replaceWith": { 7 | "start": "2020-01-01", 8 | "end": "2021-01-01" 9 | } 10 | }, 11 | { 12 | "path": "Patient.name", 13 | "method": "substitute", 14 | "replaceWith": { 15 | "family": "example", 16 | "given": [ 17 | "test", 18 | "testing" 19 | ] 20 | } 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Configurations/substitute-primitive.json: -------------------------------------------------------------------------------- 1 | { 2 | "fhirPathRules": [ 3 | { 4 | "path": "Patient.name.family", 5 | "method": "substitute", 6 | "replaceWith": "example" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/FunctionalTestUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Hl7.Fhir.Model; 4 | using Hl7.Fhir.Serialization; 5 | using Microsoft.Health.Fhir.Anonymizer.Core; 6 | using Xunit; 7 | 8 | namespace Microsoft.Health.Fhir.Anonymizer.FunctionalTests 9 | { 10 | public static class FunctionalTestUtility 11 | { 12 | public static void VerifySingleJsonResourceFromFile(AnonymizerEngine engine, string testFile, string targetFile) 13 | { 14 | Console.WriteLine($"VerifySingleJsonResourceFromFile. TestFile: {testFile}, TargetFile: {targetFile}"); 15 | string testContent = File.ReadAllText(testFile); 16 | string targetContent = File.ReadAllText(targetFile); 17 | string resultAfterAnonymize = engine.AnonymizeJson(testContent); 18 | Assert.Equal(Standardize(targetContent), Standardize(resultAfterAnonymize)); 19 | } 20 | 21 | private static string Standardize(string jsonContent) 22 | { 23 | var resource = new FhirJsonParser().Parse(jsonContent); 24 | FhirJsonSerializationSettings serializationSettings = new FhirJsonSerializationSettings 25 | { 26 | Pretty = true 27 | }; 28 | return resource.ToJson(serializationSettings); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19e3e7a2-17a6-4708-b9f2-04a2c9968c83 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/R4OnlyResource/Account-R4-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Account", 3 | "id": "698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | }, 11 | { 12 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 13 | "code": "CRYTOHASH", 14 | "display": "cryptographic hash function" 15 | }, 16 | { 17 | "code": "PERTURBED", 18 | "display": "exact value is replaced with another exact value" 19 | } 20 | ] 21 | }, 22 | "identifier": [ 23 | { 24 | "system": "urn:oid:0.1.2.3.4.5.6.7" 25 | } 26 | ], 27 | "status": "active", 28 | "type": { 29 | "coding": [ 30 | { 31 | "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode" 32 | } 33 | ] 34 | }, 35 | "subject": [ 36 | { 37 | "reference": "Patient/698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba" 38 | } 39 | ], 40 | "servicePeriod": { 41 | "start": "2015-12-12", 42 | "end": "2016-06-10" 43 | }, 44 | "coverage": [ 45 | { 46 | "coverage": { 47 | "reference": "Coverage/e283f9c81456ca81a304457d977616b5ce48a214267a0b73010e38fb90081b5c" 48 | }, 49 | "priority": 1 50 | } 51 | ], 52 | "owner": { 53 | "reference": "Organization/0c3eb5d8a74155bcdddabf5dfa60b71304c5e192198f5d3b90fc4e046003b8fb" 54 | } 55 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/R4OnlyResource/Account-R4.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Account", 3 | "id": "example", 4 | "text": { 5 | "status": "generated", 6 | "div": "
HACC Funded Billing for Peter James Chalmers
" 7 | }, 8 | "identifier": [ 9 | { 10 | "system": "urn:oid:0.1.2.3.4.5.6.7", 11 | "value": "654321" 12 | } 13 | ], 14 | "status": "active", 15 | "type": { 16 | "coding": [ 17 | { 18 | "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", 19 | "code": "PBILLACCT", 20 | "display": "patient billing account" 21 | } 22 | ], 23 | "text": "patient" 24 | }, 25 | "name": "HACC Funded Billing for Peter James Chalmers", 26 | "subject": [ 27 | { 28 | "reference": "Patient/example", 29 | "display": "Peter James Chalmers" 30 | } 31 | ], 32 | "servicePeriod": { 33 | "start": "2016-01-01", 34 | "end": "2016-06-30" 35 | }, 36 | "coverage": [ 37 | { 38 | "coverage": { 39 | "reference": "Coverage/7546D" 40 | }, 41 | "priority": 1 42 | } 43 | ], 44 | "owner": { 45 | "reference": "Organization/hl7" 46 | }, 47 | "description": "Hospital charges" 48 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/Stu3OnlyResource/Account-Stu3-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Account", 3 | "id": "698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | }, 11 | { 12 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 13 | "code": "CRYTOHASH", 14 | "display": "cryptographic hash function" 15 | }, 16 | { 17 | "code": "PERTURBED", 18 | "display": "exact value is replaced with another exact value" 19 | } 20 | ] 21 | }, 22 | "identifier": [ 23 | { 24 | "system": "urn:oid:0.1.2.3.4.5.6.7" 25 | } 26 | ], 27 | "status": "active", 28 | "type": { 29 | "coding": [ 30 | { 31 | "system": "http://hl7.org/fhir/v3/ActCode" 32 | } 33 | ] 34 | }, 35 | "subject": { 36 | "reference": "Patient/698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba" 37 | }, 38 | "period": { 39 | "start": "2015-12-12", 40 | "end": "2016-06-10" 41 | }, 42 | "active": { 43 | "start": "2015-12-12", 44 | "end": "2016-06-10" 45 | }, 46 | "balance": { 47 | "value": -1200, 48 | "unit": "USD", 49 | "system": "urn:iso:std:iso:4217", 50 | "code": "USD" 51 | }, 52 | "coverage": [ 53 | { 54 | "coverage": { 55 | "reference": "Coverage/e283f9c81456ca81a304457d977616b5ce48a214267a0b73010e38fb90081b5c" 56 | }, 57 | "priority": 1 58 | } 59 | ], 60 | "owner": { 61 | "reference": "Organization/0c3eb5d8a74155bcdddabf5dfa60b71304c5e192198f5d3b90fc4e046003b8fb" 62 | } 63 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/Stu3OnlyResource/Account-Stu3.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Account", 3 | "id": "example", 4 | "text": { 5 | "status": "generated", 6 | "div": "
HACC Funded Billing for Peter James Chalmers
" 7 | }, 8 | "identifier": [ 9 | { 10 | "system": "urn:oid:0.1.2.3.4.5.6.7", 11 | "value": "654321" 12 | } 13 | ], 14 | "status": "active", 15 | "type": { 16 | "coding": [ 17 | { 18 | "system": "http://hl7.org/fhir/v3/ActCode", 19 | "code": "PBILLACCT", 20 | "display": "patient billing account" 21 | } 22 | ], 23 | "text": "patient" 24 | }, 25 | "name": "HACC Funded Billing for Peter James Chalmers", 26 | "subject": { 27 | "reference": "Patient/example", 28 | "display": "Peter James Chalmers" 29 | }, 30 | "period": { 31 | "start": "2016-01-01", 32 | "end": "2016-06-30" 33 | }, 34 | "active": { 35 | "start": "2016-01-01", 36 | "end": "2016-06-30" 37 | }, 38 | "balance": { 39 | "value": -1200, 40 | "unit": "USD", 41 | "system": "urn:iso:std:iso:4217", 42 | "code": "USD" 43 | }, 44 | "coverage": [ 45 | { 46 | "coverage": { 47 | "reference": "Coverage/7546D" 48 | }, 49 | "priority": 1 50 | } 51 | ], 52 | "owner": { 53 | "reference": "Organization/hl7" 54 | }, 55 | "description": "Hospital charges" 56 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/Stu3OnlyResource/DeviceComponent-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "DeviceComponent", 3 | "id": "698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | }, 11 | { 12 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 13 | "code": "CRYTOHASH", 14 | "display": "cryptographic hash function" 15 | }, 16 | { 17 | "code": "PERTURBED", 18 | "display": "exact value is replaced with another exact value" 19 | } 20 | ] 21 | }, 22 | "type": { 23 | "coding": [ 24 | { 25 | "system": "urn:iso:std:iso:11073:10101" 26 | } 27 | ] 28 | }, 29 | "lastSystemChange": "2014-09-17T00:00:00+00:00", 30 | "source": { 31 | "reference": "Device/23b01b6be3759d4b617ad00809a30e57e35a911d56f36083fa0f01a097a87a26" 32 | }, 33 | "parent": { 34 | "reference": "DeviceComponent/4c0f2bfe36833c9e88f276db8122710cfb0f811ee62a9dfd42131c20d50035d0" 35 | }, 36 | "operationalStatus": [ 37 | { 38 | "coding": [ 39 | { 40 | "system": "urn:iso:std:iso:11073:10101" 41 | } 42 | ] 43 | } 44 | ], 45 | "parameterGroup": { 46 | "coding": [ 47 | { 48 | "system": "urn:iso:std:iso:11073:10101" 49 | } 50 | ] 51 | }, 52 | "measurementPrinciple": "optical", 53 | "languageCode": { 54 | "coding": [ 55 | { 56 | "system": "http://tools.ietf.org/html/bcp47" 57 | } 58 | ] 59 | } 60 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/Stu3OnlyResource/ProcessRequest-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "ProcessRequest", 3 | "id": "b5e38ee79ad5c587fe56a273b27cbe52c923bff2399222d383d3961fcd2fa9f6", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | }, 11 | { 12 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 13 | "code": "CRYTOHASH", 14 | "display": "cryptographic hash function" 15 | }, 16 | { 17 | "code": "PERTURBED", 18 | "display": "exact value is replaced with another exact value" 19 | } 20 | ] 21 | }, 22 | "identifier": [ 23 | { 24 | "system": "http://happyvalley.com/processrequest" 25 | } 26 | ], 27 | "status": "active", 28 | "action": "poll", 29 | "created": "2014-08-08", 30 | "organization": { 31 | "reference": "Organization/ccc8c71905806db18c0241b58a8e36e949ab8a45961041b4226b2a4284568e69" 32 | } 33 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/Stu3OnlyResource/ProcessRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "ProcessRequest", 3 | "id": "1110", 4 | "text": { 5 | "status": "generated", 6 | "div": "
A human-readable rendering of the Poll ProcessRequest
" 7 | }, 8 | "identifier": [ 9 | { 10 | "system": "http://happyvalley.com/processrequest", 11 | "value": "110" 12 | } 13 | ], 14 | "status": "active", 15 | "action": "poll", 16 | "created": "2014-08-16", 17 | "organization": { 18 | "reference": "Organization/1" 19 | } 20 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/Stu3OnlyResource/ProcessResponse-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "ProcessResponse", 3 | "id": "e6d4ae054f078e1674237de9e14c5700cb31cca0a9e2d059b4e6b5b7f0d852dc", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | }, 11 | { 12 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 13 | "code": "CRYTOHASH", 14 | "display": "cryptographic hash function" 15 | }, 16 | { 17 | "code": "PERTURBED", 18 | "display": "exact value is replaced with another exact value" 19 | } 20 | ] 21 | }, 22 | "identifier": [ 23 | { 24 | "system": "http://www.BenefitsInc.com/fhir/processresponse" 25 | } 26 | ], 27 | "status": "active", 28 | "created": "2014-09-23", 29 | "organization": { 30 | "reference": "Organization/8b808a62f8893cf1ce0f44fb62b433e3c21a4f5605687689d9f0bb1a3524439b" 31 | }, 32 | "request": { 33 | "reference": "60a9e4bc1586eea5e4f281c2924cd5e2d080ca774ba45d39b6a867e12c7eb6f5" 34 | }, 35 | "outcome": { 36 | "coding": [ 37 | { 38 | "system": "http://hl7.org/fhir/processoutcomecodes" 39 | } 40 | ] 41 | }, 42 | "requestOrganization": { 43 | "reference": "Organization/ccc8c71905806db18c0241b58a8e36e949ab8a45961041b4226b2a4284568e69" 44 | } 45 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/Stu3OnlyResource/ProcessResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "ProcessResponse", 3 | "id": "SR2500", 4 | "text": { 5 | "status": "generated", 6 | "div": "
A human-readable rendering of the ProcessResponse
" 7 | }, 8 | "identifier": [ 9 | { 10 | "system": "http://www.BenefitsInc.com/fhir/processresponse", 11 | "value": "881234" 12 | } 13 | ], 14 | "status": "active", 15 | "created": "2014-08-16", 16 | "organization": { 17 | "reference": "Organization/2" 18 | }, 19 | "request": { 20 | "reference": "http://happyvalley.com/fhir/claim/12345" 21 | }, 22 | "outcome": { 23 | "coding": [ 24 | { 25 | "system": "http://hl7.org/fhir/processoutcomecodes", 26 | "code": "complete" 27 | } 28 | ] 29 | }, 30 | "disposition": "Adjudication processing completed, ClaimResponse and EOB ready for retrieval.", 31 | "requestOrganization": { 32 | "reference": "Organization/1" 33 | } 34 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/bundle-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/bundle-redact-all-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | }, 12 | "entry": [ 13 | { 14 | "resource": { 15 | "resourceType": "Patient", 16 | "meta": { 17 | "security": [ 18 | { 19 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 20 | "code": "REDACTED", 21 | "display": "redacted" 22 | } 23 | ] 24 | } 25 | } 26 | }, 27 | { 28 | "resource": { 29 | "resourceType": "Patient", 30 | "meta": { 31 | "security": [ 32 | { 33 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 34 | "code": "REDACTED", 35 | "display": "redacted" 36 | } 37 | ] 38 | } 39 | } 40 | }, 41 | { 42 | "resource": { 43 | "resourceType": "Observation", 44 | "meta": { 45 | "security": [ 46 | { 47 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 48 | "code": "REDACTED", 49 | "display": "redacted" 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/bundle-without-entry-resource-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "id": "bundle-transaction", 4 | "meta": { 5 | "lastUpdated": "2014-08-25T00:00:00+00:00", 6 | "security": [ 7 | { 8 | "code": "PERTURBED", 9 | "display": "exact value is replaced with another exact value" 10 | } 11 | ], 12 | "tag": [ 13 | { 14 | "system": "http://terminology.hl7.org/CodeSystem/v3-ActReason", 15 | "code": "HTEST", 16 | "display": "test health data" 17 | } 18 | ] 19 | }, 20 | "type": "transaction", 21 | "entry": [ 22 | { 23 | "request": { 24 | "method": "GET", 25 | "url": "Patient/12334", 26 | "ifNoneMatch": "W/\"4\"", 27 | "ifModifiedSince": "2015-09-07T00:00:00+10:00" 28 | } 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/bundle-without-entry-resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "id": "bundle-transaction", 4 | "meta": { 5 | "lastUpdated": "2014-08-18T01:43:30Z", 6 | "tag": [ 7 | { 8 | "system": "http://terminology.hl7.org/CodeSystem/v3-ActReason", 9 | "code": "HTEST", 10 | "display": "test health data" 11 | } 12 | ] 13 | }, 14 | "type": "transaction", 15 | "entry": [ 16 | { 17 | "request": { 18 | "method": "GET", 19 | "url": "Patient/12334", 20 | "ifNoneMatch": "W/\"4\"", 21 | "ifModifiedSince": "2015-08-31T08:14:33+10:00" 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/condition-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-basic-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | }, 12 | "contained": [ 13 | { 14 | "resourceType": "Practitioner", 15 | "id": "p1" 16 | } 17 | ], 18 | "asserter": { 19 | "reference": "#p1" 20 | } 21 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "contained": [ 4 | { 5 | "resourceType": "Practitioner", 6 | "id": "p1", 7 | "name": [ 8 | { 9 | "family": "Person", 10 | "given": [ "Patricia" ] 11 | } 12 | ] 13 | } 14 | ], 15 | "asserter": { 16 | "reference": "#p1" 17 | } 18 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-in-bundle-redact-all-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | }, 12 | "entry": [ 13 | { 14 | "resource": { 15 | "resourceType": "Condition", 16 | "meta": { 17 | "security": [ 18 | { 19 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 20 | "code": "REDACTED", 21 | "display": "redacted" 22 | } 23 | ] 24 | }, 25 | "contained": [ 26 | { 27 | "resourceType": "Practitioner" 28 | } 29 | ] 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-in-bundle-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "id": "bundle-references", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | } 11 | ] 12 | }, 13 | "type": "collection", 14 | "entry": [ 15 | { 16 | "resource": { 17 | "resourceType": "Condition", 18 | "meta": { 19 | "security": [ 20 | { 21 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 22 | "code": "REDACTED", 23 | "display": "redacted" 24 | } 25 | ] 26 | }, 27 | "contained": [ 28 | { 29 | "resourceType": "Practitioner", 30 | "id": "p1" 31 | } 32 | ], 33 | "asserter": { 34 | "reference": "#p1" 35 | } 36 | } 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-in-bundle.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "id": "bundle-references", 4 | "type": "collection", 5 | "entry": [ 6 | { 7 | "fullUrl": "http://example.org/fhir/Condition/123", 8 | "resource": { 9 | "resourceType": "Condition", 10 | "contained": [ 11 | { 12 | "resourceType": "Practitioner", 13 | "id": "p1", 14 | "name": [ 15 | { 16 | "family": "Person", 17 | "given": [ "Patricia" ] 18 | } 19 | ] 20 | } 21 | ], 22 | "asserter": { 23 | "reference": "#p1" 24 | } 25 | } 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-redact-all-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | }, 12 | "contained": [ { "resourceType": "Practitioner" } ] 13 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-substitute-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "meta": { 4 | "security": [ 5 | { 6 | "code": "SUBSTITUTED", 7 | "display": "exact value is replaced with a predefined value" 8 | } 9 | ] 10 | }, 11 | "contained": [ 12 | { 13 | "resourceType": "Patient", 14 | "id": "p1", 15 | "name": [ 16 | { 17 | "family": "example", 18 | "given": [ 19 | "test", 20 | "testing" 21 | ] 22 | } 23 | ] 24 | }, 25 | { 26 | "resourceType": "Patient", 27 | "id": "p2", 28 | "name": [ 29 | { 30 | "family": "example", 31 | "given": [ 32 | "test", 33 | "testing" 34 | ], 35 | "period": { 36 | "start": "2020-01-01", 37 | "end": "2021-01-01" 38 | } 39 | } 40 | ] 41 | } 42 | ], 43 | "asserter": { 44 | "reference": "#p1" 45 | } 46 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/contained-substitute.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Condition", 3 | "contained": [ 4 | { 5 | "resourceType": "Patient", 6 | "id": "p1", 7 | "name": [ 8 | { 9 | "family": "Family1", 10 | "given": [ 11 | "Patricia" 12 | ] 13 | } 14 | ] 15 | }, 16 | { 17 | "resourceType": "Patient", 18 | "id": "p2", 19 | "name": [ 20 | { 21 | "family": "Family2", 22 | "given": [ 23 | "Patricia", 24 | "Jim" 25 | ], 26 | "period": { 27 | "start": "1999-01-01" 28 | } 29 | } 30 | ] 31 | } 32 | ], 33 | "asserter": { 34 | "reference": "#p1" 35 | } 36 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/patient-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/patient-no-partial-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "id": "698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | }, 11 | { 12 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 13 | "code": "CRYTOHASH", 14 | "display": "cryptographic hash function" 15 | } 16 | ] 17 | }, 18 | "active": true, 19 | "name": [ 20 | { "use": "official" }, 21 | { "use": "usual" }, 22 | { 23 | "use": "maiden" 24 | } 25 | ], 26 | "gender": "male", 27 | "deceasedBoolean": false, 28 | "address": [ 29 | { 30 | "state": "TestState" 31 | }, 32 | { 33 | "use": "home", 34 | "type": "both", 35 | "text": "123 Test St Test1, Test2, Test3 1234", 36 | "line": [ 37 | "123 Test St" 38 | ], 39 | "city": "KeepTest", 40 | "district": "TestRegion", 41 | "state": "TestState", 42 | "postalCode": "12345", 43 | "period": { 44 | "start": "2000-01-01" 45 | } 46 | } 47 | ], 48 | "contact": [ 49 | { 50 | "relationship": [ 51 | { 52 | "coding": [ 53 | { 54 | "system": "http://terminology.hl7.org/CodeSystem/v2-0131", 55 | "code": "N" 56 | } 57 | ] 58 | } 59 | ], 60 | "address": { 61 | "state": "TestState" 62 | }, 63 | "gender": "female" 64 | } 65 | ], 66 | "managingOrganization": { 67 | "reference": "Organization/1" 68 | } 69 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/patient-redact-all-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "meta": { 4 | "security": [ 5 | { 6 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 7 | "code": "REDACTED", 8 | "display": "redacted" 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Shared.FunctionalTests/TestResources/patient-special-content-target.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "id": "698d54f0494528a759f19c8e87a9f99e75a5881b9267ee3926bcf62c992d84ba", 4 | "meta": { 5 | "security": [ 6 | { 7 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 8 | "code": "REDACTED", 9 | "display": "redacted" 10 | }, 11 | { 12 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", 13 | "code": "CRYTOHASH", 14 | "display": "cryptographic hash function" 15 | }, 16 | { 17 | "code": "PERTURBED", 18 | "display": "exact value is replaced with another exact value" 19 | } 20 | ] 21 | }, 22 | "identifier": [ { "period": { "start": "2001-02-11" } } ], 23 | "active": true, 24 | "name": [ { "use": "official" } ], 25 | "gender": "male", 26 | "birthDate": "1940-02-11", 27 | "_birthDate": { 28 | "extension": [ 29 | { 30 | "url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime", 31 | "valueDateTime": "1940-02-11T00:00:00-01:00" 32 | } 33 | ] 34 | }, 35 | "address": [ 36 | { 37 | "use": "home", 38 | "text": "Abc ŴΉЙ一 Abc ŴΉЙ一 Abc ŴΉЙ一 Abc ŴΉЙ一", 39 | "line": [ "Abc ŴΉЙ一 Abc ŴΉЙ一 Abc ŴΉЙ一 Abc ŴΉЙ一" ], 40 | "city": "KeepTest", 41 | "district": "Abc ŴΉЙ", 42 | "state": "Abc ŴΉЙ", 43 | "postalCode": "123456789", 44 | "period": { "start": "2000-01-01" } 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline.UnitTests/Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline/AzureDataFactorySettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataFactoryName": "DataFactoryName", 3 | "resourceLocation": "WestUS", 4 | "sourceStorageAccountName": "sourceStorageAccountName", 5 | "sourceStorageAccountKey": "sourceStorageAccountKey", 6 | "destinationStorageAccountName": "destinationStorageAccountName", 7 | "destinationStorageAccountKey": "destinationStorageAccountKey", 8 | "sourceStorageContainerName": "sourceStorageContainerName", 9 | "destinationStorageContainerName": "destinationStorageContainerName", 10 | "sourceContainerFolderPath": "", 11 | "destinationContainerFolderPath": "", 12 | "activityContainerName": "customactivityContainerName" 13 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline/Microsoft.Health.Fhir.Anonymizer.Stu3.AzureDataFactoryPipeline.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.CommandLineTool/Microsoft.Health.Fhir.Anonymizer.Stu3.CommandLineTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.Core.UnitTests/Microsoft.Health.Fhir.Anonymizer.Stu3.Core.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.Core/Constants.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Microsoft.Health.Fhir.Anonymizer.Core 4 | { 5 | internal static partial class Constants 6 | { 7 | internal const string SupportedVersion = "STU3"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.Core/Microsoft.Health.Fhir.Anonymizer.Stu3.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net6.0 6 | true 7 | MIT 8 | Copyright © Microsoft Corporation. All rights reserved. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.Core/Processors/PerturbProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hl7.Fhir.Model; 3 | 4 | namespace Microsoft.Health.Fhir.Anonymizer.Core.Processors 5 | { 6 | public partial class PerturbProcessor : IAnonymizerProcessor 7 | { 8 | private static readonly HashSet s_quantityTypeNames = new HashSet 9 | { 10 | FHIRAllTypes.Age.ToString(), 11 | FHIRAllTypes.Count.ToString(), 12 | FHIRAllTypes.Duration.ToString(), 13 | FHIRAllTypes.Distance.ToString(), 14 | FHIRAllTypes.Money.ToString(), 15 | FHIRAllTypes.Quantity.ToString(), 16 | FHIRAllTypes.SimpleQuantity.ToString() 17 | }; 18 | } 19 | } -------------------------------------------------------------------------------- /FHIR/src/Microsoft.Health.Fhir.Anonymizer.Stu3.FunctionalTests/Microsoft.Health.Fhir.Anonymizer.Stu3.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | PreserveNewest 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /GeoPol.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | ]> 8 | 9 | 10 | 11 | &GitRepoName; 12 | 13 | 14 | 15 | . 16 | 17 | 18 | 19 | 20 | .gitignore 21 | GeoPol.xml 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------