Esempio n. 1
0
        public static RedactProcessor Create(AnonymizerConfigurationManager configuratonManager)
        {
            var parameters = configuratonManager.GetParameterConfiguration();

            return(new RedactProcessor(parameters.EnablePartialDatesForRedact, parameters.EnablePartialAgesForRedact,
                                       parameters.EnablePartialZipCodesForRedact, parameters.RestrictedZipCodeTabulationAreas));
        }
Esempio n. 2
0
        public void GivenAConfigWithoutProcessingErrorsField_WhenCreateAnonymizerConfigurationManager_TheFieldShouldBeDefaultAsRaise()
        {
            var configFilePath       = "./TestConfigurations/configuration-without-processing-error.json";
            var configurationManager = AnonymizerConfigurationManager.CreateFromConfigurationFile(configFilePath);

            Assert.Equal(ProcessingErrorsOption.Raise, configurationManager.Configuration.processingErrors);
        }
        public static DateShiftProcessor Create(AnonymizerConfigurationManager configuratonManager)
        {
            var parameters = configuratonManager.GetParameterConfiguration();

            return(new DateShiftProcessor(parameters.DateShiftKey, parameters.DateShiftKeyPrefix,
                                          parameters.EnablePartialDatesForRedact));
        }
Esempio n. 4
0
        public void GivenADateShiftKeyPrefix_WhenSet_DateShiftKeyPrefixShouldBeSetCorrectly(string dateShiftKeyPrefix)
        {
            var configFilePath       = "./TestConfigurations/configuration-test-sample.json";
            var configurationManager = AnonymizerConfigurationManager.CreateFromConfigurationFile(configFilePath);

            configurationManager.SetDateShiftKeyPrefix(dateShiftKeyPrefix);

            Assert.Equal(dateShiftKeyPrefix, configurationManager.GetParameterConfiguration().DateShiftKeyPrefix);
        }
Esempio n. 5
0
        public void GivenAValidConfig_WhenCreateAnonymizerConfigurationManager_ConfigurationShouldBeLoaded(string configFilePath)
        {
            var configurationManager = AnonymizerConfigurationManager.CreateFromConfigurationFile(configFilePath);
            var patientRules         = configurationManager.GetPathRulesByResourceType("Patient");

            Assert.True(patientRules.Any());
            var typeRules = configurationManager.GetTypeRules();

            Assert.True(typeRules.Any());
            var parameters = configurationManager.GetParameterConfiguration();

            Assert.True(!string.IsNullOrEmpty(parameters.DateShiftKey));
        }
        public static ResourceAnonymizerContext Create(ElementNode root, AnonymizerConfigurationManager configurationManager)
        {
            var rules = new List <AnonymizerRule>(configurationManager.GetPathRulesByResourceType(root.InstanceType));

            var typeRules = configurationManager.GetTypeRules();

            if (typeRules != null && typeRules.Any())
            {
                var rulePaths = rules.Select(rule => rule.Path).ToHashSet();
                TransformTypeRulesToPathRules(root, typeRules, rules, rulePaths);
            }

            return(new ResourceAnonymizerContext(rules));
        }
