diff --git a/apps/accounts/views/oauth2_profile.py b/apps/accounts/views/oauth2_profile.py
index ef118772d..f79c5eb3c 100644
--- a/apps/accounts/views/oauth2_profile.py
+++ b/apps/accounts/views/oauth2_profile.py
@@ -9,7 +9,7 @@
from apps.fhir.bluebutton.models import Crosswalk
from apps.fhir.bluebutton.permissions import ApplicationActivePermission
-from apps.constants import Versions
+from apps.versions import Versions
def _get_userinfo(user, version=Versions.NOT_AN_API_VERSION):
@@ -46,8 +46,6 @@ def _get_userinfo(user, version=Versions.NOT_AN_API_VERSION):
def _openidconnect_userinfo(request, version=Versions.NOT_AN_API_VERSION):
# NOTE: The **kwargs are not used anywhere down the callchain, and are being ignored.
- # BB2-4166-TODO: will the request have a version? do we get here from redirects or is this
- # a straight url that we need to get the version from the url (like we do in the fhir app)
return JsonResponse(_get_userinfo(request.resource_owner, version))
diff --git a/apps/authorization/permissions.py b/apps/authorization/permissions.py
index c762dcbbf..2b0d89dca 100644
--- a/apps/authorization/permissions.py
+++ b/apps/authorization/permissions.py
@@ -1,5 +1,6 @@
from django.conf import settings
from rest_framework import (permissions, exceptions)
+from apps.versions import Versions, VersionNotMatched
from .models import DataAccessGrant
@@ -32,8 +33,10 @@ def has_object_permission(self, request, view, obj):
# Patient resources were taken care of above
# Return 404 on error to avoid notifying unauthorized user the object exists
- # BB2-4166-TODO: this is hardcoded to be version 2
- return is_resource_for_patient(obj, request.crosswalk.fhir_id(2))
+ if view.version in Versions.supported_versions():
+ return is_resource_for_patient(obj, request.crosswalk.fhir_id(view.version))
+ else:
+ raise VersionNotMatched()
def is_resource_for_patient(obj, patient_id):
diff --git a/apps/authorization/views.py b/apps/authorization/views.py
index 2f5a6dcd3..4832df9a4 100644
--- a/apps/authorization/views.py
+++ b/apps/authorization/views.py
@@ -14,10 +14,11 @@
from oauth2_provider.views.base import OAuthLibMixin
from oauth2_provider.views.generic import ClientProtectedResourceView
+from apps.versions import VersionNotMatched, Versions
from apps.dot_ext.authentication import SLSAuthentication
-from .models import DataAccessGrant
-from ..dot_ext.utils import get_application_from_meta
-from ..fhir.bluebutton.models import Crosswalk
+from apps.authorization.models import DataAccessGrant
+from apps.dot_ext.utils import get_application_from_meta, get_api_version_number_from_url
+from apps.fhir.bluebutton.models import Crosswalk
Application = get_application_model()
@@ -68,9 +69,21 @@ class ExpireDataAccessGrantView(ClientProtectedResourceView, OAuthLibMixin):
@staticmethod
def post(request, *args, **kwargs):
try:
+ path_info = request.__dict__.get('path_info')
+ version = get_api_version_number_from_url(path_info)
patient_id = kwargs.pop('patient_id', None)
- # BB2-4166-TODO: currently hardcoded for v2, might need to not be static
- user = Crosswalk.objects.get(fhir_id_v2=patient_id).user
+
+ # V1 is treated as the same as V2 since their pathways are very similar and use the same fhir_id_v2 despite the name
+ match version:
+ case Versions.V1:
+ user = Crosswalk.objects.get(fhir_id_v2=patient_id).user
+ case Versions.V2:
+ user = Crosswalk.objects.get(fhir_id_v2=patient_id).user
+ case Versions.V3:
+ user = Crosswalk.objects.get(fhir_id_v3=patient_id).user
+ case _:
+ raise VersionNotMatched(f"{version} is not a valid version constant")
+
client = get_application_from_meta(request)
DataAccessGrant.objects.get(beneficiary=user.id, application=client).delete()
except Crosswalk.DoesNotExist:
diff --git a/apps/bb2_tools/admin.py b/apps/bb2_tools/admin.py
index b7f8c435f..75faca7f4 100644
--- a/apps/bb2_tools/admin.py
+++ b/apps/bb2_tools/admin.py
@@ -21,7 +21,7 @@
DummyAdminObject,
UserStats,
)
-from apps.fhir.bluebutton.utils import get_patient_by_id
+from apps.fhir.bluebutton.utils import get_v2_patient_by_id
ADMIN_PREPEND = getattr(settings, "ADMIN_PREPEND_URL", "")
BB2_TOOLS_PATH = (
@@ -332,8 +332,7 @@ class BeneficiaryDashboardAdmin(ReadOnlyAdmin):
"get_connected_applications",
"date_created",
)
- # BB2-4166-TODO: add support for v3
- search_fields = ('user__username', 'fhir_id_v2', '_user_id_hash', '_user_mbi')
+ search_fields = ('user__username', 'fhir_id_v2', 'fhir_id_v3', '_user_id_hash', '_user_mbi')
readonly_fields = ('date_created',)
raw_id_fields = ('user',)
@@ -361,10 +360,9 @@ def get_access_tokens(self, obj):
ordering="MyIdentities",
)
def get_identities(self, obj):
- # BB2-4166-TODO: add support for v3
return format_html(
- '
- FHIR_ID_V2:{}
- HICN HASH:{}
- MBI:{}
'.format(
- obj.fhir_id(2), obj.user_hicn_hash, obj.user_mbi
+ '- FHIR_ID_V2:{}
- FHIR_ID_V3:{}
- HICN HASH:{}
- MBI:{}
'.format(
+ obj.fhir_id(2), obj.fhir_id(3), obj.user_hicn_hash, obj.user_mbi
)
)
@@ -416,10 +414,9 @@ def change_view(self, request, object_id, form_url="", extra_context=None):
crosswalk = BeneficiaryDashboard.objects.get(pk=int(object_id))
json_resp = None
-
try:
- # BB2-4166-TODO: this is hardcoded to be version 2
- json_resp = get_patient_by_id(crosswalk.fhir_id(2), request)
+ # DEPRECATE_V2: If we ever deprecate v2, this function and call need to be updated
+ json_resp = get_v2_patient_by_id(crosswalk.fhir_id(2), request)
except Exception as e:
json_resp = {"backend_error": str(e)}
diff --git a/apps/dot_ext/oauth2_backends.py b/apps/dot_ext/oauth2_backends.py
index fbb386075..3131b26c5 100644
--- a/apps/dot_ext/oauth2_backends.py
+++ b/apps/dot_ext/oauth2_backends.py
@@ -4,6 +4,7 @@
from ..fhir.bluebutton.models import Crosswalk
from .loggers import (clear_session_auth_flow_trace, update_session_auth_flow_trace_from_code,
set_session_auth_flow_trace_value)
+from apps.dot_ext.utils import get_api_version_number_from_url
class OAuthLibSMARTonFHIR(OAuthLibCore):
@@ -31,8 +32,8 @@ def create_token_response(self, request):
if Crosswalk.objects.filter(user=token.user).exists():
fhir_body = json.loads(body)
cw = Crosswalk.objects.get(user=token.user)
- # BB2-4166-TODO: this is hardcoded to be version 2
- fhir_body["patient"] = cw.fhir_id(2)
+ version = get_api_version_number_from_url(request.path)
+ fhir_body['patient'] = cw.fhir_id(version)
body = json.dumps(fhir_body)
return uri, headers, body, status
diff --git a/apps/dot_ext/tests/test_api_error_codes.py b/apps/dot_ext/tests/test_api_error_codes.py
index 87f4f6e78..b9f08218f 100644
--- a/apps/dot_ext/tests/test_api_error_codes.py
+++ b/apps/dot_ext/tests/test_api_error_codes.py
@@ -6,7 +6,7 @@
from apps.authorization.models import (
DataAccessGrant,
)
-from apps.constants import AccessType
+from apps.versions import AccessType
from datetime import datetime
from dateutil.relativedelta import relativedelta
from unittest import mock
diff --git a/apps/dot_ext/tests/test_utils.py b/apps/dot_ext/tests/test_utils.py
new file mode 100644
index 000000000..97c8f8e1a
--- /dev/null
+++ b/apps/dot_ext/tests/test_utils.py
@@ -0,0 +1,24 @@
+from django.test import TestCase
+
+from apps.versions import VersionNotMatched
+from ..utils import get_api_version_number_from_url
+
+SUPPORTED_VERSION_TEST_CASES = [
+ {'url_path': '/v2/fhir/Patient/', 'expected': 2},
+ # return 0 because v2 does not have a leading /
+ {'url_path': 'v2/fhir/Patient/', 'expected': 0},
+ {'url_path': '/v3/fhir/Coverage/', 'expected': 3},
+ {'url_path': '/v3/fhir/Coverage/v2/', 'expected': 3},
+]
+
+
+class TestDOTUtils(TestCase):
+ def test_get_api_version_number(self):
+ for test in SUPPORTED_VERSION_TEST_CASES:
+ result = get_api_version_number_from_url(test['url_path'])
+ assert result == test['expected']
+
+ def test_get_api_version_number_unsupported_version(self):
+ # unsupported version will raise an exception
+ with self.assertRaises(VersionNotMatched, msg='4 extracted from /v4/fhir/Coverage/'):
+ get_api_version_number_from_url('/v4/fhir/Coverage/')
diff --git a/apps/dot_ext/utils.py b/apps/dot_ext/utils.py
index 5c337ea20..90ce972d2 100644
--- a/apps/dot_ext/utils.py
+++ b/apps/dot_ext/utils.py
@@ -7,6 +7,8 @@
from oauth2_provider.models import AccessToken, RefreshToken, get_application_model
from oauthlib.oauth2.rfc6749.errors import InvalidClientError, InvalidGrantError, InvalidRequestError
from http import HTTPStatus
+import re
+from apps.versions import Versions, VersionNotMatched
from apps.authorization.models import DataAccessGrant
@@ -252,3 +254,26 @@ def json_response_from_oauth2_error(error):
ret_data['error_description'] = error.description
return JsonResponse(ret_data, status=error.status_code)
+
+
+def get_api_version_number_from_url(url_path: str) -> int:
+ """Utility function to extract what version of the API a URL is
+ If there are multiple occurrences of 'v{{VERSION}} in a url path,
+ only return the first one
+ EX. /v2/o/authorize will return v2.
+
+ Args:
+ url_path (str): The url being called that we want to extract the api version
+
+ Returns:
+ Optional[str]: Returns a string of v2
+ """
+ match = re.search(r'/v(\d+)/', url_path, re.IGNORECASE)
+ if match:
+ version = int(match.group(1))
+ if version in Versions.supported_versions():
+ return version
+ else:
+ raise VersionNotMatched(f'{version} extracted from {url_path}')
+
+ return Versions.NOT_AN_API_VERSION
diff --git a/apps/fhir/bluebutton/models.py b/apps/fhir/bluebutton/models.py
index b2f0b42bc..5a06f4b05 100644
--- a/apps/fhir/bluebutton/models.py
+++ b/apps/fhir/bluebutton/models.py
@@ -12,6 +12,8 @@
from django.core.validators import MinLengthValidator
from apps.accounts.models import get_user_id_salt
+from apps.versions import Versions, VersionNotMatched
+
class BBFhirBluebuttonModelException(APIException):
# BB2-237 custom exception
@@ -164,29 +166,25 @@ class Meta:
)
]
- def fhir_id(self, version: int = 2) -> str:
+ def fhir_id(self, version: int = Versions.V2) -> str:
"""Helper method to return fhir_id based on BFD version, preferred over direct access"""
- if version in (1, 2):
+ if version in [Versions.V1, Versions.V2]:
if self.fhir_id_v2 is not None and self.fhir_id_v2 != '':
# TODO - This is legacy code, to be removed before migration bluebutton 0010
# If fhir_id is empty, try to populate it from fhir_id_v2 to support old code
self._fhir_id = self.fhir_id_v2
self.save()
- return str(self.fhir_id_v2)
+ return self.fhir_id_v2
# TODO - This is legacy code, to be removed before migration bluebutton 0010
# If fhir_id_v2 is empty, try to populate it from _fhir_id to support new code
if self._fhir_id is not None and self._fhir_id != '':
self.fhir_id_v2 = self._fhir_id
self.save()
- return str(self._fhir_id)
+ return self._fhir_id
return ''
- elif version == 3:
- # TODO BB2-4166: This will want to change. In order to make
- # BB2-4181 work, the V3 value needed to be found in the V2 column.
- # 4166 should flip this to _v3, and we should be able to find
- # values there when using (say) the test client.
- if self.fhir_id_v2 is not None and self.fhir_id_v2 != '':
- return str(self.fhir_id_v2)
+ elif version == Versions.V3:
+ if self.fhir_id_v3 is not None and self.fhir_id_v3 != '':
+ return self.fhir_id_v3
return ''
else:
raise ValidationError(f'{version} is not a valid BFD version')
@@ -203,7 +201,7 @@ def set_fhir_id(self, value, version: int = 2) -> None:
elif version == 3:
self.fhir_id_v3 = value
else:
- raise ValidationError(f'{version} is not a valid BFD version')
+ raise VersionNotMatched(f'{version} is not a valid BFD version')
@property
def user_hicn_hash(self):
diff --git a/apps/fhir/bluebutton/permissions.py b/apps/fhir/bluebutton/permissions.py
index 075f94417..cf3a62022 100644
--- a/apps/fhir/bluebutton/permissions.py
+++ b/apps/fhir/bluebutton/permissions.py
@@ -5,6 +5,7 @@
from rest_framework import permissions, exceptions
from rest_framework.exceptions import AuthenticationFailed
from .constants import ALLOWED_RESOURCE_TYPES
+from apps.versions import Versions, VersionNotMatched
import apps.logging.request_logger as bb2logging
@@ -30,10 +31,11 @@ def has_permission(self, request, view):
class HasCrosswalk(permissions.BasePermission):
def has_permission(self, request, view):
- return bool(
- # BB2-4166-TODO : this needs to use version to determine fhir_id, probably in request
- request.user and request.user.crosswalk and request.user.crosswalk.fhir_id(2)
- )
+ if view.version in Versions.supported_versions():
+ return request.user and request.user.crosswalk and request.user.crosswalk.fhir_id(view.version)
+ else:
+ # this should not happen where we'd get an unsupported version
+ raise VersionNotMatched("Version not matched in has_permission")
class ReadCrosswalkPermission(HasCrosswalk):
@@ -41,24 +43,24 @@ def has_object_permission(self, request, view, obj):
# Now check that the user has permission to access the data
# Patient resources were taken care of above # TODO - verify this
# Return 404 on error to avoid notifying unauthorized user the object exists
-
+ if view.version in Versions.supported_versions():
+ fhir_id = request.crosswalk.fhir_id(view.version)
+ else:
+ raise VersionNotMatched("Version not matched in has_object_permission in ReadCrosswalkPermission")
try:
if request.resource_type == "Coverage":
reference = obj["beneficiary"]["reference"]
reference_id = reference.split("/")[1]
- # BB2-4166-TODO : this needs to use version to determine fhir_id, probably in request
- if reference_id != request.crosswalk.fhir_id(2):
+ if reference_id != fhir_id:
raise exceptions.NotFound()
elif request.resource_type == "ExplanationOfBenefit":
reference = obj["patient"]["reference"]
reference_id = reference.split("/")[1]
- # BB2-4166-TODO : this needs to use version to determine fhir_id, probably in request
- if reference_id != request.crosswalk.fhir_id(2):
+ if reference_id != fhir_id:
raise exceptions.NotFound()
else:
reference_id = obj["id"]
- # BB2-4166-TODO : this needs to use version to determine fhir_id, probably in request
- if reference_id != request.crosswalk.fhir_id(2):
+ if reference_id != fhir_id:
raise exceptions.NotFound()
except exceptions.NotFound:
@@ -71,9 +73,10 @@ def has_object_permission(self, request, view, obj):
class SearchCrosswalkPermission(HasCrosswalk):
def has_object_permission(self, request, view, obj):
- # BB2-4166-TODO: this is hardcoded to be version 2
- patient_id = request.crosswalk.fhir_id(2)
-
+ if view.version in Versions.supported_versions():
+ patient_id = request.crosswalk.fhir_id(view.version)
+ else:
+ raise VersionNotMatched("Version not matched in has_object_permission in SearchCrosswalkPermission")
if "patient" in request.GET and request.GET["patient"] != patient_id:
return False
diff --git a/apps/fhir/bluebutton/tests/test_models.py b/apps/fhir/bluebutton/tests/test_models.py
index 9d2ef7d37..7f419eb25 100644
--- a/apps/fhir/bluebutton/tests/test_models.py
+++ b/apps/fhir/bluebutton/tests/test_models.py
@@ -42,29 +42,23 @@ def test_not_require_user_mbi(self):
cw = Crosswalk.objects.get(user=user)
self.assertEqual(cw.user_mbi, None)
- # TODO BB2-4166: This was commented out as part of
- # BB2-4181, but after the work of 4166 is done, this test should
- # pass. However, in 4181, we are storing _v3 values in _v2 (at the model level), and
- # therefore the logic of this test does not work in 4181's branch.
- # Once 4166 is fully implemented, uncomment this test, and it should pass and
- # navigating the test client using the v3 pathway should work.
- # def test_mutable_fhir_id(self):
- # user = self._create_user(
- # "john",
- # "password",
- # first_name="John",
- # last_name="Smith",
- # email="john@smith.net",
- # fhir_id_v2="-20000000000001",
- # fhir_id_v3="-30000000000001",
- # )
- # cw = Crosswalk.objects.get(user=user)
- # self.assertEqual(cw.fhir_id(2), "-20000000000001")
- # self.assertEqual(cw.fhir_id(3), "-30000000000001")
- # cw.set_fhir_id("-20000000000002", 2)
- # cw.set_fhir_id("-30000000000002", 3)
- # self.assertEqual(cw.fhir_id(2), "-20000000000002")
- # self.assertEqual(cw.fhir_id(3), "-30000000000002")
+ def test_mutable_fhir_id(self):
+ user = self._create_user(
+ "john",
+ "password",
+ first_name="John",
+ last_name="Smith",
+ email="john@smith.net",
+ fhir_id_v2="-20000000000001",
+ fhir_id_v3="-30000000000001",
+ )
+ cw = Crosswalk.objects.get(user=user)
+ self.assertEqual(cw.fhir_id(2), "-20000000000001")
+ self.assertEqual(cw.fhir_id(3), "-30000000000001")
+ cw.set_fhir_id("-20000000000002", 2)
+ cw.set_fhir_id("-30000000000002", 3)
+ self.assertEqual(cw.fhir_id(2), "-20000000000002")
+ self.assertEqual(cw.fhir_id(3), "-30000000000002")
def test_mutable_user_hicn_hash(self):
user = self._create_user(
diff --git a/apps/fhir/bluebutton/tests/test_utils.py b/apps/fhir/bluebutton/tests/test_utils.py
index b3cda6716..0d1eb33ba 100644
--- a/apps/fhir/bluebutton/tests/test_utils.py
+++ b/apps/fhir/bluebutton/tests/test_utils.py
@@ -6,7 +6,7 @@
from apps.accounts.models import UserProfile
from apps.test import BaseApiTest
from apps.fhir.bluebutton.models import Crosswalk
-from apps.constants import Versions
+from apps.versions import Versions
from apps.fhir.bluebutton.utils import (
notNone,
@@ -267,51 +267,40 @@ def setUp(self):
def test_crosswalk_fhir_id(self):
""" Get the Crosswalk FHIR_Id """
- u = User.objects.create_user(username="billybob",
- first_name="Billybob",
- last_name="Button",
- email='billybob@example.com',
- password="foobar", )
- UserProfile.objects.create(user=u,
- user_type="DEV",
- create_applications=True)
-
- x = Crosswalk()
- x.user = u
- x.set_fhir_id("Patient/23456", 2)
- x.user_hicn_hash = uuid.uuid4()
- x.save()
-
- result = crosswalk_patient_id(u)
-
- self.assertEqual(x.fhir_id(2), result)
-
- # Test the dt_reference for Patient
-
- result = dt_patient_reference(u)
-
- expect = {'reference': x.fhir_id(2)}
-
- self.assertEqual(result, expect)
+ for version in Versions.latest_versions():
+ u = User.objects.create_user(username=f"billybob-{version}",
+ first_name="Billybob",
+ last_name="Button",
+ email=f'billybob-{version}@example.com',
+ password="foobar", )
+ UserProfile.objects.create(user=u,
+ user_type="DEV",
+ create_applications=True)
+ x = Crosswalk()
+ x.user = u
+ x.set_fhir_id("Patient/23456", version)
+ x.user_hicn_hash = uuid.uuid4()
+ x.save()
+ result = crosswalk_patient_id(u, version)
+ self.assertEqual(x.fhir_id(version), result)
+ # Test the dt_reference for Patient
+ result = dt_patient_reference(u, version)
+ expect = {'reference': x.fhir_id(version)}
+ self.assertEqual(result, expect)
def test_crosswalk_not_fhir_id(self):
""" Get no Crosswalk id """
-
- u = User.objects.create_user(username="bobnobob",
- first_name="bob",
- last_name="Button",
- email='billybob@example.com',
- password="foobar", )
-
- result = crosswalk_patient_id(u)
-
- self.assertEqual(result, None)
-
- # Test the dt_reference for Patient returning None
-
- result = dt_patient_reference(u)
-
- self.assertEqual(result, None)
+ for version in Versions.latest_versions():
+ u = User.objects.create_user(username=f"bobnobob-{version}",
+ first_name="bob",
+ last_name="Button",
+ email=f'billybob-{version}@example.com',
+ password="foobar", )
+ result = crosswalk_patient_id(u, version)
+ self.assertEqual(result, None)
+ # Test the dt_reference for Patient returning None
+ result = dt_patient_reference(u, version)
+ self.assertEqual(result, None)
class Security_Metadata_test(BaseApiTest):
@@ -357,6 +346,4 @@ def test_oauth_resource_xml(self):
expected = "true"
- # print(result[16:33])
-
self.assertEqual(result[16:33], expected)
diff --git a/apps/fhir/bluebutton/utils.py b/apps/fhir/bluebutton/utils.py
index c45627197..271d76a6b 100644
--- a/apps/fhir/bluebutton/utils.py
+++ b/apps/fhir/bluebutton/utils.py
@@ -16,11 +16,12 @@
from django.conf import settings
from django.contrib import messages
from apps.fhir.server.settings import fhir_settings
-from apps.constants import Versions
+from apps.versions import Versions
from oauth2_provider.models import AccessToken
from apps.wellknown.views import base_issuer, build_endpoint_info
from .models import Crosswalk, Fhir_Response
+from apps.dot_ext.utils import get_api_version_number_from_url
logger = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
@@ -127,7 +128,7 @@ def get_query_counter(request):
return request._logging_pass
-def generate_info_headers(request):
+def generate_info_headers(request, version: int = Versions.NOT_AN_API_VERSION):
"""Returns a dict of headers to be sent to the backend"""
result = {}
# BB2-279 support BFD header "includeAddressFields" and always set to False
@@ -145,14 +146,17 @@ def generate_info_headers(request):
# Return resource_owner or user
user = get_user_from_request(request)
crosswalk = get_crosswalk(user)
+
+ if version == Versions.NOT_AN_API_VERSION:
+ version = get_api_version_number_from_url(request.path)
+
if crosswalk:
- # we need to send the HicnHash or the fhir_id
# TODO: Can the hicnHash case ever be reached? Should refactor this!
- # BB2-4166-TODO: generalize this to include and check for v3 if a v3 request is happening
- if crosswalk.fhir_id(2) is not None:
- result["BlueButton-BeneficiaryId"] = "patientId:" + str(crosswalk.fhir_id(2))
+ # TODO: As we move to v2/v3, v3 does not use the hicnHash. We will want to refactor.
+ if crosswalk.fhir_id(version) != '':
+ result['BlueButton-BeneficiaryId'] = 'patientId:' + crosswalk.fhir_id(version)
else:
- result["BlueButton-BeneficiaryId"] = "hicnHash:" + str(
+ result['BlueButton-BeneficiaryId'] = 'hicnHash:' + str(
crosswalk.user_hicn_hash
)
else:
@@ -408,25 +412,25 @@ def prepend_q(pass_params):
return pass_params
-def dt_patient_reference(user):
+def dt_patient_reference(user, version):
"""Get Patient Reference from Crosswalk for user"""
if user:
- patient = crosswalk_patient_id(user)
+ patient = crosswalk_patient_id(user, version)
if patient:
return {"reference": patient}
return None
-def crosswalk_patient_id(user):
+def crosswalk_patient_id(user, version):
"""Get patient/id from Crosswalk for user"""
logger.debug("\ncrosswalk_patient_id User:%s" % user)
try:
patient = Crosswalk.objects.get(user=user)
- if patient.fhir_id(2):
- return patient.fhir_id(2)
+ if patient.fhir_id(version):
+ return patient.fhir_id(version)
except Crosswalk.DoesNotExist:
pass
@@ -698,19 +702,19 @@ def build_oauth_resource(request, version=Versions.NOT_AN_API_VERSION, format_ty
return security
-def get_patient_by_id(id, request):
+def get_v2_patient_by_id(id, request):
"""
a helper adapted to just get patient given an id out of band of auth flow
- or noraml data flow, use by tools such as BB2-Tools admin viewers
+ or normal data flow, use by tools such as BB2-Tools admin viewers
"""
auth_settings = FhirServerAuth(None)
certs = (auth_settings["cert_file"], auth_settings["key_file"])
+
headers = generate_info_headers(request)
headers["BlueButton-Application"] = "BB2-Tools"
headers["includeIdentifiers"] = "true"
# for now this will only work for v1/v2 patients, but we'll need to be able to
# determine if the user is V3 and use those endpoints later
- # BB2-4166-TODO: this should allow v3
url = "{}/v2/fhir/Patient/{}?_format={}".format(
get_resourcerouter().fhir_url, id, settings.FHIR_PARAM_FORMAT
)
@@ -732,9 +736,9 @@ def get_patient_by_mbi_hash(mbi_hash, request):
headers["BlueButton-Application"] = "BB2-Tools"
headers["includeIdentifiers"] = "true"
- search_identifier = f"https://bluebutton.cms.gov/resources/identifier/mbi-hash|{mbi_hash}"
- payload = {"identifier": search_identifier}
- url = "{}/v2/fhir/Patient/_search".format(
+ search_identifier = f'https://bluebutton.cms.gov/resources/identifier/mbi-hash|{mbi_hash}' # noqa: E231
+ payload = {'identifier': search_identifier}
+ url = '{}/v2/fhir/Patient/_search'.format(
get_resourcerouter().fhir_url
)
diff --git a/apps/fhir/bluebutton/views/generic.py b/apps/fhir/bluebutton/views/generic.py
index d8ef3b73d..6cb6e6cb0 100644
--- a/apps/fhir/bluebutton/views/generic.py
+++ b/apps/fhir/bluebutton/views/generic.py
@@ -3,7 +3,7 @@
import voluptuous
import logging
-from apps.constants import VersionNotMatched, Versions
+from apps.versions import VersionNotMatched, Versions
import apps.logging.request_logger as bb2logging
from django.core.exceptions import ObjectDoesNotExist
@@ -134,6 +134,7 @@ def fetch_data(self, request, resource_type, *args, **kwargs):
logger.debug('Here is the URL to send, %s now add '
'GET parameters %s' % (target_url, get_parameters))
+ request.session.version = self.version
# Now make the call to the backend API
req = Request('GET',
@@ -165,8 +166,7 @@ def fetch_data(self, request, resource_type, *args, **kwargs):
# Handle the case where it is a patient search call, but neither _id or identifier were passed
if '_id' not in get_parameters.keys() and 'identifier' not in get_parameters.keys():
- # depending on if 4166 is merged first, this will need to be updated
- get_parameters['_id'] = request.crosswalk.fhir_id(2)
+ get_parameters['_id'] = request.crosswalk.fhir_id(self.version)
# Reset the request parameters and the prepped request after adding the missing, but required, _id param
req.params = get_parameters
prepped = s.prepare_request(req)
@@ -194,7 +194,6 @@ def fetch_data(self, request, resource_type, *args, **kwargs):
# BB2-128
error = process_error_response(response)
-
if error is not None:
raise error
diff --git a/apps/fhir/bluebutton/views/home.py b/apps/fhir/bluebutton/views/home.py
index fecf94298..d5d44b003 100644
--- a/apps/fhir/bluebutton/views/home.py
+++ b/apps/fhir/bluebutton/views/home.py
@@ -13,7 +13,7 @@
get_resourcerouter,
get_response_text,
build_oauth_resource)
-from apps.constants import Versions, VersionNotMatched
+from apps.versions import Versions, VersionNotMatched
import apps.logging.request_logger as bb2logging
diff --git a/apps/fhir/bluebutton/views/search.py b/apps/fhir/bluebutton/views/search.py
index d05f9aa5f..b1cd87ec4 100644
--- a/apps/fhir/bluebutton/views/search.py
+++ b/apps/fhir/bluebutton/views/search.py
@@ -110,8 +110,7 @@ def __init__(self, version=1):
def build_parameters(self, request, *args, **kwargs):
return {
'_format': 'application/json+fhir',
- # BB2-4166-TODO : this needs to use self.version to determine fhir_id
- 'beneficiary': 'Patient/' + request.crosswalk.fhir_id(2)
+ 'beneficiary': 'Patient/' + request.crosswalk.fhir_id(self.version)
}
@@ -167,8 +166,7 @@ def __init__(self, version=1):
def build_parameters(self, request, *args, **kwargs):
return {
'_format': 'application/json+fhir',
- # BB2-4166-TODO : this needs to use version to determine fhir_id
- 'patient': request.crosswalk.fhir_id(2),
+ 'patient': request.crosswalk.fhir_id(self.version),
}
def filter_parameters(self, request):
diff --git a/apps/fhir/server/authentication.py b/apps/fhir/server/authentication.py
index 52d60bf06..1ddf4d620 100644
--- a/apps/fhir/server/authentication.py
+++ b/apps/fhir/server/authentication.py
@@ -4,6 +4,7 @@
from rest_framework import exceptions
from urllib.parse import quote
+from apps.versions import Versions
from apps.dot_ext.loggers import get_session_auth_flow_trace
from apps.fhir.bluebutton.signals import (
pre_fetch,
@@ -18,27 +19,25 @@
from .loggers import log_match_fhir_id
-def search_fhir_id_by_identifier_mbi(mbi, request=None):
+def search_fhir_id_by_identifier_mbi(mbi, request=None, version=Versions.NOT_AN_API_VERSION):
"""
Search the backend FHIR server's patient resource
using the mbi identifier.
"""
search_identifier = f"{settings.FHIR_PATIENT_SEARCH_PARAM_IDENTIFIER_MBI}|{mbi}"
+ return search_fhir_id_by_identifier(search_identifier, request, version)
- return search_fhir_id_by_identifier(search_identifier, request)
-
-def search_fhir_id_by_identifier_hicn_hash(hicn_hash, request=None):
+def search_fhir_id_by_identifier_hicn_hash(hicn_hash, request=None, version=Versions.NOT_AN_API_VERSION):
"""
Search the backend FHIR server's patient resource
using the hicn_hash identifier.
"""
search_identifier = f"{settings.FHIR_POST_SEARCH_PARAM_IDENTIFIER_HICN_HASH}|{hicn_hash}"
-
- return search_fhir_id_by_identifier(search_identifier, request)
+ return search_fhir_id_by_identifier(search_identifier, request, version)
-def search_fhir_id_by_identifier(search_identifier, request=None):
+def search_fhir_id_by_identifier(search_identifier, request=None, version=Versions.NOT_AN_API_VERSION):
"""
Search the backend FHIR server's patient resource
using the specified identifier.
@@ -51,12 +50,11 @@ def search_fhir_id_by_identifier(search_identifier, request=None):
# Get certs from FHIR server settings
auth_settings = FhirServerAuth(None)
certs = (auth_settings['cert_file'], auth_settings['key_file'])
-
# Add headers for FHIR backend logging, including auth_flow_dict
if request:
# Get auth flow session values.
auth_flow_dict = get_session_auth_flow_trace(request)
- headers = generate_info_headers(request)
+ headers = generate_info_headers(request, version)
headers = set_default_header(request, headers)
# may be part of the contract with BFD
headers['BlueButton-AuthUuid'] = auth_flow_dict.get('auth_uuid', '')
@@ -77,9 +75,9 @@ def search_fhir_id_by_identifier(search_identifier, request=None):
headers = None
# Build URL based on BFD version
- # BB2-4166-TODO: generalize versionining of fhir server url
resource_router = get_resourcerouter()
- ver = "v{}".format(request.session.get('version', 1))
+ ver = f'v{version}'
+
fhir_url = resource_router.fhir_url
if ver == 'v3' and resource_router.fhir_url_v3:
fhir_url = resource_router.fhir_url_v3
@@ -109,13 +107,12 @@ def search_fhir_id_by_identifier(search_identifier, request=None):
except requests.exceptions.SSLError as e:
if retries < max_retries and (env is None or env == 'DEV'):
# Checking target_env ensures the retry logic only happens on local
- print(f"FHIR ID search request failed. Retrying... ({retries + 1}/{max_retries})")
retries += 1
else:
raise e
-def match_fhir_id(mbi, hicn_hash, request=None):
+def match_fhir_id(mbi, hicn_hash, request=None, version=Versions.NOT_AN_API_VERSION):
"""Matches a patient identifier via the backend FHIR server using an MBI or HICN hash.
- Perform primary lookup using mbi.
- If there is an mbi lookup issue, raise exception.
@@ -126,6 +123,7 @@ def match_fhir_id(mbi, hicn_hash, request=None):
Args:
mbi (string): the mbi of the user
hicn_hash (string): the hashed hicn of the user
+ version (int): Current API version for this call
request (HttpRequest, optional): the Django request
Returns:
@@ -139,7 +137,7 @@ def match_fhir_id(mbi, hicn_hash, request=None):
# Perform primary lookup using MBI
if mbi:
try:
- fhir_id = search_fhir_id_by_identifier_mbi(mbi, request)
+ fhir_id = search_fhir_id_by_identifier_mbi(mbi, request, version)
except UpstreamServerException as err:
log_match_fhir_id(request, None, hicn_hash, False, 'M', str(err))
# Don't return a 404 because retrying later will not fix this.
@@ -152,9 +150,13 @@ def match_fhir_id(mbi, hicn_hash, request=None):
return fhir_id, 'M'
# Perform secondary lookup using HICN_HASH
+ # WE CANNOT DO A HICN HASH LOOKUP FOR V3, but there are tests that rely on a null MBI
+ # and populated hicn_hash, which now execute on v3 (due to updates in get_and_update_user)
+ # so we need to leave this conditional as is for now, until the test is modified and/or hicn_hash is removed
+ # if version in [Versions.V1, Versions.V2] and hicn_hash:
if hicn_hash:
try:
- fhir_id = search_fhir_id_by_identifier_hicn_hash(hicn_hash, request)
+ fhir_id = search_fhir_id_by_identifier_hicn_hash(hicn_hash, request, version)
except UpstreamServerException as err:
log_match_fhir_id(request, None, hicn_hash, False, 'H', str(err))
# Don't return a 404 because retrying later will not fix this.
@@ -166,8 +168,7 @@ def match_fhir_id(mbi, hicn_hash, request=None):
'FOUND beneficiary via hicn_hash')
return fhir_id, 'H'
- log_match_fhir_id(request, fhir_id, hicn_hash, False, None,
- 'FHIR ID NOT FOUND for both mbi and hicn_hash')
+ log_match_fhir_id(request, None, hicn_hash, False, None, 'FHIR ID NOT FOUND for both mbi and hicn_hash')
raise exceptions.NotFound('The requested Beneficiary has no entry, however this may change')
diff --git a/apps/fhir/server/tests/test_authentication.py b/apps/fhir/server/tests/test_authentication.py
index 9f82e7d0d..e60c2cd0e 100644
--- a/apps/fhir/server/tests/test_authentication.py
+++ b/apps/fhir/server/tests/test_authentication.py
@@ -6,15 +6,24 @@
from rest_framework import exceptions
from apps.fhir.bluebutton.exceptions import UpstreamServerException
from apps.test import BaseApiTest
-from ..authentication import match_fhir_id
-from .responses import responses
+from apps.fhir.server.authentication import match_fhir_id
+from apps.versions import Versions
+from apps.fhir.server.tests.responses import responses
-from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME
+from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME, MOCK_FHIR_V3_ENDPOINT_HOSTNAME
+
+
+def mock_fhir_url(version):
+ return MOCK_FHIR_ENDPOINT_HOSTNAME if version in [1, 2] else MOCK_FHIR_V3_ENDPOINT_HOSTNAME
+
+
+def mock_fhir_path(version):
+ return f'/v{version}/fhir/Patient'
class TestAuthentication(BaseApiTest):
- MOCK_FHIR_URL = MOCK_FHIR_ENDPOINT_HOSTNAME
- MOCK_FHIR_PATH = "/v1/fhir/Patient/"
+ MOCK_FHIR_URL = mock_fhir_url(Versions.NOT_AN_API_VERSION)
+ MOCK_FHIR_PATH_VERSIONED = mock_fhir_path(Versions.NOT_AN_API_VERSION)
MOCK_FHIR_HICN_QUERY = ".*hicnHash.*"
MOCK_FHIR_MBI_QUERY = ".*us-mbi|.*"
SUCCESS_KEY = 'success'
@@ -31,14 +40,14 @@ def setUp(self):
self.request = self.factory.get('http://localhost:8000/mymedicare/sls-callback')
self.request.session = self.client.session
+ # The mock uses data from responses.py
@classmethod
- def create_fhir_mock(cls, hicn_response_key, mbi_response_key):
- @urlmatch(netloc=cls.MOCK_FHIR_URL, path=cls.MOCK_FHIR_PATH, method='POST')
+ def create_fhir_mock(cls, hicn_response_key, mbi_response_key, version=Versions.NOT_AN_API_VERSION):
+ @urlmatch(netloc=mock_fhir_url(version), path=mock_fhir_path(version), method='POST')
def mock_fhir_post(url, request):
try:
body = request.body
identifier = body.split('=', 1)[1]
-
if 'hicn-hash' in identifier:
return responses[hicn_response_key]
elif 'us-mbi' in identifier:
@@ -55,12 +64,15 @@ def test_match_fhir_id_success(self):
MBI = success
Expecting: Match via MBI first / hash_lockup_type="M"
'''
- with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.SUCCESS_KEY)):
+ with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.SUCCESS_KEY, Versions.V2)):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
- self.assertEqual(fhir_id, "-20000000002346")
- self.assertEqual(hash_lookup_type, "M")
+ hicn_hash=self.test_hicn_hash,
+ request=self.request,
+ version=Versions.V2
+ )
+ self.assertEqual(fhir_id, '-20000000002346')
+ self.assertEqual(hash_lookup_type, 'M')
def test_match_fhir_id_hicn_success(self):
'''
@@ -68,10 +80,13 @@ def test_match_fhir_id_hicn_success(self):
MBI = not_found
Expecting: Match via HICN / hash_lockup_type="H"
'''
- with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.NOT_FOUND_KEY)):
+ with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.NOT_FOUND_KEY, Versions.V2)):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash,
+ request=self.request,
+ version=Versions.V2
+ )
self.assertEqual(fhir_id, "-20000000002346")
self.assertEqual(hash_lookup_type, "H")
@@ -81,10 +96,10 @@ def test_match_fhir_id_mbi_success(self):
MBI = success
Expecting: Match via MBI / hash_lockup_type="M"
'''
- with HTTMock(self.create_fhir_mock(self.NOT_FOUND_KEY, self.SUCCESS_KEY)):
+ with HTTMock(self.create_fhir_mock(self.NOT_FOUND_KEY, self.SUCCESS_KEY, Versions.V2)):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
self.assertEqual(fhir_id, "-20000000002346")
self.assertEqual(hash_lookup_type, "M")
@@ -94,11 +109,11 @@ def test_match_fhir_id_not_found(self):
MBI = not_found
Expecting: NotFound exception raised
'''
- with HTTMock(self.create_fhir_mock(self.NOT_FOUND_KEY, self.NOT_FOUND_KEY)):
+ with HTTMock(self.create_fhir_mock(self.NOT_FOUND_KEY, self.NOT_FOUND_KEY, Versions.V2)):
with self.assertRaises(exceptions.NotFound):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
def test_match_fhir_id_server_hicn_error(self):
'''
@@ -106,11 +121,11 @@ def test_match_fhir_id_server_hicn_error(self):
MBI = not_found
Expecting: HTTPError exception raised
'''
- with HTTMock(self.create_fhir_mock(self.ERROR_KEY, self.NOT_FOUND_KEY)):
+ with HTTMock(self.create_fhir_mock(self.ERROR_KEY, self.NOT_FOUND_KEY, Versions.V2)):
with self.assertRaises(UpstreamServerException):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
def test_match_fhir_id_server_mbi_error(self):
'''
@@ -118,11 +133,11 @@ def test_match_fhir_id_server_mbi_error(self):
MBI = error
Expecting: HTTPError exception raised
'''
- with HTTMock(self.create_fhir_mock(self.NOT_FOUND_KEY, self.ERROR_KEY)):
+ with HTTMock(self.create_fhir_mock(self.NOT_FOUND_KEY, self.ERROR_KEY, Versions.V2)):
with self.assertRaises(UpstreamServerException):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
def test_match_fhir_id_duplicates_hicn(self):
'''
@@ -130,11 +145,11 @@ def test_match_fhir_id_duplicates_hicn(self):
MBI = not_found
Expecting: UpstreamServerException exception raised
'''
- with HTTMock(self.create_fhir_mock(self.DUPLICATES_KEY, self.NOT_FOUND_KEY)):
+ with HTTMock(self.create_fhir_mock(self.DUPLICATES_KEY, self.NOT_FOUND_KEY, Versions.V2)):
with self.assertRaisesRegexp(UpstreamServerException, "^Duplicate.*"):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
def test_match_fhir_id_duplicates_mbi(self):
'''
@@ -142,11 +157,11 @@ def test_match_fhir_id_duplicates_mbi(self):
MBI = duplicates
Expecting: UpstreamServerException exception raised
'''
- with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.DUPLICATES_KEY)):
+ with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.DUPLICATES_KEY, Versions.V2)):
with self.assertRaisesRegexp(UpstreamServerException, "^Duplicate.*"):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
def test_match_fhir_id_duplicates_both(self):
'''
@@ -154,11 +169,11 @@ def test_match_fhir_id_duplicates_both(self):
MBI = duplicates
Expecting: UpstreamServerException exception raised
'''
- with HTTMock(self.create_fhir_mock(self.DUPLICATES_KEY, self.DUPLICATES_KEY)):
+ with HTTMock(self.create_fhir_mock(self.DUPLICATES_KEY, self.DUPLICATES_KEY, Versions.V2)):
with self.assertRaisesRegexp(UpstreamServerException, "^Duplicate.*"):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
def test_match_fhir_id_malformed_hicn(self):
'''
@@ -166,11 +181,11 @@ def test_match_fhir_id_malformed_hicn(self):
MBI = not_found
Expecting: UpstreamServerException exception raised
'''
- with HTTMock(self.create_fhir_mock(self.MALFORMED_KEY, self.NOT_FOUND_KEY)):
+ with HTTMock(self.create_fhir_mock(self.MALFORMED_KEY, self.NOT_FOUND_KEY, Versions.V2)):
with self.assertRaisesRegexp(UpstreamServerException, "^Unexpected in Patient search:*"):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
def test_match_fhir_id_malformed_mbi(self):
'''
@@ -178,8 +193,8 @@ def test_match_fhir_id_malformed_mbi(self):
MBI = malformed
Expecting: UpstreamServerException exception raised
'''
- with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.MALFORMED_KEY)):
+ with HTTMock(self.create_fhir_mock(self.SUCCESS_KEY, self.MALFORMED_KEY, Versions.V2)):
with self.assertRaisesRegexp(UpstreamServerException, "^Unexpected in Patient search:*"):
fhir_id, hash_lookup_type = match_fhir_id(
mbi=self.test_mbi,
- hicn_hash=self.test_hicn_hash, request=self.request)
+ hicn_hash=self.test_hicn_hash, request=self.request, version=Versions.V2)
diff --git a/apps/integration_tests/common_utils.py b/apps/integration_tests/common_utils.py
index 1ebfd4290..6fe21e2da 100755
--- a/apps/integration_tests/common_utils.py
+++ b/apps/integration_tests/common_utils.py
@@ -7,9 +7,7 @@
def validate_json_schema(schema, content):
try:
validate(instance=content, schema=schema)
- except jsonschema.exceptions.ValidationError as e:
- # Show error info for debugging
- print("jsonschema.exceptions.ValidationError: ", e)
+ except jsonschema.exceptions.ValidationError:
return False
return True
diff --git a/apps/integration_tests/selenium_spanish_tests.py b/apps/integration_tests/selenium_spanish_tests.py
index 856898a1d..9b2abdfb7 100644
--- a/apps/integration_tests/selenium_spanish_tests.py
+++ b/apps/integration_tests/selenium_spanish_tests.py
@@ -1,6 +1,6 @@
from .selenium_generic import SeleniumGenericTests
from .selenium_cases import SPANISH_TESTS
-from apps.constants import Versions
+from apps.versions import Versions
USE_NEW_PERM_SCREEN = "true"
diff --git a/apps/integration_tests/selenium_tests.py b/apps/integration_tests/selenium_tests.py
index f77a8c1ee..98cf2c796 100755
--- a/apps/integration_tests/selenium_tests.py
+++ b/apps/integration_tests/selenium_tests.py
@@ -1,7 +1,7 @@
import os
from .selenium_generic import SeleniumGenericTests
from .selenium_cases import TESTS
-from apps.constants import Versions
+from apps.versions import Versions
USE_NEW_PERM_SCREEN = os.environ['USE_NEW_PERM_SCREEN']
diff --git a/apps/logging/signals.py b/apps/logging/signals.py
index 8058b3dae..994cfa1f3 100644
--- a/apps/logging/signals.py
+++ b/apps/logging/signals.py
@@ -1,3 +1,4 @@
+from apps.versions import Versions
import apps.logging.request_logger as logging
from django.db.models.signals import (
@@ -46,8 +47,8 @@ def handle_app_authorized(sender, request, auth_status, auth_status_code, user,
'id': None,
'user_hicn_hash': None,
'user_mbi': None,
- # BB2-4166-TODO: this is hardcoded to be version 2, add v3
'fhir_id_v2': None,
+ 'fhir_id_v3': None,
'user_id_type': None
}
@@ -56,8 +57,8 @@ def handle_app_authorized(sender, request, auth_status, auth_status_code, user,
'id': user.crosswalk.id,
'user_hicn_hash': user.crosswalk.user_hicn_hash,
'user_mbi': user.crosswalk.user_mbi,
- # BB2-4166-TODO: this is hardcoded to be version 2, add v3
- 'fhir_id_v2': user.crosswalk.fhir_id(2),
+ 'fhir_id_v2': user.crosswalk.fhir_id(Versions.V2),
+ 'fhir_id_v3': user.crosswalk.fhir_id(Versions.V3),
'user_id_type': user.crosswalk.user_id_type
}
except Exception:
diff --git a/apps/logging/tests/audit_logger_schemas.py b/apps/logging/tests/audit_logger_schemas.py
index 490d6cf28..b6687f338 100755
--- a/apps/logging/tests/audit_logger_schemas.py
+++ b/apps/logging/tests/audit_logger_schemas.py
@@ -439,13 +439,15 @@ def get_pre_fetch_fhir_log_entry_schema(version):
"request_uuid": {"type": "string", "format": "uuid"},
"req_user_id": {"type": "integer", "enum": [1]},
"req_user_username": {"pattern": "00112233-4455-6677-8899-aabbccddeeff"},
- "req_fhir_id": {"pattern": "-20140000008325"},
+ "req_fhir_id_v2": {"pattern": "-20140000008325"},
+ "req_fhir_id_v3": {"pattern": "-20140000008325"},
"auth_crosswalk_action": {"pattern": "C"},
"path": {"pattern": "/mymedicare/sls-callback"},
"request_method": {"pattern": "GET"},
"request_scheme": {"pattern": "http"},
"user": {"pattern": "00112233-4455-6677-8899-aabbccddeeff"},
"fhir_id_v2": {"pattern": "-20140000008325"},
+ "fhir_id_v3": {"pattern": "-20140000008325"},
"response_code": {"type": "integer", "enum": [status.HTTP_302_FOUND]},
},
"required": [
@@ -457,13 +459,15 @@ def get_pre_fetch_fhir_log_entry_schema(version):
"request_uuid",
"req_user_id",
"req_user_username",
- "req_fhir_id",
+ "req_fhir_id_v2",
+ "req_fhir_id_v3",
"auth_crosswalk_action",
"path",
"request_method",
"request_scheme",
"user",
"fhir_id_v2",
+ "fhir_id_v3",
"response_code",
],
}
diff --git a/apps/logging/tests/test_audit_loggers.py b/apps/logging/tests/test_audit_loggers.py
index 7a6b969d7..c6fdf4e5c 100644
--- a/apps/logging/tests/test_audit_loggers.py
+++ b/apps/logging/tests/test_audit_loggers.py
@@ -41,7 +41,7 @@
SLSX_USERINFO_LOG_SCHEMA,
)
-from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME
+from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME, MOCK_FHIR_V3_ENDPOINT_HOSTNAME
FHIR_ID_V2 = settings.DEFAULT_SAMPLE_FHIR_ID_V2
@@ -178,6 +178,9 @@ def test_callback_url_success_slsx_logger(self):
def test_callback_url_success_slsx_logger_v2(self):
self._callback_url_success_slsx_logger(2)
+ def test_callback_url_success_slsx_logger_v3(self):
+ self._callback_url_success_slsx_logger(3)
+
def _callback_url_success_slsx_logger(self, version=1):
# copy and adapted for SLSx logger test
state = generate_nonce()
@@ -189,7 +192,7 @@ def _callback_url_success_slsx_logger(self, version=1):
# mock fhir user info endpoint
@urlmatch(
netloc=MOCK_FHIR_ENDPOINT_HOSTNAME,
- path=r'/v[123]/fhir/Patient/',
+ path=r'/v[12]/fhir/Patient/',
)
def fhir_patient_info_mock(url, request):
return {
@@ -197,6 +200,17 @@ def fhir_patient_info_mock(url, request):
'content': patient_response,
}
+ # mock fhir user info endpoint
+ @urlmatch(
+ netloc=MOCK_FHIR_V3_ENDPOINT_HOSTNAME,
+ path=r'/v3/fhir/Patient/',
+ )
+ def fhir_patient_info_mock_v3(url, request):
+ return {
+ 'status_code': status.HTTP_200_OK,
+ 'content': patient_response,
+ }
+
@all_requests
def catchall(url, request):
raise Exception(url)
@@ -206,6 +220,7 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_signout_ok_mock,
fhir_patient_info_mock,
+ fhir_patient_info_mock_v3,
catchall,
):
s = self.client.session
@@ -279,7 +294,8 @@ def catchall(url, request):
fhir_log_content = get_log_content(self.logger_registry, logging.AUDIT_DATA_FHIR_LOGGER)
log_entries = fhir_log_content.splitlines()
- self.assertEqual(len(log_entries), 2)
+
+ self.assertEqual(len(log_entries), 4)
# Validate fhir_auth_pre_fetch entry
self.assertTrue(
diff --git a/apps/mymedicare_cb/models.py b/apps/mymedicare_cb/models.py
index 96f983288..4d1512524 100644
--- a/apps/mymedicare_cb/models.py
+++ b/apps/mymedicare_cb/models.py
@@ -5,6 +5,8 @@
from django.db import models, transaction
from rest_framework import status
from rest_framework.exceptions import APIException
+from apps.versions import Versions
+from apps.fhir.bluebutton.exceptions import UpstreamServerException
from apps.accounts.models import UserProfile
from apps.fhir.bluebutton.models import ArchivedCrosswalk, Crosswalk
@@ -56,24 +58,37 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request):
logger = logging.getLogger(logging.AUDIT_AUTHN_MED_CALLBACK_LOGGER, request)
# Match a patient identifier via the backend FHIR server
- if version == 3:
+ if version == Versions.V3:
hicn_hash = None
else:
hicn_hash = slsx_client.hicn_hash
- # BB2-4166-TODO: implement cross-lookup
- # BFD v2 Lookup
- # BFD v3 Lookup
-
- fhir_id, hash_lookup_type = match_fhir_id(
- mbi=slsx_client.mbi, hicn_hash=hicn_hash, request=request
- )
+ versioned_fhir_ids = {}
+ # Perform fhir_id lookup for all supported versions
+ # If the lookup for the requested version fails, raise the exception
+ # This is wrapped in the case that if the requested version fails, match_fhir_id
+ # will still bubble up UpstreamServerException
+ for supported_version in Versions.latest_versions():
+ try:
+ fhir_id, hash_lookup_type = match_fhir_id(
+ mbi=slsx_client.mbi,
+ hicn_hash=hicn_hash,
+ request=request,
+ version=supported_version,
+ )
+ versioned_fhir_ids[supported_version] = fhir_id
+ except UpstreamServerException as e:
+ if supported_version == version:
+ raise e
+
+ bfd_fhir_id_v2 = versioned_fhir_ids.get(Versions.V2, None)
+ bfd_fhir_id_v3 = versioned_fhir_ids.get(Versions.V3, None)
log_dict = {
'type': 'mymedicare_cb:get_and_update_user',
'subject': slsx_client.user_id,
- # BB2-4166-TODO: add fhir_id_v3 when the lookup above is completed
- 'fhir_id_v2': fhir_id,
+ 'fhir_id_v2': bfd_fhir_id_v2,
+ 'fhir_id_v3': bfd_fhir_id_v3,
'hicn_hash': slsx_client.hicn_hash,
'hash_lookup_type': hash_lookup_type,
'crosswalk': {},
@@ -82,41 +97,37 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request):
# Init for hicn crosswalk updates.
hicn_updated = False
-
try:
# Does an existing user and crosswalk exist for SLSx username?
user = User.objects.get(username=slsx_client.user_id)
- # fhir_id can not change for an existing user!
- # BB2-4166-TODO: this should be removed when we enable tandem v2/v3 usage
- if user.crosswalk.fhir_id(2) != fhir_id:
- mesg = "Found user's fhir_id did not match"
- log_dict.update({
- 'status': 'FAIL',
- 'user_id': user.id,
- 'user_username': user.username,
- 'mesg': mesg,
- })
- logger.info(log_dict)
- raise BBMyMedicareCallbackCrosswalkUpdateException(mesg)
-
# Did the hicn change?
if user.crosswalk.user_hicn_hash != slsx_client.hicn_hash:
hicn_updated = True
+ update_fhir_id = False
+ if (
+ user.crosswalk.fhir_id(Versions.V2) == ''
+ or user.crosswalk.fhir_id(Versions.V3) == ''
+ or user.crosswalk.fhir_id(Versions.V2) != bfd_fhir_id_v2
+ or user.crosswalk.fhir_id(Versions.V3) != bfd_fhir_id_v3
+ ):
+ update_fhir_id = True
# Update Crosswalk if the user_mbi is null, but we have an mbi value from SLSx or
# if the saved user_mbi value is different than what SLSx has
if (
(user.crosswalk.user_mbi is None and slsx_client.mbi is not None)
or (user.crosswalk.user_mbi is not None and user.crosswalk.user_mbi != slsx_client.mbi)
or (user.crosswalk.user_id_type != hash_lookup_type or hicn_updated)
+ or update_fhir_id
):
# Log crosswalk before state
log_dict.update({
'crosswalk_before': {
'id': user.crosswalk.id,
'user_hicn_hash': user.crosswalk.user_hicn_hash,
- 'fhir_id_v2': user.crosswalk.fhir_id(version),
+ 'fhir_id_v2': bfd_fhir_id_v2,
+ 'fhir_id_v3': bfd_fhir_id_v3,
'user_id_type': user.crosswalk.user_id_type,
},
})
@@ -124,7 +135,9 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request):
with transaction.atomic():
# Archive to audit crosswalk changes
ArchivedCrosswalk.create(user.crosswalk)
-
+ if update_fhir_id:
+ user.crosswalk.fhir_id_v2 = bfd_fhir_id_v2
+ user.crosswalk.fhir_id_v3 = bfd_fhir_id_v3
# Update crosswalk per changes
user.crosswalk.user_id_type = hash_lookup_type
user.crosswalk.user_hicn_hash = slsx_client.hicn_hash
@@ -141,8 +154,8 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request):
'crosswalk': {
'id': user.crosswalk.id,
'user_hicn_hash': user.crosswalk.user_hicn_hash,
- # BB2-4166-TODO: this is hardcoded to be version 2
- 'fhir_id_v2': user.crosswalk.fhir_id(2),
+ 'fhir_id_v2': bfd_fhir_id_v2,
+ 'fhir_id_v3': bfd_fhir_id_v3,
'user_id_type': user.crosswalk.user_id_type,
},
})
@@ -152,9 +165,13 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request):
except User.DoesNotExist:
pass
- # BB2-4166-TODO: this is hardcoded to be version 2, does not account for both fhir_ids
- # v3 and v2 are BOTH saved in the v2 field
- user = create_beneficiary_record(slsx_client, fhir_id_v2=fhir_id, user_id_type=hash_lookup_type, request=request)
+ user = create_beneficiary_record(
+ slsx_client,
+ fhir_id_v2=bfd_fhir_id_v2,
+ fhir_id_v3=bfd_fhir_id_v3,
+ user_id_type=hash_lookup_type,
+ request=request
+ )
log_dict.update({
'status': 'OK',
@@ -165,8 +182,8 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request):
'crosswalk': {
'id': user.crosswalk.id,
'user_hicn_hash': user.crosswalk.user_hicn_hash,
- # BB2-4166-TODO: this needs to include both fhir versions
- 'fhir_id_v2': user.crosswalk.fhir_id(2),
+ 'fhir_id_v2': bfd_fhir_id_v2,
+ 'fhir_id_v3': bfd_fhir_id_v3,
'user_id_type': user.crosswalk.user_id_type,
},
})
diff --git a/apps/mymedicare_cb/tests/test_callback_slsx.py b/apps/mymedicare_cb/tests/test_callback_slsx.py
index 80ea39201..edb22a1a4 100644
--- a/apps/mymedicare_cb/tests/test_callback_slsx.py
+++ b/apps/mymedicare_cb/tests/test_callback_slsx.py
@@ -39,7 +39,7 @@
from .responses import patient_response
-from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME
+from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME, MOCK_FHIR_V3_ENDPOINT_HOSTNAME
from http import HTTPStatus
@@ -217,23 +217,61 @@ def test_authorize_uuid(self):
)
self.assertEqual(status.HTTP_302_FOUND, response.status_code)
- def test_callback_url_success(self):
+ def test_callback_url_success_v1(self):
+ self._test_callback_url_success(1)
+
+ def test_callback_url_success_v2(self):
+ self._test_callback_url_success(2)
+
+ def test_callback_url_success_v3(self):
+ self._test_callback_url_success(3)
+
+ # mock fhir user info endpoint
+ @urlmatch(
+ netloc=MOCK_FHIR_ENDPOINT_HOSTNAME,
+ path=r'/v1/fhir/Patient/',
+ )
+ def fhir_patient_info_mock_v1(self, url, request):
+ return {
+ 'status_code': status.HTTP_200_OK,
+ 'content': patient_response,
+ }
+
+ # mock fhir user info endpoint
+ @urlmatch(
+ netloc=MOCK_FHIR_ENDPOINT_HOSTNAME,
+ path=r'/v2/fhir/Patient/',
+ )
+ def fhir_patient_info_mock_v2(self, url, request):
+ return {
+ 'status_code': status.HTTP_200_OK,
+ 'content': patient_response,
+ }
+
+ # mock fhir user info endpoint
+ @urlmatch(
+ netloc=MOCK_FHIR_V3_ENDPOINT_HOSTNAME,
+ path=r'/v3/fhir/Patient/',
+ )
+ def fhir_patient_info_mock_v3(self, url, request):
+ print("fhir_patient_info_mock_v3")
+ return {
+ 'status_code': status.HTTP_200_OK,
+ 'content': patient_response,
+ }
+
+ def _test_callback_url_success(self, version):
# create a state
state = generate_nonce()
+ # We ALWAYS version our next_uri, and therefore
+ # this test should include a versioned next_uri for authenticity.
AnonUserState.objects.create(
state=state,
- next_uri="http://www.google.com?client_id=test&redirect_uri=test.com&response_type=token&state=test",
- )
-
- # mock fhir user info endpoint
- @urlmatch(
- netloc=MOCK_FHIR_ENDPOINT_HOSTNAME, path="/v2/fhir/Patient/"
+ next_uri=(
+ f'http://www.doesnotexist.gov?next=/v{version}/o/authorize' # noqa: E231
+ '&client_id=test&redirect_uri=test.com&response_type=token&state=test'
+ )
)
- def fhir_patient_info_mock(url, request):
- return {
- "status_code": status.HTTP_200_OK,
- "content": patient_response,
- }
@all_requests
def catchall(url, request):
@@ -244,7 +282,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
catchall,
):
# need to fake an auth flow context to pass
@@ -257,26 +297,29 @@ def catchall(url, request):
"auth_app_id": "2",
"auth_app_name": "TestApp-001",
"auth_client_id": "uouIr1mnblrv3z0PJHgmeHiYQmGVgmk5DZPDNfop",
+ "version": version,
}
)
s.save()
+
response = self.client.get(
self.callback_url,
data={"req_token": "0000-test_req_token-0000", "relay": state},
)
+
# assert http redirect
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
self.assertIn("client_id=test", response.url)
self.assertIn("redirect_uri=test.com", response.url)
self.assertIn("response_type=token", response.url)
- self.assertIn("http://www.google.com/v2/o/authorize/", response.url)
+ self.assertIn(f"http://www.doesnotexist.gov/v{version}/o/authorize/", response.url) # noqa: E231
# assert login
self.assertNotIn("_auth_user_id", self.client.session)
def test_callback_url_failure(self):
# create a state
state = generate_nonce()
- AnonUserState.objects.create(state=state, next_uri="http://www.google.com")
+ AnonUserState.objects.create(state=state, next_uri="http://www.doesnotexist.gov") # noqa: E231
@all_requests
def catchall(url, request):
@@ -337,7 +380,7 @@ def catchall(url, request):
sls_client.exchange_for_access_token("test_code", None)
def test_callback_exceptions(self):
- versions = [1, 2]
+ versions = [1, 2, 3]
for version in versions:
with self.subTest(version=version):
self._callback_exception_runner(version)
@@ -351,21 +394,10 @@ def _callback_exception_runner(self, version):
AnonUserState.objects.create(
state=state,
next_uri=''.join([
- f'http://www.google.com/v{version}/o/authorize?client_id=test',
+ f'http://www.doesnotexist.gov/v{version}/o/authorize?client_id=test',
'&redirect_uri=test.com&response_type=token&state=test'])
)
- # mock fhir user info endpoint
- # currently, we use v2 fhir endpoint even if the request coming in is v1 authorize (because we treat them the same)
- @urlmatch(
- netloc=MOCK_FHIR_ENDPOINT_HOSTNAME, path=f'/v{version if version == 3 else 2}/fhir/Patient/'
- )
- def fhir_patient_info_mock(url, request):
- return {
- "status_code": status.HTTP_200_OK,
- "content": patient_response,
- }
-
@all_requests
def catchall(url, request):
raise Exception(url)
@@ -375,7 +407,11 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ self.mock_response.slsx_token_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
+ catchall,
catchall,
):
response = self.client.get(
@@ -386,32 +422,11 @@ def catchall(url, request):
# Change existing fhir_id prior to next test
cw = Crosswalk.objects.get(id=1)
- saved_fhir_id = cw.fhir_id(2)
- cw.set_fhir_id("XXX", 2)
- cw.save()
-
- with HTTMock(
- self.mock_response.slsx_token_mock,
- self.mock_response.slsx_user_info_mock,
- self.mock_response.slsx_health_ok_mock,
- self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
- catchall,
- ):
- response = self.client.get(
- self.callback_url, data={"req_token": "test", "relay": state}
- )
-
- # assert 500 exception
- self.assertEqual(
- response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR
- )
- content = json.loads(response.content)
- self.assertEqual(content["error"], "Found user's fhir_id did not match")
+ saved_fhir_id = cw.fhir_id(version)
# Restore fhir_id
cw = Crosswalk.objects.get(id=1)
- cw.set_fhir_id(saved_fhir_id, 2)
+ cw.set_fhir_id(saved_fhir_id, version)
cw.save()
# With HTTMock sls_user_info_no_sub_mock that has no sub/username
@@ -420,7 +435,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_no_username_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
catchall,
):
with self.assertRaises(BBMyMedicareSLSxUserinfoException):
@@ -434,7 +451,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_empty_hicn_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -454,7 +473,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_invalid_mbi_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -470,7 +491,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
catchall,
):
with self.assertRaises(HTTPError):
@@ -484,7 +507,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_http_error_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
catchall,
):
with self.assertRaises(HTTPError):
@@ -498,7 +523,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_fail_mock,
- fhir_patient_info_mock,
+ self.fhir_patient_info_mock_v1,
+ self.fhir_patient_info_mock_v2,
+ self.fhir_patient_info_mock_v3,
catchall,
):
with self.assertRaises(HTTPError):
@@ -512,7 +539,6 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_fail2_mock,
- fhir_patient_info_mock,
catchall,
):
with self.assertRaises(BBMyMedicareSLSxSignoutException):
@@ -573,14 +599,37 @@ def test_callback_allow_slsx_changes_to_hicn_and_mbi(self):
state = generate_nonce()
AnonUserState.objects.create(
state=state,
- next_uri="http://www.google.com?client_id=test&redirect_uri=test.com&response_type=token&state=test",
+ next_uri=(
+ 'http://www.doesnotexist.gov?next=/v{version}/o/authorize'
+ '&client_id=test&redirect_uri=test.com&response_type=token&state=test'
+ )
)
+ # mock fhir patient endpoint (back end bfd) with fhir_id == "-20140000008325"
+ @urlmatch(
+ netloc=MOCK_FHIR_ENDPOINT_HOSTNAME, path="/v1/fhir/Patient/"
+ )
+ def fhir_patient_info_mock_v1(url, request):
+ return {
+ "status_code": status.HTTP_200_OK,
+ "content": patient_response,
+ }
+
# mock fhir patient endpoint (back end bfd) with fhir_id == "-20140000008325"
@urlmatch(
netloc=MOCK_FHIR_ENDPOINT_HOSTNAME, path="/v2/fhir/Patient/"
)
- def fhir_patient_info_mock(url, request):
+ def fhir_patient_info_mock_v2(url, request):
+ return {
+ "status_code": status.HTTP_200_OK,
+ "content": patient_response,
+ }
+
+ # mock fhir patient endpoint (back end bfd) with fhir_id == "-20140000008325"
+ @urlmatch(
+ netloc=MOCK_FHIR_V3_ENDPOINT_HOSTNAME, path="/v3/fhir/Patient/"
+ )
+ def fhir_patient_info_mock_v3(url, request):
return {
"status_code": status.HTTP_200_OK,
"content": patient_response,
@@ -596,7 +645,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_empty_mbi_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ fhir_patient_info_mock_v1,
+ fhir_patient_info_mock_v2,
+ fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -652,7 +703,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ fhir_patient_info_mock_v1,
+ fhir_patient_info_mock_v2,
+ fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -744,7 +797,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ fhir_patient_info_mock_v1,
+ fhir_patient_info_mock_v2,
+ fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -795,7 +850,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock_changed_hicn,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ fhir_patient_info_mock_v1,
+ fhir_patient_info_mock_v2,
+ fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -891,7 +948,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock_changed_mbi,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ fhir_patient_info_mock_v1,
+ fhir_patient_info_mock_v2,
+ fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -971,7 +1030,9 @@ def catchall(url, request):
self.mock_response.slsx_user_info_mock_changed_hicn_mbi,
self.mock_response.slsx_health_ok_mock,
self.mock_response.slsx_signout_ok_mock,
- fhir_patient_info_mock,
+ fhir_patient_info_mock_v1,
+ fhir_patient_info_mock_v2,
+ fhir_patient_info_mock_v3,
catchall,
):
response = self.client.get(
@@ -1078,7 +1139,7 @@ def _callback_usrinfo_invalid_hicn_mbi(self, mock_response_func, err_msg):
state = generate_nonce()
AnonUserState.objects.create(
state=state,
- next_uri="http://www.google.com?client_id=test&redirect_uri=test.com&response_type=token&state=test")
+ next_uri="http://www.doesnotexist.gov?client_id=test&redirect_uri=test.com&response_type=token&state=test")
@all_requests
def catchall(url, request):
diff --git a/apps/mymedicare_cb/views.py b/apps/mymedicare_cb/views.py
index aa0f82ec1..337c302a3 100644
--- a/apps/mymedicare_cb/views.py
+++ b/apps/mymedicare_cb/views.py
@@ -14,6 +14,7 @@
from django.views.decorators.cache import never_cache
from rest_framework import status
from rest_framework.exceptions import NotFound
+from apps.versions import Versions
from apps.dot_ext.loggers import (clear_session_auth_flow_trace,
set_session_auth_flow_trace_value,
@@ -33,9 +34,6 @@
def authenticate(request):
# Update authorization flow from previously stored state in AuthFlowUuid instance in mymedicare_login().
request_state = request.GET.get('relay')
-
- version = request.session['version']
-
clear_session_auth_flow_trace(request)
update_session_auth_flow_trace_from_state(request, request_state)
@@ -72,7 +70,6 @@ def authenticate(request):
set_session_auth_flow_trace_value(request, 'auth_crosswalk_action', crosswalk_action)
# Log successful authentication with beneficiary when we return back here.
- # BB2-4166-TODO: set both fhir_ids if we get both of them
slsx_client.log_authn_success(request, {
'user': {
'id': user.id,
@@ -81,8 +78,8 @@ def authenticate(request):
'id': user.crosswalk.id,
'user_hicn_hash': user.crosswalk.user_hicn_hash,
'user_mbi': user.crosswalk.user_mbi,
- # BB2-4166-TODO: this needs to account for both fhir_ids if they are found
- ('fhir_id_v3' if version == 3 else 'fhir_id_v2'): user.crosswalk.fhir_id(version),
+ 'fhir_id_v2': user.crosswalk.fhir_id(Versions.V2),
+ 'fhir_id_v3': user.crosswalk.fhir_id(Versions.V3),
'user_id_type': user.crosswalk.user_id_type,
},
},
@@ -103,14 +100,14 @@ def callback(request):
return JsonResponse({"error": 'The requested state was not found'}, status=status.HTTP_400_BAD_REQUEST)
next_uri = anon_user_state.next_uri
- # BB2-4166-TODO: refactor this to be generalized
- if "/v3/o/authorize" in next_uri:
- version = 3
- elif "/v2/o/authorize" in next_uri:
- version = 2
- else:
- version = 2
+ # We don't have a `version` coming back from auth. Therefore, we check
+ # the authorize URL to find what version pathway we are on.
+ version = Versions.NOT_AN_API_VERSION
+ for supported_version in Versions.supported_versions():
+ if f"/v{supported_version}/o/authorize" in next_uri:
+ version = supported_version
+ break
request.session['version'] = version
user_not_found_error = None
@@ -170,6 +167,7 @@ def callback(request):
url_map_name = 'oauth2_provider_v2:authorize-instance-v2'
else:
url_map_name = 'oauth2_provider:authorize-instance'
+
auth_uri = reverse(url_map_name, args=[approval.uuid])
_, _, auth_path, _, _ = urlsplit(auth_uri)
@@ -198,8 +196,6 @@ def mymedicare_login(request):
except requests.exceptions.ConnectionError as e:
if retries < max_retries and (env is None or env == 'DEV'):
time.sleep(0.5)
- # Checking target_env ensures the retry logic only happens on local
- print(f"SLSx service health check during login failed. Retrying... ({retries + 1}/{max_retries})")
retries += 1
else:
raise e
diff --git a/apps/test.py b/apps/test.py
index 3ec7114f8..3a65a9db2 100644
--- a/apps/test.py
+++ b/apps/test.py
@@ -408,8 +408,8 @@ def _create_user_app_token_grant(
username = first_name + last_name + "@example.com"
# Create unique hashes using FHIR_ID
- # BB2-4166-TODO: this is only checking v2, possible rewrite these helper functions to allow more
- # generalized fhir_id handling
+ # Eventually, we will be getting rid of the hicn hash. We can leave this v2 reference for now.
+ # This is "just" creating a unique value.
hicn_hash = re.sub(
"[^A-Za-z0-9]+", "a", fhir_id_v2 + self.test_hicn_hash[len(fhir_id_v2):]
)
diff --git a/apps/testclient/constants.py b/apps/testclient/constants.py
index 2d51df038..2133c25ba 100644
--- a/apps/testclient/constants.py
+++ b/apps/testclient/constants.py
@@ -1,7 +1,7 @@
from django.http import JsonResponse
import apps.logging.request_logger as bb2logging
import logging
-from apps.constants import Versions
+from apps.versions import Versions
logger = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
diff --git a/apps/testclient/tests.py b/apps/testclient/tests.py
index beeb07ca7..1df1f48ba 100644
--- a/apps/testclient/tests.py
+++ b/apps/testclient/tests.py
@@ -5,7 +5,7 @@
from django.urls import reverse
from unittest import skipIf
from django.conf import settings
-from apps.constants import Versions, VersionNotMatched
+from apps.versions import Versions, VersionNotMatched
from apps.testclient.utils import (_ormap, _deepfind)
from apps.testclient.constants import EndpointUrl
from apps.testclient.views import FhirDataParams, _build_pagination_uri
@@ -315,7 +315,9 @@ def test_offset_math(self):
# our control, then these unit tests will always be suspect (including offsets and pagination values).
# This seems to have been the case 7mo ago with the "total" test, above.
# self.assertEqual(len(response_data["entry"]), 7)
- self.assertEqual(len(response_data["entry"]), 5)
+ # From commit f6d4d7dcc91cea27288d4bc280cf0c395c60e6be, there was a change to 12 here.
+ # The changes in that commit are around the logging of fhir_id_v2/fhir_id_v3.
+ self.assertEqual(len(response_data["entry"]), 12)
previous_links = [
data["url"]
for data in response_data["link"]
@@ -328,7 +330,7 @@ def test_offset_math(self):
data["url"] for data in response_data["link"] if data["relation"] == "first"
]
self.assertEqual(len(previous_links), 1)
- self.assertEqual(len(next_links), 0)
+ self.assertEqual(len(next_links), 1)
self.assertEqual(len(first_links), 1)
self.assertIn("startIndex=13", previous_links[0])
self.assertIn("startIndex=0", first_links[0])
diff --git a/apps/testclient/utils.py b/apps/testclient/utils.py
index f6f6ed65f..7f644cd28 100644
--- a/apps/testclient/utils.py
+++ b/apps/testclient/utils.py
@@ -6,7 +6,7 @@
from collections import OrderedDict
from django.conf import settings
from urllib.parse import parse_qs, urlparse
-from apps.constants import Versions
+from apps.versions import Versions
from ..dot_ext.models import Application
diff --git a/apps/testclient/views.py b/apps/testclient/views.py
index fc365273d..36b415c2d 100644
--- a/apps/testclient/views.py
+++ b/apps/testclient/views.py
@@ -27,7 +27,7 @@
import apps.logging.request_logger as bb2logging
-from apps.constants import Versions, VersionNotMatched
+from apps.versions import Versions, VersionNotMatched
from apps.testclient.constants import (
HOME_PAGE,
diff --git a/apps/constants.py b/apps/versions.py
similarity index 87%
rename from apps/constants.py
rename to apps/versions.py
index b143ff576..2920d452f 100644
--- a/apps/constants.py
+++ b/apps/versions.py
@@ -37,6 +37,12 @@ def as_int(version: int) -> int:
case _:
raise VersionNotMatched(f"{version} is not a valid version constant")
+ def supported_versions():
+ return [Versions.V1, Versions.V2, Versions.V3]
+
+ def latest_versions():
+ return [Versions.V2, Versions.V3]
+
class AccessType:
ONE_TIME = 'ONE_TIME'
diff --git a/apps/wellknown/views/openid.py b/apps/wellknown/views/openid.py
index 2a2ff1293..f253231dc 100644
--- a/apps/wellknown/views/openid.py
+++ b/apps/wellknown/views/openid.py
@@ -8,7 +8,7 @@
import apps.logging.request_logger as bb2logging
-from apps.constants import Versions
+from apps.versions import Versions
logger = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
SCOPES_SUPPORTED = [
diff --git a/hhs_oauth_server/request_logging.py b/hhs_oauth_server/request_logging.py
index 9df8ef517..21f9d340b 100644
--- a/hhs_oauth_server/request_logging.py
+++ b/hhs_oauth_server/request_logging.py
@@ -9,6 +9,7 @@
from django.utils.deprecation import MiddlewareMixin
from oauth2_provider.models import AccessToken, RefreshToken, get_application_model
from rest_framework.response import Response
+from apps.versions import Versions
from apps.dot_ext.loggers import (
SESSION_AUTH_FLOW_TRACE_KEYS,
@@ -323,7 +324,10 @@ def to_dict(self):
)
if getattr(self.request.user, "crosswalk", False):
self._log_msg_update_from_object(
- self.request.user.crosswalk, "req_fhir_id", "fhir_id"
+ self.request.user.crosswalk, 'req_fhir_id_v2', 'fhir_id_v2'
+ )
+ self._log_msg_update_from_object(
+ self.request.user.crosswalk, 'req_fhir_id_v3', 'fhir_id_v3'
)
"""
@@ -386,8 +390,8 @@ def to_dict(self):
if user:
self.log_msg["user"] = str(user)
try:
- # BB2-4166-TODO: this is hardcoded to be version 2
- self.log_msg["fhir_id_v2"] = user.crosswalk.fhir_id(2)
+ self.log_msg["fhir_id_v2"] = user.crosswalk.fhir_id(Versions.V2)
+ self.log_msg["fhir_id_v3"] = user.crosswalk.fhir_id(Versions.V3)
except ObjectDoesNotExist:
pass
diff --git a/hhs_oauth_server/settings/base.py b/hhs_oauth_server/settings/base.py
index 8720a15ea..8ef49b02d 100644
--- a/hhs_oauth_server/settings/base.py
+++ b/hhs_oauth_server/settings/base.py
@@ -636,6 +636,7 @@
# The hostname is ultimately used in a mock, and therefore does not strictly need to exist
# or be correct. But, it does need to be consistent.
MOCK_FHIR_ENDPOINT_HOSTNAME = urlparse(FHIR_SERVER["FHIR_URL"]).hostname
+MOCK_FHIR_V3_ENDPOINT_HOSTNAME = urlparse(FHIR_SERVER["FHIR_URL_V3"]).hostname
FHIR_POST_SEARCH_PARAM_IDENTIFIER_MBI_HASH = (