public static FHIRClient getClient(ILogger log) { /* Get/update/check current bearer token to authenticate the proxy to the FHIR Server * The following parameters must be defined in environment variables: * To use Manged Service Identity or Service Client: * FS_URL = the fully qualified URL to the FHIR Server * FS_RESOURCE = the audience or resource for the FHIR Server for Azure API for FHIR should be https://azurehealthcareapis.com * To use a Service Client Principal the following must also be specified: * FS_TENANT_NAME = the GUID or UPN of the AAD tenant that is hosting FHIR Server Authentication * FS_CLIENT_ID = the client or app id of the private client authorized to access the FHIR Server * FS_SECRET = the client secret to pass to FHIR Server Authentication */ if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FS_RESOURCE")) && FHIRClient.isTokenExpired(_bearerToken)) { lock (_lock) { if (FHIRClient.isTokenExpired(_bearerToken)) { log.LogInformation("Token is expired...Obtaining new bearer token..."); _bearerToken = FHIRClient.GetOAUTH2BearerToken(System.Environment.GetEnvironmentVariable("FS_RESOURCE"), System.Environment.GetEnvironmentVariable("FS_TENANT_NAME"), System.Environment.GetEnvironmentVariable("FS_CLIENT_ID"), System.Environment.GetEnvironmentVariable("FS_SECRET")).GetAwaiter().GetResult(); } } } /* Get a FHIR Client instance to talk to the FHIR Server */ return(new FHIRClient(System.Environment.GetEnvironmentVariable("FS_URL"), _bearerToken)); }
public static async Task <FHIRResponse> callFHIRServer(string requestBody, HttpRequest req, ILogger log, string res, string id, string hist, string vid) { FHIRClient fhirClient = FHIRClientFactory.getClient(log); FHIRResponse fhirresp = null; if (req.Method.Equals("GET")) { var qs = req.QueryString.HasValue ? req.QueryString.Value : null; StringBuilder sb = new StringBuilder(); sb.Append(res); if (!string.IsNullOrEmpty(id)) { sb.Append("/" + id); if (!string.IsNullOrEmpty(hist)) { sb.Append("/" + hist); if (!string.IsNullOrEmpty(vid)) { sb.Append("/" + vid); } } } fhirresp = await fhirClient.LoadResource(sb.ToString(), qs, false, req.Headers); } else { if (req.Method.Equals("DELETE")) { fhirresp = await fhirClient.DeleteResource(res + (id == null ? "" : "/" + id), req.Headers); } else if (req.Method.Equals("POST") && !string.IsNullOrEmpty(id) && id.StartsWith("_search")) { var qs = req.QueryString.HasValue ? req.QueryString.Value : null; fhirresp = await fhirClient.PostCommand(res + "/" + id, requestBody, qs, req.Headers); } else { fhirresp = await fhirClient.SaveResource(res, requestBody, req.Method, req.Headers); } } return(fhirresp); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "manage/{cmd}/{res}/{id}/{name}")] HttpRequest req, ILogger log, ClaimsPrincipal principal, string cmd, string res, string id, string name) { log.LogInformation("SecureLink Function Invoked"); //Is the principal authenticated if (!Utils.isServerAccessAuthorized(req)) { return(new ContentResult() { Content = "User is not Authenticated", StatusCode = (int)System.Net.HttpStatusCode.Unauthorized }); } if (!Utils.inServerAccessRole(req, "A")) { return(new ContentResult() { Content = "User does not have suffiecient rights (Administrator required)", StatusCode = (int)System.Net.HttpStatusCode.Unauthorized }); } if (string.IsNullOrEmpty(cmd) || !validcmds.Any(cmd.Contains)) { return(new BadRequestObjectResult("Invalid Command....Valid commands are link, unlink and list")); } //Are we linking the correct resource type if (string.IsNullOrEmpty(res) || !allowedresources.Any(res.Contains)) { return(new BadRequestObjectResult("Resource must be Patient,Practitioner or RelatedPerson")); } ClaimsIdentity ci = (ClaimsIdentity)principal.Identity; string aadten = (string.IsNullOrEmpty(ci.Tenant()) ? "Unknown" : ci.Tenant()); FhirJsonParser _parser = new FhirJsonParser(); _parser.Settings.AcceptUnknownMembers = true; _parser.Settings.AllowUnrecognizedEnums = true; //Get a FHIR Client so we can talk to the FHIR Server log.LogInformation($"Instanciating FHIR Client Proxy"); FHIRClient fhirClient = FHIRClientFactory.getClient(log); int i_link_days = 0; int.TryParse(System.Environment.GetEnvironmentVariable("FP-LINK-DAYS"), out i_link_days); if (i_link_days == 0) { i_link_days = 365; } //Load the resource to Link var fhirresp = await fhirClient.LoadResource(res + "/" + id, null, false, req.Headers); var lres = _parser.Parse <Resource>((string)fhirresp.Content); if (lres.ResourceType == Hl7.Fhir.Model.ResourceType.OperationOutcome) { return(new BadRequestObjectResult(lres.ToString())); } CloudTable table = Utils.getTable(); switch (cmd) { case "link": LinkEntity linkentity = new LinkEntity(res, aadten + "-" + name); linkentity.ValidUntil = DateTime.Now.AddDays(i_link_days); linkentity.LinkedResourceId = id; Utils.setLinkEntity(table, linkentity); return(new OkObjectResult($"Identity: {name} in directory {aadten} is now linked to {res}/{id}")); case "unlink": LinkEntity delentity = Utils.getLinkEntity(table, res, aadten + "-" + name); if (delentity == null) { return(new OkObjectResult($"Resource {res}/{id} has no links to Identity {name} in directory {aadten}")); } Utils.deleteLinkEntity(table, delentity); return(new OkObjectResult($"Identity: {name} in directory {aadten} has been unlinked from {res}/{id}")); case "list": LinkEntity entity = Utils.getLinkEntity(table, res, aadten + "-" + name); if (entity != null) { return(new OkObjectResult($"Resource {res}/{id} is linked to Identity: {name} in directory {aadten}")); } else { return(new OkObjectResult($"Resource {res}/{id} has no links to Identity {name} in directory {aadten}")); } } return(new OkObjectResult($"No action taken Identity: {name}")); }
public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "linkresource/{res}/{id}")] HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id) { FhirJsonParser _parser = new FhirJsonParser(); _parser.Settings.AcceptUnknownMembers = true; _parser.Settings.AllowUnrecognizedEnums = true; log.LogInformation("SecureLink Function Invoked"); //Is the principal authenticated if (!principal.Identity.IsAuthenticated) { return(new ContentResult() { Content = "User is not Authenticated", StatusCode = (int)System.Net.HttpStatusCode.Unauthorized }); } //Is the prinicipal a FHIR Server Administrator ClaimsIdentity ci = (ClaimsIdentity)principal.Identity; if (!ci.IsInFHIRRole(Environment.GetEnvironmentVariable("ADMIN_ROLE"))) { return(new ContentResult() { Content = "User does not have suffiecient rights (Administrator required)", StatusCode = (int)System.Net.HttpStatusCode.Unauthorized }); } string aadten = ci.Tenant(); //Custom Headers for User Audit in FHIR List <HeaderParm> auditheaders = new List <HeaderParm>(); auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-USERID", principal.Identity.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-LinkResource")); //Are we linking the correct resource type if (string.IsNullOrEmpty(res) || !allowedresources.Any(res.ToLower().Contains)) { return(new BadRequestObjectResult("Linked resource must be Patient,Practitioner or RelatedPerson")); } string name = req.Query["name"]; if (string.IsNullOrEmpty(name)) { return(new BadRequestObjectResult("Linked resource must have principal name specified in parameters (e.g. ?name=)")); } //Get/update/check current bearer token to talk to authenticate to FHIR Server if (_bearerToken == null || FHIRClient.isTokenExpired(_bearerToken)) { lock (_lock) { 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")); } } //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); int.TryParse(System.Environment.GetEnvironmentVariable("LINK_DAYS"), out int i_link_days); //Load the resource to Link var fhirresp = fhirClient.LoadResource(res + "/" + id, null, false, auditheaders.ToArray()); var lres = _parser.Parse <Resource>((string)fhirresp.Content); if (lres.ResourceType == Hl7.Fhir.Model.ResourceType.OperationOutcome) { return(new BadRequestObjectResult(lres.ToString())); } //Add Link to AAD Tenent in Identifiers if (lres.ResourceType == Hl7.Fhir.Model.ResourceType.Practitioner) { var tr = (Hl7.Fhir.Model.Practitioner)lres; var fbid = tr.Identifier.FirstOrDefault(ident => ident.System == aadten); if (fbid != null) { tr.Identifier.Remove(fbid); } Hl7.Fhir.Model.Identifier newid = new Hl7.Fhir.Model.Identifier(aadten, name); newid.Period = new Hl7.Fhir.Model.Period(Hl7.Fhir.Model.FhirDateTime.Now(), new Hl7.Fhir.Model.FhirDateTime(DateTimeOffset.Now.AddDays(i_link_days))); tr.Identifier.Add(newid); FhirJsonSerializer serializer = new FhirJsonSerializer(); string srv = serializer.SerializeToString(tr); var saveresult = fhirClient.SaveResource(Enum.GetName(typeof(ResourceType), tr.ResourceType), srv, "PUT", auditheaders.ToArray()); if (saveresult.StatusCode == System.Net.HttpStatusCode.OK) { return(new OkObjectResult($"Identity: {name} in directory {aadten} is now linked to {res}/{id}")); } else { return(new BadRequestObjectResult($"Unable to link Identity: {name} in directory {aadten}:{saveresult.StatusCode}")); } } else if (lres.ResourceType == Hl7.Fhir.Model.ResourceType.Patient) { var tr = (Hl7.Fhir.Model.Patient)lres; var fbid = tr.Identifier.FirstOrDefault(ident => ident.System == aadten); if (fbid != null) { tr.Identifier.Remove(fbid); } Hl7.Fhir.Model.Identifier newid = new Hl7.Fhir.Model.Identifier(aadten, name); newid.Period = new Hl7.Fhir.Model.Period(Hl7.Fhir.Model.FhirDateTime.Now(), new Hl7.Fhir.Model.FhirDateTime(DateTimeOffset.Now.AddDays(i_link_days))); tr.Identifier.Add(newid); FhirJsonSerializer serializer = new FhirJsonSerializer(); string srv = serializer.SerializeToString(tr); var saveresult = fhirClient.SaveResource(Enum.GetName(typeof(ResourceType), tr.ResourceType), srv, "PUT", auditheaders.ToArray()); if (saveresult.StatusCode == System.Net.HttpStatusCode.OK) { return(new OkObjectResult($"Identity: {name} in directory {aadten} is now linked to {res}/{id}")); } else { return(new BadRequestObjectResult($"Unable to link Identity: {name} in directory {aadten}:{saveresult.StatusCode}")); } } return(new OkObjectResult($"No action taken Identity: {name}")); }
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)); }
private static bool IsAParticipantOrPatient(JObject resource, FHIRClient fhirClient, IEnumerable <string> knownresourceIdentities, Dictionary <string, bool> porcache, HeaderParm[] auditheaders) { string patientId = null; string encounterId = null; JObject patient = null; JObject encounter = null; string rt = (string)resource["resourceType"]; //Check for Patient resource or load patient resource from subject member if (rt.Equals("Patient")) { patient = resource; patientId = rt + "/" + (string)resource["id"]; } if (patient == null) { patientId = (string)resource?["subject"]?["reference"]; } if (rt.Equals("Encounter")) { encounter = resource; encounterId = rt + "/" + (string)resource["id"]; patientId = (string)resource?["subject"]?["reference"]; } if (encounter == null) { encounterId = (string)resource?["encounter"]?["reference"]; } //If no patient or encounter records present assume not tied to patient do not filter; if (patientId == null && encounterId == null) { return(true); } //See if patientId is in POR Cache if (!string.IsNullOrEmpty(patientId) && porcache.ContainsKey(patientId)) { return(porcache[patientId]); } if (!string.IsNullOrEmpty(encounterId) && porcache.ContainsKey(encounterId)) { return(porcache[encounterId]); } //Load the patient if needed if (patient == null) { if (!string.IsNullOrEmpty(patientId)) { var pat = fhirClient.LoadResource(patientId, null, false, auditheaders); JObject temp = JObject.Parse((string)pat.Content); if (temp != null && ((string)temp["resourceType"]).Equals("Patient")) { patient = temp; } else { porcache[patientId] = false; return(false); } } else if (!string.IsNullOrEmpty(encounterId) && patient == null) { var enc = fhirClient.LoadResource(encounterId, null, false, auditheaders); if (enc != null) { JObject temp = JObject.Parse((string)enc.Content); if (temp != null && ((string)temp["resourceType"]).Equals("Encounter") && (string)temp["subject"]?["reference"] != null) { patientId = (string)temp["subject"]?["reference"]; var pat = fhirClient.LoadResource(patientId, null, false, auditheaders); JObject temp1 = JObject.Parse((string)pat.Content); if (temp1 != null && ((string)temp1["resourceType"]).Equals("Patient")) { patient = temp1; } else { porcache[patientId] = false; return(false); } } else { porcache[encounterId] = false; return(false); } } } else { //Cannot Determine/Find a Patient or Encounter reference assume it's not a patient reference return(true); } } foreach (string rid in knownresourceIdentities) { if (rid.StartsWith("Patient")) { string pid = rid.Split("/")[1]; if (pid.Equals(patientId)) { porcache[patientId] = true; if (!string.IsNullOrEmpty(encounterId)) { porcache[encounterId] = true; } return(true); } } else if (rid.StartsWith("Practitioner")) { if (patient["generalPractitioner"] != null) { var gp_s = from gp in patient["generalPractitioner"] where (string)gp["reference"] == rid select(string) gp["reference"]; if (gp_s != null && gp_s.Count() > 0) { porcache[patientId] = true; if (!string.IsNullOrEmpty(encounterId)) { porcache[encounterId] = true; } return(true); } } string pid = rid.Split("/")[1]; string patid = (string)patient["id"]; var porencs = fhirClient.LoadResource("Encounter", $"patient={patid}&participant={pid}", false, auditheaders); if (porencs != null) { JObject temp2 = JObject.Parse((string)porencs.Content); if (temp2 != null && ((string)temp2["resourceType"]).Equals("Bundle")) { JArray entries = (JArray)temp2["entry"]; if (entries != null && entries.Count > 0) { porcache[patientId] = true; if (!string.IsNullOrEmpty(encounterId)) { porcache[encounterId] = true; } return(true); } } } } } porcache[patientId] = false; if (!string.IsNullOrEmpty(encounterId)) { porcache[encounterId] = false; } return(false); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", "put", "patch", "delete", Route = "fhir/{res?}/{id?}")] HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id) { /*The basic FHIR proxy by default only validates that the user principal is authenticated (AuthN). * You can clone this code and add your own authorization logic, pre and post processing logic to fit your business * use cases. This function will accept standard REST Verbs as used by HL7 FHIR * * IMPORTANT: Do not publish this function without Authentication (Easy Auth or APIM) you will compromise your FHIR server! * */ log.LogInformation("FHIR ProxyBase Function Invoked"); if (!principal.Identity.IsAuthenticated) { return(new ContentResult() { Content = "User is not Authenticated", StatusCode = (int)System.Net.HttpStatusCode.Unauthorized }); } /* Load the ClaimsIdentity for use in RBAC based logic */ ClaimsIdentity ci = (ClaimsIdentity)principal.Identity; /* Load the tenant Name from principal */ string aadten = ci.Tenant(); /* Load the principal Name */ string name = principal.Identity.Name; /* Minimum Authorization must be in an access Role Reader, Writer or Admin based on HTTP Verb*/ 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-access", "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-access", "User/Application must be in a writer role to update"), StatusCode = (int)System.Net.HttpStatusCode.Unauthorized, ContentType = "application/json" }); } } /* Load the request contents */ string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); /* Get/update/check current bearer token to authenticate the proxy to the FHIR Server * The following parameters must be defined in environment variables: * To use Manged Service Identity or Service Client: * FS_URL = the fully qualified URL to the FHIR Server * FS_RESOURCE = the audience or resource for the FHIR Server for Azure API for FHIR should be https://azurehealthcareapis.com * To use a Service Client Principal the following must also be specified: * FS_TENANT_NAME = the GUID or UPN of the AAD tenant that is hosting FHIR Server Authentication * FS_CLIENT_ID = the client or app id of the private client authorized to access the FHIR Server * FS_SECRET = the client secret to pass to FHIR Server Authentication */ if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FS_RESOURCE")) && FHIRClient.isTokenExpired(_bearerToken)) { lock (_lock) { if (FHIRClient.isTokenExpired(_bearerToken)) { log.LogInformation($"Obtaining new OAUTH2 Bearer Token for access to FHIR Server"); _bearerToken = FHIRClient.GetOAUTH2BearerToken(System.Environment.GetEnvironmentVariable("FS_RESOURCE"), System.Environment.GetEnvironmentVariable("FS_TENANT_NAME"), System.Environment.GetEnvironmentVariable("FS_CLIENT_ID"), System.Environment.GetEnvironmentVariable("FS_SECRET")).GetAwaiter().GetResult(); } } } /* * Create User Custom Headers these headers are passed to the FHIR Server to communicate credentials of the authorized user for each proxy call * this is ensures accruate audit trails for FHIR server access. Note: This headers are honored by the Azure API for FHIR Server */ List <HeaderParm> auditheaders = new List <HeaderParm>(); auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-USERID", principal.Identity.Name)); auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-TENANT", ci.Tenant())); auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-SOURCE", req.HttpContext.Connection.RemoteIpAddress.ToString())); auditheaders.Add(new HeaderParm("X-MS-AZUREFHIR-AUDIT-PROXY", "FHIRProxy-ProxyBase")); /* Preserve FHIR Specific change control headers and include in the proxy call */ 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 instance to talk to the FHIR Server */ log.LogInformation($"Instanciating FHIR Client Proxy"); FHIRClient fhirClient = new FHIRClient(System.Environment.GetEnvironmentVariable("FS_URL"), _bearerToken); FHIRResponse fhirresp = null; /* * TODO: Add your pre-call Filter Logic here * Any custom pre FHIR filtering, security, validations * or transform mappings. */ /* Proxy the call to the FHIR Server */ JObject result = null; 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 str = fhirresp.Content == null ? "" : (string)fhirresp.Content; /* Fix server locations to proxy address */ str = str.Replace(Environment.GetEnvironmentVariable("FS_URL"), req.Scheme + "://" + req.Host.Value + req.Path.Value.Substring(0, req.Path.Value.IndexOf(res) - 1)); result = JObject.Parse(str); /* * TODO: Add your Filter Logic here * Any custom post FHIR filtering or security checks * or transform mappings. */ /* Add Headers from FHIR Server Response */ foreach (string key in fhirresp.Headers.Keys) { req.HttpContext.Response.Headers.Remove(key); req.HttpContext.Response.Headers.Add(key, fhirresp.Headers[key].Value); } var jr = new JsonResult(result); jr.StatusCode = (int)fhirresp.StatusCode; return(jr); }