Esempio n. 7
0
        public void GivenAValidConfig_WhenCreateAnonymizerConfigurationManager_ConfigurationShouldBeLoaded(string configFilePath)
        {
            var configurationManager = AnonymizerConfigurationManager.CreateFromConfigurationFile(configFilePath);
            var fhirRules            = configurationManager.FhirPathRules;

            Assert.True(fhirRules.Any());
            fhirRules = configurationManager.FhirPathRules;
            Assert.Single(configurationManager.FhirPathRules.Where(r => "Patient".Equals(r.ResourceType)));
            Assert.Single(configurationManager.FhirPathRules.Where(r => "TestResource".Equals(r.ResourceType)));
            Assert.Single(configurationManager.FhirPathRules.Where(r => string.IsNullOrEmpty(r.ResourceType)));
            Assert.Single(configurationManager.FhirPathRules.Where(r => "Resource".Equals(r.ResourceType)));

            var parameters = configurationManager.GetParameterConfiguration();

            Assert.True(!string.IsNullOrEmpty(parameters.DateShiftKey));
        }
        public void GivenATypeRuleContainsAnother_WhenParseRule_NestedOneShouldOverwriteInheritedOne()
        {
            AnonymizerConfiguration configuration = new AnonymizerConfiguration()
            {
                PathRules = new Dictionary <string, string>(),
                ParameterConfiguration = new ParameterConfiguration(),
                TypeRules = new Dictionary <string, string>()
                {
                    { "Address", "redact" },
                    { "dateTime", "keep" }
                }
            };

            AnonymizerConfigurationManager configurationManager = new AnonymizerConfigurationManager(configuration);
            var context = ResourceAnonymizerContext.Create(TestPatientElementNode(), configurationManager);

            Assert.Contains("Patient.address", context.PathSet);
            Assert.Contains("Patient.address.period.start", context.PathSet);
            Assert.Equal("keep", context.RuleList.First(r => r.Path.Equals("Patient.address.period.start")).Method);
            Assert.Equal("redact", context.RuleList.First(r => r.Path.Equals("Patient.address")).Method);
        }
        public void GivenConflictTypeRuleAndPathRule_WhenParseRule_TypeRuleShouldBeIgnored()
        {
            AnonymizerConfiguration configuration = new AnonymizerConfiguration()
            {
                PathRules = new Dictionary <string, string>()
                {
                    { "Patient.address", "keep" }
                },
                ParameterConfiguration = new ParameterConfiguration(),
                TypeRules = new Dictionary <string, string>()
                {
                    { "Address", "redact" }
                }
            };

            AnonymizerConfigurationManager configurationManager = new AnonymizerConfigurationManager(configuration);
            var context = ResourceAnonymizerContext.Create(TestPatientElementNode(), configurationManager);

            Assert.Contains("Patient.address", context.PathSet);
            Assert.Equal("keep", context.RuleList.First().Method);
        }
Esempio n. 10
0
        public async Task <IAnonymizer> CreateAnonymizerAsync(string configurationLocation, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNullOrEmpty(configurationLocation, nameof(configurationLocation));

            using (Stream stream = new MemoryStream())
            {
                try
                {
                    await _artifactProvider.FetchAsync(configurationLocation, stream, cancellationToken);

                    stream.Position = 0;
                }
                catch (FileNotFoundException ex)
                {
                    throw new AnonymizationConfigurationNotFoundException(ex.Message, ex);
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Failed to fetch Anonymization configuration file: {configurationLocation}");
                    throw new AnonymizationConfigurationFetchException(ex.Message, ex);
                }

                using (StreamReader reader = new StreamReader(stream))
                {
                    string configurationContent = await reader.ReadToEndAsync();

                    try
                    {
                        var engine = new AnonymizerEngine(AnonymizerConfigurationManager.CreateFromSettingsInJson(configurationContent));
                        return(new ExportAnonymizer(engine));
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"Failed to parse configuration file: {ex.Message}");
                        throw new FailedToParseAnonymizationConfigurationException(ex.Message, ex);
                    }
                }
            }
        }
Esempio n. 11
0
        public void GivenATypeRule_WhenParseRule_TransformedPathRuleShouldBeReturned()
        {
            AnonymizerConfiguration configuration = new AnonymizerConfiguration()
            {
                PathRules = new Dictionary <string, string>(),
                ParameterConfiguration = new ParameterConfiguration(),
                TypeRules = new Dictionary <string, string>()
                {
                    { "Address", "redact" },
                    { "HumanName", "redact" },
                    { "dateTime", "dateShift" }
                }
            };

            AnonymizerConfigurationManager configurationManager = new AnonymizerConfigurationManager(configuration);
            var context = ResourceAnonymizerContext.Create(TestPatientElementNode(), configurationManager);

            Assert.Contains("Patient.address", context.PathSet);
            Assert.Contains("Patient.name", context.PathSet);
            Assert.Contains("Patient.address.period.start", context.PathSet);
            Assert.Contains("Patient.identifier.period.start", context.PathSet);
        }
Esempio n. 12
0
 public void GivenAnInvalidConfig_WhenCreateAnonymizerConfigurationManager_ExceptionShouldBeThrown(string configFilePath)
 {
     Assert.Throws <AnonymizerConfigurationErrorsException>(() => AnonymizerConfigurationManager.CreateFromConfigurationFile(configFilePath));
 }
