Skip to content

Commit 63e5245

Browse files
Resolve conflict, still work to do to resolve TODO
2 parents 4e41897 + d8b898d commit 63e5245

File tree

9 files changed

+117
-240
lines changed

9 files changed

+117
-240
lines changed

apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v1.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,6 @@
128128
"type": "string",
129129
"documentation": "The offset used for result pagination"
130130
},
131-
{
132-
"name": "_has:Coverage",
133-
"type": "token",
134-
"documentation": "Part D coverage type"
135-
},
136131
{
137132
"name": "cursor",
138133
"type": "string",

apps/fhir/bluebutton/tests/fhir_resources/fhir_meta_v2.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,6 @@
136136
"type": "string",
137137
"documentation": "The offset used for result pagination"
138138
},
139-
{
140-
"name": "_has:Coverage",
141-
"type": "token",
142-
"documentation": "Part D coverage type"
143-
},
144139
{
145140
"name": "cursor",
146141
"type": "string",

apps/fhir/bluebutton/tests/test_read_and_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
def get_expected_read_request(version: int):
2727
return {
2828
'method': 'GET',
29-
'url': f'{FHIR_SERVER["FHIR_URL"]}/v{version}/fhir/Patient/{FHIR_ID_V2}/?_format=json',
29+
'url': f'{FHIR_SERVER["FHIR_URL"]}/v{version}/fhir/Patient/{FHIR_ID_V2}/?_format=json&_id={FHIR_ID_V2}',
3030
'headers': {
3131
# 'User-Agent': 'python-requests/2.20.0',
3232
'Accept-Encoding': 'gzip, deflate',

apps/fhir/bluebutton/tests/test_utils.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
crosswalk_patient_id,
1919
get_resourcerouter,
2020
build_oauth_resource,
21-
valid_caller_for_patient_read,
21+
valid_patient_read_or_search_call,
2222
)
2323

2424
ENCODED = settings.ENCODING
@@ -70,16 +70,48 @@ def test_notNone(self):
7070
response = notNone(listing, "number")
7171
self.assertEqual(response, listing)
7272

73-
def test_valid_caller_for_patient_read(self):
74-
result = valid_caller_for_patient_read('PatientId:-20140000008326', '-20140000008326')
73+
def test_valid_patient_read_or_search_call_valid_read_calls(self):
74+
result = valid_patient_read_or_search_call('PatientId:-20140000008329', '-20140000008329', '')
7575
assert result is True
7676

77-
result = valid_caller_for_patient_read('PatientId:-20140000008326', '-20140000008329')
77+
result = valid_patient_read_or_search_call('PatientId:-99140000008329', '-99140000008329', '')
78+
assert result is True
79+
80+
def test_valid_patient_read_or_search_call_invalid_read_calls(self):
81+
result = valid_patient_read_or_search_call('PatientId:-20140000008329', '-99140000008329', '')
82+
assert result is False
83+
84+
result = valid_patient_read_or_search_call('PatientId:-99140000008329', '-20140000008329', '')
7885
assert result is False
7986

80-
# call with no colon in beneficiary_id to make sure
81-
invalid_call = valid_caller_for_patient_read('PatientId-20140000008326', '-20140000008329')
82-
assert invalid_call is False
87+
def test_valid_patient_read_or_search_call_valid_search_calls(self):
88+
result = valid_patient_read_or_search_call('PatientId:-20140000008329', None, '_id=-20140000008329')
89+
assert result is True
90+
91+
result = valid_patient_read_or_search_call(
92+
'PatientId:-99140000008329',
93+
None,
94+
'_lastUpdated=lt2024-06-15&startIndex=0&cursor=0&_id=-99140000008329'
95+
)
96+
assert result is True
97+
98+
result = valid_patient_read_or_search_call(
99+
'PatientId:-99140000008329',
100+
None,
101+
'_id=-99140000008329&_lastUpdated=lt2024-06-15&startIndex=0&cursor=0'
102+
)
103+
assert result is True
104+
105+
def test_valid_patient_read_or_search_call_invalid_search_calls(self):
106+
result = valid_patient_read_or_search_call('PatientId:-20140000008329', None, '_id=-99140000008329')
107+
assert result is False
108+
109+
result = valid_patient_read_or_search_call(
110+
'PatientId:-99140000008329',
111+
None,
112+
'_lastUpdated=lt2024-06-15&startIndex=0&cursor=0&_id=-20140000008329'
113+
)
114+
assert result is False
83115

84116

85117
class BlueButtonUtilSupportedResourceTypeControlTestCase(TestCase):

apps/fhir/bluebutton/utils.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from collections import OrderedDict
1111
from datetime import datetime
1212
from pytz import timezone
13+
from typing import Optional
14+
from urllib.parse import parse_qs
1315

1416
from django.conf import settings
1517
from django.contrib import messages
@@ -749,18 +751,31 @@ def get_patient_by_mbi_hash(mbi_hash, request):
749751
return response.json()
750752

751753

752-
def valid_caller_for_patient_read(beneficiary_id: str, patient_id: str) -> bool:
753-
"""When making a read patient call, we only want to ping BFD if the patient_id
754-
being passed matches the fhir_id (currently v2) associated with the current session
754+
def valid_patient_read_or_search_call(beneficiary_id: str, resource_id: Optional[str], query_param: str) -> bool:
755+
"""Determine if a read or search Patient call is valid, based on what was passed for the resource_id (read call)
756+
or the query_parameter (search call)
755757
756758
Args:
757-
beneficiary_id (str): beneficiary_id that is associated with the current session/
758-
Should have format 'patientId:00000000000000 coming in
759-
patient_id (str): patient_id that is will be passed to BFD
759+
beneficiary_id (str): This comes from the BlueButton-OriginalQuery attribute of the headers for the API call.
760+
Has the format, patientId:{{patientId}}, where patientId comes from the bluebutton_crosswalk table
761+
resource_id (Optional[str]): This will only be populated for read calls, and it is the id being passed to BFD
762+
query_param (str): String for the query parameter being passed for search calls. For read calls this is a blank string
760763
761764
Returns:
762-
bool: If the patient_id matches the beneficiary_id, then return True, else False
765+
bool: Whether or not the call is valid
763766
"""
764-
parts = beneficiary_id.split(':', 1)
765-
beneficiary_comparison_id = parts[1] if len(parts) == 2 else None
766-
return beneficiary_comparison_id == patient_id
767+
bene_split = beneficiary_id.split(':', 1)
768+
beneficiary_id = bene_split[1] if len(bene_split) > 1 else None
769+
# Handles the case where it is a read call, but what is passed does not match the beneficiary_id
770+
# which is constructed using the patient id for the current session in generate_info_headers.
771+
if resource_id and beneficiary_id and resource_id != beneficiary_id:
772+
return False
773+
774+
# Handles the case where it is a search call, but what is passed does not match the beneficiary_id
775+
# so a 404 Not found will be thrown before reaching out to BFD
776+
query_dict = parse_qs(query_param)
777+
passed_identifier = query_dict.get('_id', [None])
778+
if passed_identifier[0] and passed_identifier[0] != beneficiary_id:
779+
return False
780+
781+
return True

apps/fhir/bluebutton/views/generic.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from ..utils import (build_fhir_response,
3434
FhirServerVerify,
3535
get_resourcerouter,
36-
valid_caller_for_patient_read)
36+
valid_patient_read_or_search_call)
3737

3838
logger = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
3939

@@ -152,16 +152,26 @@ def fetch_data(self, request, resource_type, *args, **kwargs):
152152

153153
prepped = s.prepare_request(req)
154154

155-
resource_id = kwargs.get('resource_id')
156-
beneficiary_id = prepped.headers.get('BlueButton-BeneficiaryId')
155+
if resource_type == 'Patient':
156+
query_param = prepped.headers.get('BlueButton-OriginalQuery')
157+
resource_id = kwargs.get('resource_id')
158+
beneficiary_id = prepped.headers.get('BlueButton-BeneficiaryId')
157159

158-
if resource_type == 'Patient' and resource_id and beneficiary_id:
159-
# If it is a patient read request, confirm it is valid for the current user
160-
# If not, throw a 404 before pinging BFD
161-
if not valid_caller_for_patient_read(beneficiary_id, resource_id):
160+
# For patient read and search calls, we need to ensure that what is being passed, either in
161+
# query parameters for search calls, or in the resource_id for read calls, is valid for the
162+
# current session (matching the beneficiary_id). If not, raise a 404 Not found before calling BFD.
163+
if not valid_patient_read_or_search_call(beneficiary_id, resource_id, query_param):
162164
error = NotFound('Not found.')
163165
raise error
164166

167+
# Handle the case where it is a patient search call, but neither _id or identifier were passed
168+
if '_id' not in get_parameters.keys() and 'identifier' not in get_parameters.keys():
169+
# depending on if 4166 is merged first, this will need to be updated
170+
get_parameters['_id'] = request.crosswalk.fhir_id(2)
171+
# Reset the request parameters and the prepped request after adding the missing, but required, _id param
172+
req.params = get_parameters
173+
prepped = s.prepare_request(req)
174+
165175
match self.version:
166176
case Versions.V1:
167177
api_ver_str = 'v1'

apps/fhir/bluebutton/views/search.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ def build_url(self, resource_router, resource_type, *args, **kwargs):
8383
class SearchViewPatient(SearchView):
8484
# Class used for Patient resource search view
8585
required_scopes = ['patient/Patient.read', 'patient/Patient.rs', 'patient/Patient.s']
86+
QUERY_SCHEMA = {
87+
**SearchView.QUERY_SCHEMA,
88+
'_id': str,
89+
'identifier': str
90+
}
8691

8792
def __init__(self, version=1):
8893
super().__init__(version)
@@ -91,7 +96,6 @@ def __init__(self, version=1):
9196
def build_parameters(self, request, *args, **kwargs):
9297
return {
9398
'_format': 'application/json+fhir',
94-
'_id': request.crosswalk.fhir_id(self.version)
9599
}
96100

97101

0 commit comments

Comments
 (0)