public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", "put", "patch", Route = "fhir/{res}/{id?}")] HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id) { /* 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: * FS_URL = the fully qualified URL to the FHIR Server * 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 * FS_RESOURCE = the audience or resource for the FHIR Server for Azure API for FHIR should be https://azurehealthcareapis.com */ if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FS_CLIENT_ID")) && (_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")); } } /* * 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-PROXY", "FHIREventProcessor")); /* 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; /* 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(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); /* 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); } return(new JsonResult(result)); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", "put", Route = "import/{resourceType?}")] HttpRequest req, string resourceType, [EventHub("%EventHubName%", Connection = "EventHubConnection")] IAsyncCollector <EventData> outputEvents, ILogger log) { log.LogInformation("FHIR Proxy - Import Function Invoked"); 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-PROXY", "FHIREventProcessor-Import")); /* 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); //Check for transaction bundle var result = fhirClient.SaveResource(resourceType, requestBody, "POST", customandrestheaders.ToArray()); if (result.StatusCode == HttpStatusCode.OK || result.StatusCode == HttpStatusCode.Created) { var fhirresp = JObject.Parse((string)result.Content); if (fhirresp != null && ((string)fhirresp["resourceType"]).Equals("Bundle") && ((string)fhirresp["type"]).EndsWith("-response")) { JArray entries = (JArray)fhirresp["entry"]; foreach (JToken tok in entries) { JObject entryresp = (JObject)tok["response"]; var entrystatus = (string)entryresp["status"]; if (entrystatus.Equals("200") || entrystatus.Equals("201")) { var res = (JObject)tok["resource"]; string msg = "{\"effectedresource\":\"" + (string)res["resourceType"] + "\",\"id\":\"" + (string)res["id"] + "\"}"; log.LogInformation("Adding Event: " + msg); EventData dt = new EventData(Encoding.UTF8.GetBytes(msg)); await outputEvents.AddAsync(dt); } } } else { string msg = "{\"effectedresource\":\"" + (string)fhirresp["resourceType"] + "\",\"id\":\"" + (string)fhirresp["id"] + "\"}"; log.LogInformation("Adding Event: " + msg); EventData dt = new EventData(Encoding.UTF8.GetBytes(msg)); await outputEvents.AddAsync(dt); } return(new JsonResult(fhirresp)); } string retval = (string)result.Content; if (string.IsNullOrEmpty(retval)) { retval = "{\"error\":\"Unexpected Result\"}"; } var jr = new JsonResult(JObject.Parse(retval)); jr.StatusCode = (int)result.StatusCode; return(jr); }
private static string transformTransactionBundles(string requestBody, FHIRClient fsclient, ILogger log) { if (string.IsNullOrEmpty(requestBody)) { return(requestBody); } JObject result = JObject.Parse(requestBody); if (result == null || result["resourceType"] == null || result["type"] == null) { return(requestBody); } string rtt = (string)result["resourceType"]; string bt = (string)result["type"]; if (rtt.Equals("Bundle") && bt.Equals("transaction")) { log.LogInformation($"TransformBundleProcess: looks like a valid transaction bundle"); JArray entries = (JArray)result["entry"]; if (entries == null) { return(requestBody); } log.LogInformation($"TransformBundleProcess: Phase 1 searching for existing entries on FHIR Server..."); foreach (JToken tok in entries) { if (tok != null && tok["request"]["ifNoneExist"] != null) { string resource = (string)tok["request"]["url"]; string query = (string)tok["request"]["ifNoneExist"]; log.LogInformation($"TransformBundleProcess:Loading Resource {resource} with query {query}"); var r = fsclient.LoadResource(resource, query); if (r.StatusCode == System.Net.HttpStatusCode.OK) { var rs = (JObject)r.Content; if (rs != null && ((string)rs["resourceType"]).Equals("Bundle") && rs["entry"] != null) { JArray respentries = (JArray)rs["entry"]; string existingid = "urn:uuid:" + (string)respentries[0]["resource"]["id"]; string furl = (string)tok["fullUrl"]; if (!string.IsNullOrEmpty(furl)) { requestBody.Replace(furl, existingid); } } } } } //reparse JSON with replacement of existing ids prepare to convert to Batch bundle with PUT to maintain relationships Dictionary <string, string> convert = new Dictionary <string, string>(); result = JObject.Parse(requestBody); result["type"] = "batch"; entries = (JArray)result["entry"]; foreach (JToken tok in entries) { string urn = (string)tok["fullUrl"]; if (!string.IsNullOrEmpty(urn) && urn.StartsWith("urn:uuid:") && tok["resource"] != null) { string rt = (string)tok["resource"]["resourceType"]; string rid = urn.Replace("urn:uuid:", ""); tok["resource"]["id"] = rid; convert.Add(rid, rt); tok["request"]["method"] = "PUT"; tok["request"]["url"] = $"{rt}?_id={rid}"; } } log.LogInformation($"TransformBundleProcess: Phase 2 Localizing {convert.Count} resource entries..."); string str = result.ToString(); foreach (string id1 in convert.Keys) { string r1 = convert[id1] + "/" + id1; string f = "urn:uuid:" + id1; str = str.Replace(f, r1); } return(str); } return(requestBody); }