Esempio n. 13
0
        public static async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", "put", "patch", "delete", Route = "participant/{res}/{id?}")] HttpRequest req,
            [Blob("%DEIDCONFIG%", FileAccess.Read, Connection = "STORAGEACCT")] CloudBlockBlob deidconfig,
            ILogger log, ClaimsPrincipal principal, string res, string id)
        {
            log.LogInformation("FHIR SecureAccess Function Invoked");
            if (!principal.Identity.IsAuthenticated)
            {
                return(new ContentResult()
                {
                    Content = Utils.genOOErrResponse("login", "User is not Authenticated"), StatusCode = (int)System.Net.HttpStatusCode.Unauthorized, ContentType = "application/json"
                });
            }
            //Is the prinicipal a FHIR Server Administrator
            ClaimsIdentity ci    = (ClaimsIdentity)principal.Identity;
            bool           admin = ci.IsInFHIRRole(Environment.GetEnvironmentVariable("ADMIN_ROLE"));

            //GET (READ)

            if (req.Method.Equals("GET"))
            {
                if (!admin && !ci.IsInFHIRRole(Environment.GetEnvironmentVariable("READER_ROLE")))
                {
                    return(new ContentResult()
                    {
                        Content = Utils.genOOErrResponse("auth-denied", "User/Application must be in a reader role to access"), StatusCode = (int)System.Net.HttpStatusCode.Unauthorized, ContentType = "application/json"
                    });
                }
            }
            else
            { //OTHER VERBS ARE WRITER
                if (!admin && !ci.IsInFHIRRole(Environment.GetEnvironmentVariable("WRITER_ROLE")))
                {
                    return(new ContentResult()
                    {
                        Content = Utils.genOOErrResponse("auth-denied", "User/Application must be in a writer role to update"), StatusCode = (int)System.Net.HttpStatusCode.Unauthorized, ContentType = "application/json"
                    });
                }
            }
            string aadten      = ci.Tenant();
            string name        = principal.Identity.Name;
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            //Get/update/check current bearer token to talk to authemticate to FHIR Server
            if (_bearerToken == null || FHIRClient.isTokenExpired(_bearerToken))
            {
                lock (_lock)
                {
                    if (_bearerToken == null || FHIRClient.isTokenExpired(_bearerToken))
                    {
                        log.LogInformation($"Obtaining new OAUTH2 Bearer Token for access to FHIR Server");
                        _bearerToken = FHIRClient.GetOAUTH2BearerToken(System.Environment.GetEnvironmentVariable("FS_TENANT_NAME"), System.Environment.GetEnvironmentVariable("FS_RESOURCE"),
                                                                       System.Environment.GetEnvironmentVariable("FS_CLIENT_ID"), System.Environment.GetEnvironmentVariable("FS_SECRET"));
                    }
                }
            }
            //Create User Custom Headers for Audit
            List <HeaderParm> auditheaders = new List <HeaderParm>();

            auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-USERID", name));
            auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-TENANT", aadten));
            auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-SOURCE", req.HttpContext.Connection.RemoteIpAddress.ToString()));
            auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-PROXY", "FHIRProxy-ParticipantPatientRelationship"));
            //Preserve Relevant FHIR Headers
            List <HeaderParm> customandrestheaders = new List <HeaderParm>();

            foreach (string key in req.Headers.Keys)
            {
                string s = key.ToLower();
                if (s.Equals("etag"))
                {
                    customandrestheaders.Add(new HeaderParm(key, req.Headers[key].First()));
                }
                else if (s.StartsWith("if-"))
                {
                    customandrestheaders.Add(new HeaderParm(key, req.Headers[key]
                                                            .First()));
                }
            }
            //Add User Audit Headers
            customandrestheaders.AddRange(auditheaders);
            //Get a FHIR Client so we can talk to the FHIR Server
            log.LogInformation($"Instanciating FHIR Client Proxy");
            FHIRClient    fhirClient         = new FHIRClient(System.Environment.GetEnvironmentVariable("FS_URL"), _bearerToken);
            FHIRResponse  fhirresp           = null;
            List <string> resourceidentities = new List <string>();
            List <string> inroles            = ci.Roles();
            List <string> fhirresourceroles  = new List <string>();

            fhirresourceroles.AddRange(Environment.GetEnvironmentVariable("PARTICIPANT_ACCESS_ROLES").Split(","));
            fhirresourceroles.AddRange(Environment.GetEnvironmentVariable("PATIENT_ACCESS_ROLES").Split(","));
            //Load linked Resource Identifiers for each known role the user is in
            foreach (string r in inroles)
            {
                if (fhirresourceroles.Any(r.Equals))
                {
                    fhirresp = fhirClient.LoadResource(r, $"identifier={aadten}|{name}", true, auditheaders.ToArray());
                    var st = (JObject)fhirresp.Content;
                    if (st != null && ((string)st["resourceType"]).Equals("Bundle"))
                    {
                        JArray entries = (JArray)st["entry"];
                        foreach (JToken tok in entries)
                        {
                            resourceidentities.Add((string)tok["resource"]["resourceType"] + "/" + (string)tok["resource"]["id"]);
                        }
                    }
                }
            }
            //Proxy the call to the FHIR Server
            JObject result = null;
            Dictionary <string, bool> porcache = new Dictionary <string, bool>();

            if (req.Method.Equals("GET"))
            {
                var qs = req.QueryString.HasValue ? req.QueryString.Value : null;
                fhirresp = fhirClient.LoadResource(res + (id == null ? "" : "/" + id), qs, false, customandrestheaders.ToArray());
            }
            else
            {
                fhirresp = fhirClient.SaveResource(res, requestBody, req.Method, customandrestheaders.ToArray());
            }
            //Fix location header to proxy address
            if (fhirresp.Headers.ContainsKey("Location"))
            {
                fhirresp.Headers["Location"].Value = fhirresp.Headers["Location"].Value.Replace(Environment.GetEnvironmentVariable("FS_URL"), req.Scheme + "://" + req.Host.Value + req.Path.Value.Substring(0, req.Path.Value.IndexOf(res) - 1));
            }
            var fhirstr = fhirresp.Content == null ? "" : (string)fhirresp.Content;

            //Fix server locations to proxy address
            fhirstr = fhirstr.Replace(Environment.GetEnvironmentVariable("FS_URL"), req.Scheme + "://" + req.Host.Value + req.Path.Value.Substring(0, req.Path.Value.IndexOf(res) - 1));
            result  = JObject.Parse(fhirstr);
            //Role Checks if not Administrator
            if (!admin && !ci.IsInFHIRRole(Environment.GetEnvironmentVariable("GLOBAL_ACCESS_ROLES")))
            {
                if (((string)result["resourceType"]).Equals("Bundle"))
                {
                    JArray entries  = (JArray)result["entry"];
                    JArray toremove = new JArray();
                    for (int i = entries.Count - 1; i >= 0; i--)
                    {
                        if (!IsAParticipantOrPatient((JObject)entries[i]["resource"], fhirClient, resourceidentities, porcache, auditheaders.ToArray()))
                        {
                            entries[i].Remove();
                        }
                    }
                }
                else if (!((string)result["resourceType"]).Equals("OperationalOutcome"))
                {
                    if (!IsAParticipantOrPatient(result, fhirClient, resourceidentities, porcache, auditheaders.ToArray()))
                    {
                        return(new ContentResult()
                        {
                            Content = Utils.genOOErrResponse("auth-denied", $"Not authorized to access resource:{res + (id == null ? "" : "/" + id)}"), StatusCode = (int)System.Net.HttpStatusCode.Unauthorized, ContentType = "application/json"
                        });
                    }
                }
            }
            //Add Response from FHIR Server Headers
            foreach (string key in fhirresp.Headers.Keys)
            {
                req.HttpContext.Response.Headers.Remove(key);
                req.HttpContext.Response.Headers.Add(key, fhirresp.Headers[key].Value);
            }
            //DE-ID
            if (ci.IsInFHIRRole(Environment.GetEnvironmentVariable("DEID_ROLES")))
            {
                if (_deidconfig == null && deidconfig.ExistsAsync().GetAwaiter().GetResult())
                {
                    lock (_lock)
                    {
                        if (_deidconfig == null)
                        {
                            log.LogInformation($"Loading de-id config from blob store");
                            var cs = deidconfig.DownloadTextAsync().GetAwaiter().GetResult();
                            _deidconfig = AnonymizerConfigurationManager.CreateFromConfigurationString(cs);
                        }
                    }
                }
                AnonymizerEngine _engine = new AnonymizerEngine(_deidconfig);
                var str1 = _engine.AnonymizeJson(result.ToString(Formatting.None));
                result = JObject.Parse(str1);
            }
            return(new JsonResult(result));
        }
