public InteractController() { string fhirserver = CloudConfigurationManager.GetSetting("FHIRServerAddress"); string fhirtenant = CloudConfigurationManager.GetSetting("FHIRAADTenant"); string fhirclientid = CloudConfigurationManager.GetSetting("FHIRClientId"); string fhirclientsecret = CloudConfigurationManager.GetSetting("FHIRClientSecret"); _client = new FHIRClient(fhirserver, fhirtenant, fhirclientid, fhirclientsecret); }
public async Task <ProxyProcessResult> Process(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id, string hist, string vid) { if (!req.Method.Equals("GET") || response.StatusCode != HttpStatusCode.OK || response.Content == null) { return(new ProxyProcessResult(true, "", "", response)); } List <JToken> ss = null; if (DATE_SORT_RESOURCES_SUPPORTED.Contains(res) && req.Query.ContainsKey("_sort") && req.Query["_sort"].First().Contains("date")) { var fhirresp = JObject.Parse(response.Content.ToString()); if (fhirresp.IsNullOrEmpty() || !((string)fhirresp["resourceType"]).Equals("Bundle") || !((string)fhirresp["type"]).Equals("searchset")) { return(new ProxyProcessResult(true, "", "", response)); } ss = new List <JToken>(); addEntries((JArray)fhirresp["entry"], ss, log); //Process Next Pages until out or max array bool nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); while (nextlink && ss.Count < MAX_ARRAY_SIZE) { string nextpage = (string)fhirresp["link"].getFirstField()["url"]; FHIRClient fhirClient = FHIRClientFactory.getClient(log); var nextresult = await fhirClient.LoadResource(nextpage); fhirresp = JObject.Parse(nextresult.Content.ToString()); if (fhirresp.IsNullOrEmpty() || !fhirresp.FHIRResourceType().Equals("Bundle") || !((string)fhirresp["type"]).Equals("searchset")) { return(new ProxyProcessResult(false, "Next Page not Returned or server error", "", nextresult)); } addEntries((JArray)fhirresp["entry"], ss, log); nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); } ss.Sort(new DateSortedIndexComparar(req.Query["_sort"].First().Contains("-date"))); fhirresp["entry"] = new JArray(ss.ToArray()); fhirresp["link"] = new JArray(); response.Content = fhirresp.ToString(); } var retVal = new ProxyProcessResult(); if (ss != null && ss.Count >= MAX_ARRAY_SIZE) { retVal.ErrorMsg = "Warning: _Sort exceeed or met MAX_ARRAY_SIZE results may not be accurate!"; log.LogWarning("_Sort exceeed or met MAX_ARRAY_SIZE results may not be accurate!"); } retVal.Response = response; return(retVal); }
public async Task <ProxyProcessResult> Process(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id, string hist, string vid) { if (req.Method.Equals("GET") && res.SafeEquals("Patient") && !string.IsNullOrEmpty(id) && hist.SafeEquals("$everything")) { ConcurrentBag <JToken> ss = new ConcurrentBag <JToken>(); FHIRClient fhirClient = FHIRClientFactory.getClient(log); var nextresult = await fhirClient.LoadResource("Patient", "_id=" + id); var fhirresp = JObject.Parse(nextresult.Content.ToString()); if (fhirresp.IsNullOrEmpty() || !fhirresp.FHIRResourceType().Equals("Bundle") || !((string)fhirresp["type"]).Equals("searchset")) { return(new ProxyProcessResult(false, "Patient not found", "", nextresult)); } addEntries((JArray)fhirresp["entry"], ss, log, "Patient"); CountdownEvent countdown = new CountdownEvent(everyresource.Length); foreach (string rt in everyresource) { ThreadPool.QueueUserWorkItem(async delegate { try { FHIRClient fhirClient1 = FHIRClientFactory.getClient(log); string[] s = rt.Split(":"); log.LogInformation($"Loading {s[0]} resources for patient {id}"); var rslt = await fhirClient1.LoadResource(s[0], s[1].Replace("{id}", id) + "&_count=100"); var resp = JObject.Parse(rslt.Content.ToString()); if (!resp.IsNullOrEmpty() && resp.FHIRResourceType().Equals("Bundle") && ((string)resp["type"]).Equals("searchset")) { addEntries((JArray)resp["entry"], ss, log, s[0]); } countdown.Signal(); } catch (Exception e) { log.LogError($"Error fetching {rt} resources for patient {id}:{e.Message}"); countdown.Signal(); } }); } countdown.Wait(); fhirresp["entry"] = new JArray(ss.ToArray()); fhirresp["link"] = new JArray(); nextresult.Content = fhirresp; return(new ProxyProcessResult(false, "", requestBody, nextresult)); } return(new ProxyProcessResult(true, "", requestBody, null)); }
public async Task <ProxyProcessResult> Process(string requestBody, HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id, string hist, string vid) { if (req.Method.Equals("GET") && res.SafeEquals("Patient") && !string.IsNullOrEmpty(id) && hist.SafeEquals("$everything")) { ConcurrentBag <JToken> ss = new ConcurrentBag <JToken>(); FHIRClient fhirClient = FHIRClientFactory.getClient(log); var nextresult = await fhirClient.LoadResource("Patient", "_id=" + id); var fhirresp = JObject.Parse(nextresult.Content.ToString()); if (fhirresp.IsNullOrEmpty() || fhirresp["entry"].IsNullOrEmpty()) { return(new ProxyProcessResult(false, "Patient not found or server error", "", null)); } addEntries((JArray)fhirresp["entry"], ss, log); nextresult = await fhirClient.LoadResource($"Patient/{id}/*", "_count=100"); fhirresp = JObject.Parse(nextresult.Content.ToString()); if (!fhirresp.IsNullOrEmpty() && !fhirresp["entry"].IsNullOrEmpty()) { addEntries((JArray)fhirresp["entry"], ss, log); bool nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); while (nextlink && ss.Count < MAX_ARRAY_SIZE) { string nextpage = (string)fhirresp["link"].getFirstField()["url"]; FHIRClient fhirClient1 = FHIRClientFactory.getClient(log); nextresult = await fhirClient1.LoadResource(nextpage); fhirresp = JObject.Parse(nextresult.Content.ToString()); if (fhirresp.IsNullOrEmpty() || !fhirresp.FHIRResourceType().Equals("Bundle") || !((string)fhirresp["type"]).Equals("searchset")) { return(new ProxyProcessResult(false, "Next Page not Returned or server error", "", null)); } addEntries((JArray)fhirresp["entry"], ss, log); nextlink = !fhirresp["link"].IsNullOrEmpty() && ((string)fhirresp["link"].getFirstField()["relation"]).Equals("next"); } } fhirresp["entry"] = new JArray(ss.ToArray()); fhirresp["link"] = new JArray(); nextresult.Content = fhirresp; return(new ProxyProcessResult(false, "", requestBody, nextresult)); } return(new ProxyProcessResult(true, "", requestBody, null)); }
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable <object> result) { var activity = await result as Activity; string msg = activity.Text.ToLower().Trim(); if (msg == "start over" || msg == "exit" || msg == "quit" || msg == "done" || msg == "start again" || msg == "restart" || msg == "leave" || msg == "reset" || msg == "bye" || msg == "goodbye") { context.Done("end"); } else { if (isSessionTimedOut(context)) { context.Fail(new SessionTimedOutException("Your session has timedout or is invalid.")); } else { string fhirserver = null; context.PrivateConversationData.TryGetValue <string>("fhirserver", out fhirserver); string token = null; context.PrivateConversationData.TryGetValue <string>("bearertoken", out token); string patid = null; context.PrivateConversationData.TryGetValue <string>("id", out patid); //Initialize FHIR Client FHIRClient client = new FHIRClient(fhirserver, token); //Determine Intent/Entities from LUIS var luisresp = await LUISClient.RequestAsync <LUISResponse>(activity.Text); var intent = luisresp.topScoringIntent; //Nothing over 50% confidence bail if (intent == null) { await context.PostAsync($"Sorry, I didn't understand {activity.Text}..."); } if (intent.intent == "FindPractitioner") { if (luisresp.entities == null || luisresp.entities.Count() == 0) { await context.PostAsync($"Sorry, I think you want to find a practioner but I can't determine the location you want"); } //Handle Geography var geo = luisresp.entities.FirstOrDefault(); //Call FHIR Server string parm = geo.entity.ToLower().StartsWith("anywhere") ? "" : $"city={geo.entity}"; var bundle = (Hl7.Fhir.Model.Bundle)client.LoadResource("Practitioner", parm); if (bundle.Entry.Count == 0) { await context.PostAsync($"Sorry, I couldn't find providers in {geo.entity}..."); } else { StringBuilder sb = new StringBuilder(); sb.Append($"Got {bundle.Entry.Count()} practioners in {geo.entity}\n\n"); foreach (var entry in bundle.Entry) { Hl7.Fhir.Model.Practitioner p = (Hl7.Fhir.Model.Practitioner)entry.Resource; var address = (p.Address.Count > 0 ? p.Address[0] : null); var tel = (p.Telecom.Count > 0 ? p.Telecom[0] : null); sb.Append($"{p.Name[0].Text} is accepting new patients."); if (address != null) { sb.Append($"Located at " + address.LineElement[0] + " " + address.City); } if (tel != null) { sb.Append($" Phone:" + tel.Value); } sb.Append("\n\n"); } await context.PostAsync(sb.ToString()); } } else if (intent.intent == "ChartHeartRate") { var entity = luisresp.FindEntityByType("builtin.number"); if (entity != null) { var hr = Convert.ToDecimal(new String(entity.entity.Where(Char.IsDigit).ToArray())); Hl7.Fhir.Model.Observation rv = new Hl7.Fhir.Model.Observation(); rv.Status = Hl7.Fhir.Model.ObservationStatus.Final; rv.Category = new List <Hl7.Fhir.Model.CodeableConcept>(); Hl7.Fhir.Model.CodeableConcept cc = new Hl7.Fhir.Model.CodeableConcept("http://hl7.org/fhir/observation-category", "vital-signs", "Vital Signs"); rv.Category.Add(cc); rv.Code = new Hl7.Fhir.Model.CodeableConcept("http://loinc.org", "8867-4", "Heart rate"); rv.Subject = new Hl7.Fhir.Model.ResourceReference("patient/" + patid); rv.Effective = Hl7.Fhir.Model.FhirDateTime.Now(); rv.Value = new Hl7.Fhir.Model.Quantity(hr, "beats/minute"); var rslt = client.SaveResource(rv); if (rslt) { await context.PostAsync($"Thanks, I recorded your heart rate of {entity.entity} in your medical record"); } else { await context.PostAsync($"Sorry I had a problem posting your heartrate...try again later."); } } else { await context.PostAsync($"Sorry, I think you want to record your heartrate vital but I can't understand the value you provided."); } } else if (intent.intent == "ChartWeight") { string entity_weight = null; var entity = luisresp.FindEntityByType("weight"); if (entity == null) { entity = luisresp.FindEntityByType("builtin.number"); if (entity != null) { entity_weight = entity.entity; var wt = luisresp.FindEntityByType("builtin.dimension"); if (wt != null) { if (wt.entity.ToLower().EndsWith("kilograms") || wt.entity.ToLower().EndsWith("kg")) { entity_weight = entity_weight + "kg"; } else { entity_weight = entity_weight + "lbs"; } } } } else { entity_weight = entity.entity; } if (entity_weight != null) { var weight = Convert.ToDecimal(new String(entity_weight.Where(Char.IsDigit).ToArray())); //Determine pounds or kg if (entity_weight.ToLower().EndsWith("kg")) { weight = weight * (decimal)2.20462; } Hl7.Fhir.Model.Observation rv = new Hl7.Fhir.Model.Observation(); rv.Status = Hl7.Fhir.Model.ObservationStatus.Final; rv.Category = new List <Hl7.Fhir.Model.CodeableConcept>(); Hl7.Fhir.Model.CodeableConcept cc = new Hl7.Fhir.Model.CodeableConcept("http://hl7.org/fhir/observation-category", "vital-signs", "Vital Signs"); rv.Category.Add(cc); rv.Code = new Hl7.Fhir.Model.CodeableConcept("http://loinc.org", "29463-7", "Body Weight"); rv.Code.Coding.Add(new Hl7.Fhir.Model.Coding("http://snomed.info/sct", "27113001", "Body Weight")); rv.Subject = new Hl7.Fhir.Model.ResourceReference("patient/" + patid); rv.Effective = Hl7.Fhir.Model.FhirDateTime.Now(); rv.Value = new Hl7.Fhir.Model.Quantity(weight, "lbs"); ((Hl7.Fhir.Model.Quantity)rv.Value).Code = "[lb_av]"; var rslt = client.SaveResource(rv); if (rslt) { string weightmsg = ""; string parm = $"patient={patid}"; var bundle = (Hl7.Fhir.Model.Bundle)client.LoadResource("Observation", parm); List <Hl7.Fhir.Model.Observation> weights = new List <Hl7.Fhir.Model.Observation>(); foreach (Hl7.Fhir.Model.Bundle.EntryComponent entry in bundle.Entry) { Hl7.Fhir.Model.Observation o = (Hl7.Fhir.Model.Observation)entry.Resource; if (FHIRUtils.isCodeMatch(o.Code.Coding, "http://loinc.org", "29463-7")) { weights.Add(o); } } if (weights.Count > 1) { Hl7.Fhir.Model.Observation[] sorted = weights.OrderBy(o => Convert.ToDateTime(o.Effective.ToString())).ToArray(); decimal prevw = ((Hl7.Fhir.Model.SimpleQuantity)sorted[sorted.Length - 2].Value).Value.Value; decimal curw = ((Hl7.Fhir.Model.SimpleQuantity)sorted[sorted.Length - 1].Value).Value.Value; decimal dif = prevw - curw; if (dif < 0) { weightmsg = "Looks like you gained around " + Math.Round(Math.Abs(dif), 1) + " lbs."; } else if (dif > 0) { weightmsg = "Looks like you loss around " + Math.Round(Math.Abs(dif), 1) + " lbs. Keep it up!!"; } else { weightmsg = "Your weight is holding steady"; } } await context.PostAsync($"Thanks, I recorded your weight of {weight} lbs in your medical record\n\n{weightmsg}"); } else { await context.PostAsync($"Sorry I had a problem posting your weight...try again later."); } } } else if (intent.intent == "ChartGlucose") { var entity = luisresp.FindEntityByType("builtin.number"); if (entity != null) { var glucose = Convert.ToDecimal(new String(entity.entity.Where(Char.IsDigit).ToArray())); var unit = "mg/dL"; Hl7.Fhir.Model.CodeableConcept loinc = new Hl7.Fhir.Model.CodeableConcept("http://loinc.org", "2345-7", "Glucose [Mass/volume] in Serum or Plasma"); //convert mg/DL if mmol/l if (glucose < 20) { glucose = glucose * 18; } Hl7.Fhir.Model.Observation rv = new Hl7.Fhir.Model.Observation(); rv.Status = Hl7.Fhir.Model.ObservationStatus.Final; rv.Category = new List <Hl7.Fhir.Model.CodeableConcept>(); Hl7.Fhir.Model.CodeableConcept cc = new Hl7.Fhir.Model.CodeableConcept("http://hl7.org/fhir/observation-category", "laboratory", "Laboratory"); rv.Category.Add(cc); rv.Code = loinc; rv.Subject = new Hl7.Fhir.Model.ResourceReference("patient/" + patid); rv.Effective = Hl7.Fhir.Model.FhirDateTime.Now(); rv.Issued = DateTimeOffset.UtcNow; rv.ReferenceRange = new List <Hl7.Fhir.Model.Observation.ReferenceRangeComponent>(); Hl7.Fhir.Model.Observation.ReferenceRangeComponent rrc = new Hl7.Fhir.Model.Observation.ReferenceRangeComponent(); rrc.High = new Hl7.Fhir.Model.SimpleQuantity(); rrc.High.Unit = "mg/dL"; rrc.High.Code = rrc.High.Unit; rrc.High.System = "http://unitsofmeasure.org"; rrc.High.Value = 140; rrc.Low = new Hl7.Fhir.Model.SimpleQuantity(); rrc.Low.Unit = "mg/dL"; rrc.Low.Code = rrc.Low.Unit; rrc.Low.System = "http://unitsofmeasure.org"; rrc.Low.Value = 70; rv.ReferenceRange.Add(rrc); rv.Value = new Hl7.Fhir.Model.Quantity(glucose, unit); ((Hl7.Fhir.Model.Quantity)rv.Value).Code = unit; var rslt = client.SaveResource(rv); if (rslt) { int numglucose = 0; string parm = $"patient={patid}"; var bundle = (Hl7.Fhir.Model.Bundle)client.LoadResource("Observation", parm); decimal bga = 0; foreach (Hl7.Fhir.Model.Bundle.EntryComponent entry in bundle.Entry) { Hl7.Fhir.Model.Observation o = (Hl7.Fhir.Model.Observation)entry.Resource; if (FHIRUtils.isCodeMatch(o.Code.Coding, "http://loinc.org", "2345-7")) { bga = bga + ((Hl7.Fhir.Model.SimpleQuantity)o.Value).Value.Value; numglucose++; } } bga = Math.Round(bga / numglucose, 1); decimal a1c = ((Convert.ToDecimal(46.7) + bga) / Convert.ToDecimal(28.7)); string bgmsg = $"Your blood glucose is averaging {bga} mg/dL " + (bga > 140 ? "looks like it's running high" : (bga < 70 ? "looks like it's running low" : "")); bgmsg = bgmsg + "\n\nYour estimated A1C is " + Convert.ToString(Math.Round(a1c, 1)); await context.PostAsync($"Thanks, I recorded your blood glucose of {glucose} mg/dL in your medical record\n\n{bgmsg}"); } else { await context.PostAsync($"Sorry I had a problem posting your blood glucose...you can try again later."); } } else { await context.PostAsync($"Sorry, I couldnt determine your blood glucose value...try again"); } } else if (intent.intent == "DisplayGlucose") { int numglucose = 0; string parm = $"patient={patid}"; var bundle = (Hl7.Fhir.Model.Bundle)client.LoadResource("Observation", parm); decimal bga = 0; decimal la1c = 0; decimal ea1c = 0; DateTime?dta1c = null; foreach (Hl7.Fhir.Model.Bundle.EntryComponent entry in bundle.Entry) { Hl7.Fhir.Model.Observation o = (Hl7.Fhir.Model.Observation)entry.Resource; if (FHIRUtils.isCodeMatch(o.Code.Coding, "http://loinc.org", "2345-7")) { bga = bga + ((Hl7.Fhir.Model.SimpleQuantity)o.Value).Value.Value; numglucose++; } else if (FHIRUtils.isCodeMatch(o.Code.Coding, "http://loinc.org", "4548-4")) { la1c = ((Hl7.Fhir.Model.SimpleQuantity)o.Value).Value.Value; dta1c = Convert.ToDateTime(o.Effective.ToString()); } } if (numglucose > 0) { bga = Math.Round(bga / numglucose, 1); ea1c = ((Convert.ToDecimal(46.7) + bga) / Convert.ToDecimal(28.7)); } string a1cmsg = "I don't see any A1C tests on file for you."; string bgmsg = "I don't see any blood glucose tests on file for you."; if (la1c > 0) { a1cmsg = $"Your last A1C test was on " + String.Format("{0:MM/dd/yyyy}", dta1c.Value) + " the result was " + Convert.ToString(la1c) + "% " + (la1c > 6 ? "thats high" : ""); } if (bga > 0) { bgmsg = $"Your blood glucose is averaging {bga} mg/dL " + (bga > 140 ? "looks like it's running high" : (bga < 70 ? "looks like it's running low" : "")); bgmsg = bgmsg + "\n\nYour current estimated A1C is " + Convert.ToString(Math.Round(ea1c, 1)) + (ea1c < 6 ? " great control!" : (ea1c <= 7 ? " doing good for a diabetic!" : " need to work at lowering!")); } await context.PostAsync($"{a1cmsg}\n\n{bgmsg}"); } else if (intent.intent == "DisplayCareplan") { //Call FHIR Server string parm = $"patient={patid}"; var bundle = (Hl7.Fhir.Model.Bundle)client.LoadResource("CarePlan", parm); if (bundle.Entry.Count == 0) { await context.PostAsync($"Sorry, I couldn't find a plan of care for you in the database..."); } else { StringBuilder sb = new StringBuilder(); sb.Append("Your current plan of care addresses the following conditions:\n\n"); Hl7.Fhir.Model.CarePlan cp = (Hl7.Fhir.Model.CarePlan)bundle.Entry[0].Resource; int x = 1; foreach (var cond in cp.Addresses) { sb.Append(x++ + ". " + cond.Display + "\n\n"); } sb.Append("\n\nYou have the following goals:\n\n"); x = 1; foreach (var goal in cp.Goal) { sb.Append(x++ + ". " + goal.Display + "\n\n"); } sb.Append("\n\nYou should:\n\n"); x = 1; foreach (var act in cp.Activity) { if (act.Detail != null) { sb.Append(x++ + ". " + act.Detail.Description + "\n\n"); } else { if (act.Reference != null && act.Reference.Reference.StartsWith("MedicationRequest")) { sb.Append(x++ + ". Take " + act.Reference.Display + " as directed.\n\n"); } } } await context.PostAsync(sb.ToString()); } } else { await context.PostAsync($"Sorry, I didn't understand {activity.Text}..."); } } context.Wait(MessageReceivedAsync); } }
/* Opt-out: Default is for health information of patients to be included automatically, but the patient can opt out completely. * Note: This is an access only policy, it does not prevent updates to the medical record as these could be valid */ public async Task <ProxyProcessResult> Process(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id, string hist, string vid) { FHIRResponse fr = response; if (!req.Method.Equals("GET")) { return(new ProxyProcessResult(true, "", "", fr)); } //Load the consent category code from settings string consent_category = System.Environment.GetEnvironmentVariable("FP-MOD-CONSENT-OPTOUT-CATEGORY"); if (string.IsNullOrEmpty(consent_category)) { log.LogWarning("ConsentOptOutFilter: No value for FP-MOD-CONSENT-OPTOUT-CATEGORY in settings...Filter will not execute"); return(new ProxyProcessResult(true, "", "", fr)); } if (fr == null || fr.Content == null || string.IsNullOrEmpty(fr.Content.ToString())) { log.LogInformation("ConsentOptOutFilter: No FHIR Response found in context...Nothing to filter"); return(new ProxyProcessResult(true, "", "", fr)); } JObject result = JObject.Parse(fr.ToString()); //Administrator is allowed access if (Utils.inServerAccessRole(req, "A")) { return(new ProxyProcessResult(true, "", "", fr)); } ClaimsIdentity ci = (ClaimsIdentity)principal.Identity; string aadten = ci.Tenant(); string name = principal.Identity.Name; //We'll need FHIRClient to check server FHIRClient fhirClient = FHIRClientFactory.getClient(log); var cache = Utils.RedisConnection.GetDatabase(); List <string> associations = ((string)cache.StringGet($"{ASSOCIATION_CACHE_PREFIX}{aadten}-{name}")).DeSerializeList <string>(); if (associations == null) { //Load Associations if not in cache associations = new List <string>(); var table = Utils.getTable(); //Practioner var practitioner = Utils.getLinkEntity(table, "Practitioner", aadten + "-" + name); if (practitioner != null) { associations.Add(practitioner.PartitionKey + "/" + practitioner.LinkedResourceId); //Load organization from PractionerRoles var prs = await fhirClient.LoadResource("PractitionerRole", "practitioner=" + practitioner.LinkedResourceId, true, req.Headers); var ro = (JObject)prs.Content; if (ro.FHIRResourceType().Equals("Bundle")) { JArray entries = (JArray)ro["entry"]; if (!entries.IsNullOrEmpty()) { foreach (JToken tok in entries) { associations.Add(tok["resource"].FHIRReferenceId()); if (!tok["resource"]["organization"].IsNullOrEmpty() && !tok["resource"]["organization"]["reference"].IsNullOrEmpty()) { associations.Add((string)tok["resource"]["organization"]["reference"]); } } } } } //RealtedPerson var related = Utils.getLinkEntity(table, "RelatedPerson", aadten + "-" + name); if (related != null) { associations.Add(related.PartitionKey + "/" + related.LinkedResourceId); } cache.StringSet($"{ASSOCIATION_CACHE_PREFIX}{aadten}-{name}", associations.Distinct().ToList().SerializeList(), TimeSpan.FromMinutes(Utils.GetIntEnvironmentVariable("CONSENT_CACHE_TTL_MINUTES", "60"))); } //Loop through results load any consent records and validate no opt-out default is to allow if (result.FHIRResourceType().Equals("Bundle")) { JArray entries = (JArray)result["entry"]; if (!entries.IsNullOrEmpty()) { foreach (JToken tok in entries) { if (await denyAccess(tok["resource"], fhirClient, cache, associations, consent_category, req.Headers)) { JObject denyObj = new JObject(); denyObj["resourceType"] = tok["resource"].FHIRResourceType(); denyObj["id"] = tok["resource"].FHIRResourceId(); denyObj["text"] = new JObject(); denyObj["text"]["status"] = "generated"; denyObj["text"]["div"] = "<div xmlns =\"http://www.w3.org/1999/xhtml\"><p>Patient has withheld access to this resource</p></div>"; tok["resource"] = denyObj; } } } } else if (!result.FHIRResourceType().Equals("OperationalOutcome")) { if (await denyAccess(result, fhirClient, cache, associations, consent_category, req.Headers)) { fr.Content = Utils.genOOErrResponse("access-denied", $"The patient has withheld access to this resource: {res + (id == null ? "" : "/" + id)}"); fr.StatusCode = System.Net.HttpStatusCode.Unauthorized; return(new ProxyProcessResult(false, "access-denied", "", fr)); } } fr.Content = result.ToString(); return(new ProxyProcessResult(true, "", "", fr)); }
private async Task <bool> denyAccess(JToken resource, FHIRClient fhirclient, IDatabase cache, List <string> associations, string consentcat, IHeaderDictionary headers) { string patientId = null; string rt = resource.FHIRResourceType(); //Check for Patient resource or load patient resource id from subject/patient member if (rt.Equals("Patient")) { patientId = rt + "/" + (string)resource["id"]; } if (patientId == null) { patientId = (string)resource?["subject"]?["reference"]; if (string.IsNullOrEmpty(patientId) || !patientId.StartsWith("Patient")) { patientId = (string)resource?["patient"]?["reference"]; } } //If no patient id present assume not tied to patient do not filter; if (string.IsNullOrEmpty(patientId)) { return(false); } //Load Cache if needed List <string> denyactors = ((string)cache.StringGet($"{PATIENT_DENY_ACTORS_PREFIX}{patientId}")).DeSerializeList <string>(); if (denyactors == null) { //Fetch and Cache Deny access Consent Information var pid = patientId.Split("/")[1]; var consentrecs = await fhirclient.LoadResource("Consent", "patient=" + pid + "&category=" + consentcat, true, headers); var result = (JObject)consentrecs.Content; if (result.FHIRResourceType().Equals("Bundle")) { JArray entries = (JArray)result["entry"]; if (!entries.IsNullOrEmpty()) { denyactors = new List <string>(); foreach (JToken tok in entries) { var r = tok["resource"]; if (!r["provision"].IsNullOrEmpty()) { //Check enforceemnt period if (isEnforced(r["provision"]["period"])) { string type = (string)r["provision"]["type"]; //Load deny provisions only if (type != null && type.Equals("deny")) { //Load actor references to deny JArray actors = (JArray)r["provision"]["actors"]; if (!actors.IsNullOrEmpty()) { foreach (JToken actor in actors) { denyactors.Add((string)actor["reference"]); } } else { //Nobody specified so everybody is denied access this trumps all opt out advise denyactors.Clear(); denyactors.Add("*"); break; } } } } } } else { denyactors = new List <string>(); } cache.StringSet($"{PATIENT_DENY_ACTORS_PREFIX}{patientId}", denyactors.Distinct().ToList().SerializeList(), TimeSpan.FromHours(Utils.GetIntEnvironmentVariable("CONSENT_CACHE_TTL_MINUTES", "60"))); } } //If there is an empty actor array that means there is no deny provision specified so return false to allow access if (denyactors.Count == 0) { return(false); } //It there is a wildcard in first entry then everyone is denied return true if (denyactors.First().Equals("*")) { return(true); } //Check for intersection of denied actors and associations of current user if found access will be denied return(associations.Select(x => x) .Intersect(denyactors) .Any()); }
public async Task <ProxyProcessResult> Process(FHIRResponse response, HttpRequest req, ILogger log, ClaimsPrincipal principal, string res, string id, string hist, string vid) { FHIRResponse fr = response; if (!req.Method.Equals("GET")) { return(new ProxyProcessResult(true, "", "", fr)); } JObject result = (fr == null ? null : JObject.Parse(fr.ToString())); if (fr == null) { log.LogInformation("ParticipantFilter: No FHIR Response found in context"); return(new ProxyProcessResult()); } //Needed Variables ClaimsIdentity ci = (ClaimsIdentity)principal.Identity; string aadten = ci.Tenant(); string name = principal.Identity.Name; bool admin = Utils.inServerAccessRole(req, "A"); List <string> resourceidentities = new List <string>(); List <string> inroles = ci.Roles(); List <string> fhirresourceroles = new List <string>(); fhirresourceroles.AddRange(Environment.GetEnvironmentVariable("FP-PARTICIPANT-ACCESS-ROLES").Split(",")); fhirresourceroles.AddRange(Environment.GetEnvironmentVariable("FP-PATIENT-ACCESS-ROLES").Split(",")); Dictionary <string, bool> porcache = new Dictionary <string, bool>(); FHIRClient fhirClient = FHIRClientFactory.getClient(log); var table = Utils.getTable(); //Load linked Resource Identifiers from linkentities table for each known role the user is in foreach (string r in inroles) { if (fhirresourceroles.Any(r.Equals)) { var entity = Utils.getLinkEntity(table, r, aadten + "-" + name); if (entity != null) { resourceidentities.Add(r + "/" + entity.LinkedResourceId); } } } if (!admin && !ci.IsInFHIRRole(Environment.GetEnvironmentVariable("FP-GLOBAL-ACCESS-ROLES"))) { if (((string)result["resourceType"]).Equals("Bundle")) { JArray entries = (JArray)result["entry"]; if (!entries.IsNullOrEmpty()) { foreach (JToken entry in entries) { if (!await IsAParticipantOrPatient(entry["resource"], fhirClient, resourceidentities, porcache, req.Headers)) { JObject denyObj = new JObject(); denyObj["resourceType"] = entry["resource"].FHIRResourceType(); denyObj["id"] = entry["resource"].FHIRResourceId(); denyObj["text"] = new JObject(); denyObj["text"]["status"] = "generated"; denyObj["text"]["div"] = "<div xmlns =\"http://www.w3.org/1999/xhtml\"><p>You do not have access to data contained in this resource</p></div>"; entry["resource"] = denyObj; } } } } else if (!((string)result["resourceType"]).Equals("OperationalOutcome")) { if (!await IsAParticipantOrPatient(result, fhirClient, resourceidentities, porcache, req.Headers)) { fr.Content = Utils.genOOErrResponse("access-denied", $"You are not an authorized Paticipant in care and cannot access this resource: {res + (id == null ? "" : "/" + id)}"); fr.StatusCode = System.Net.HttpStatusCode.Unauthorized; return(new ProxyProcessResult(false, "access-denied", "", fr)); } } } fr.Content = result.ToString(); return(new ProxyProcessResult(true, "", "", fr)); }
private static async Task <bool> IsAParticipantOrPatient(JToken resource, FHIRClient fhirClient, IEnumerable <string> knownresourceIdentities, Dictionary <string, bool> porcache, IHeaderDictionary auditheaders) { string patientId = null; string encounterId = null; JToken patient = null; JToken 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 (string.IsNullOrEmpty(patientId)) { patientId = (string)resource?["patient"]?["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 = await 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 = await 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 = await 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; 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 = await 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); }
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable <object> result) { var activity = await result as Activity; string msg = activity.Text.Trim().ToLower(); if (msg == "start over" || msg == "exit" || msg == "quit" || msg == "done" || msg == "start again" || msg == "restart" || msg == "leave" || msg == "reset" || msg == "bye" || msg == "goodbye") { context.PrivateConversationData.Clear(); await context.PostAsync($"Ending your session...Dont' forget to delete your conversation for privacy!"); context.Done(""); } else { model.Patient pat = null; // Within the code body set your variable string fhirserver = CloudConfigurationManager.GetSetting("FHIRServerAddress"); string fhirtenant = CloudConfigurationManager.GetSetting("FHIRAADTenant"); string fhirclientid = CloudConfigurationManager.GetSetting("FHIRClientId"); string fhirclientsecret = CloudConfigurationManager.GetSetting("FHIRClientSecret"); FHIRClient fhirclient = new FHIRClient(fhirserver, fhirtenant, fhirclientid, fhirclientsecret); var rslt = (model.Bundle)fhirclient.LoadResource("Patient", "identifier=http://fhirbot.org|" + activity.Text.Trim()); if (rslt != null && rslt.Entry != null && rslt.Entry.Count > 0) { pat = (model.Patient)rslt.Entry.FirstOrDefault().Resource; if (pat != null) { var fbid = pat.Identifier.Single(ident => ident.System == "http://fhirbot.org"); if (fbid != null) { var period = fbid.Period; var now = model.FhirDateTime.Now(); if (period != null && (now > period.EndElement)) { //Use Period Expired remove the token from the patient and update db pat.Identifier.Remove(fbid); fhirclient.SaveResource(pat); pat = null; context.PrivateConversationData.Clear(); } } } } if (pat != null) { // Set BotUserData context.PrivateConversationData.SetValue <string>( "id", pat.Id); context.PrivateConversationData.SetValue <string>( "name", pat.Name[0].Text); context.PrivateConversationData.SetValue <DateTime?>( "sessionstart", DateTime.Now); context.PrivateConversationData.SetValue <string>("bearertoken", fhirclient.BearerToken); context.PrivateConversationData.SetValue <string>("fhirserver", fhirserver); context.Done(pat.Name[0].Text); } else { context.Fail(new InvalidPINException("Not a valid PIN Code")); } } }