Esempio n. 14
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Fastest);
            services.AddResponseCompression(options =>
            {
                options.MimeTypes =
                    ResponseCompressionDefaults.MimeTypes.Concat(
                        new[] { "application/fhir+json" });
            });

            var apiKey = Configuration.GetValue <string>("ApiKey");

            services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme)
            .AddApiKeyInHeaderOrQueryParams(options =>
            {
                options.Realm   = "FHIR Pseudonymizer";
                options.KeyName = "X-Api-Key";
                options.IgnoreAuthenticationIfAllowAnonymous = true;

                options.Events = new ApiKeyEvents
                {
                    OnValidateKey = ctx =>
                    {
                        if (string.IsNullOrWhiteSpace(apiKey) || !apiKey.Equals(ctx.ApiKey, StringComparison.InvariantCulture))
                        {
                            ctx.ValidationFailed();
                            return(Task.CompletedTask);
                        }

                        var claims = new[]
                        {
                            new Claim("ApiAccess", "Access to FHIR Pseudonymizer API")
                        };
                        ctx.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, ctx.Scheme.Name));
                        ctx.Success();
                        return(Task.CompletedTask);
                    }
                };
            });

            services.AddHttpClient("gPAS", client =>
            {
                client.BaseAddress = new Uri(Configuration["gPAS:Url"]);

                if (Configuration["gPAS:Auth:Basic:Username"] != null)
                {
                    var basicAuthString = $"{Configuration["gPAS:Auth:Basic:Username"]}:{Configuration["gPAS:Auth:Basic:Password"]}";
                    var byteArray       = Encoding.UTF8.GetBytes(basicAuthString);
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
                                                                                               Convert.ToBase64String(byteArray));
                }
            }).SetHandlerLifetime(TimeSpan.FromMinutes(5))
            .AddPolicyHandler(GetRetryPolicy(Configuration.GetValue <int>("gPAS:RequestRetryCount")))
            .UseHttpClientMetrics();

            services.AddTransient <IGPasFhirClient, GPasFhirClient>();

            AnonymizerEngine.InitializeFhirPathExtensionSymbols();

            var anonConfigManager = AnonymizerConfigurationManager.CreateFromYamlConfigFile(Configuration["AnonymizationEngineConfigPath"]);

            // add the anon config as an additional service to allow mocking it
            services.AddSingleton(_ => anonConfigManager);

            services.AddSingleton <IAnonymizerEngine>(sp =>
            {
                var anonConfig = sp.GetService <AnonymizerConfigurationManager>();
                var engine     = new AnonymizerEngine(anonConfig);

                if (!string.IsNullOrWhiteSpace(Configuration["gPAS:Url"]))
                {
                    var gpasFhirClient = sp.GetService <IGPasFhirClient>();
                    engine.AddProcessor("pseudonymize", new GPasPseudonymizationProcessor(gpasFhirClient));
                }

                return(engine);
            });

            services.AddSingleton <IDePseudonymizerEngine>(sp =>
            {
                var anonConfig = sp.GetService <AnonymizerConfigurationManager>();
                var engine     = new DePseudonymizerEngine(anonConfig);

                if (!string.IsNullOrWhiteSpace(Configuration["gPAS:Url"]))
                {
                    var gpasFhirClient = sp.GetService <IGPasFhirClient>();
                    engine.AddProcessor("pseudonymize", new GPasDePseudonymizationProcessor(gpasFhirClient));
                }

                engine.AddProcessor("encrypt", new DecryptProcessor(anonConfig.GetParameterConfiguration().EncryptKey));
                return(engine);
            });

            services.AddRouting(options => options.LowercaseUrls = true);

            services.AddControllers(options =>
            {
                options.InputFormatters.Insert(0, new FhirInputFormatter());
                options.OutputFormatters.Insert(0, new FhirOutputFormatter());
            });

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v2", new OpenApiInfo {
                    Title = "FHIR Pseudonymizer", Version = "v2"
                });

                // Set the comments path for the Swagger JSON and UI.
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                c.IncludeXmlComments(xmlPath);
            });

            services.AddHealthChecks()
            .AddCheck("live", () => HealthCheckResult.Healthy());

            if (Configuration.GetValue <bool>("Tracing:Enabled"))
            {
                services.AddOpenTelemetryTracing(builder =>
                {
                    var serviceName = Environment.GetEnvironmentVariable("JAEGER_SERVICE_NAME") ??
                                      Environment.GetEnvironmentVariable("OTEL_SERVICE_NAME") ??
                                      Configuration.GetValue <string>("Tracing:ServiceName");

                    builder
                    .AddAspNetCoreInstrumentation(o =>
                    {
                        o.Filter = (r) =>
                        {
                            var ignoredPaths = new[]
                            {
                                "/health",
                                "/ready",
                                "/live",
                                "/fhir/metadata"
                            };

                            var path = r.Request.Path.Value;
                            return(!ignoredPaths.Any(path.Contains));
                        };
                    })
                    .AddSource(Program.ActivitySource.Name)
                    .AddHttpClientInstrumentation()
                    .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName))
                    .AddJaegerExporter();
                });

                services.Configure <JaegerExporterOptions>(Configuration.GetSection("Tracing:Jaeger"));
            }
        }