Skip to content

Commit c80bc9d

Browse files
Merge branch 'master' into clewellyn-nava/bb2-4271
2 parents 1a8d284 + f03b648 commit c80bc9d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1663
-475
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,9 @@ reqs-install:
1515

1616
reqs-install-dev:
1717
pip install -r requirements/requirements.dev.txt --no-index --find-links ./vendor/
18+
19+
build-local:
20+
cd dev-local ; make build-local ; cd ..
21+
22+
run-local:
23+
cd dev-local ; make run-local ; cd ..

apps/accounts/views/oauth2_profile.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from apps.fhir.bluebutton.models import Crosswalk
1010
from apps.fhir.bluebutton.permissions import ApplicationActivePermission
1111

12-
from apps.constants import Versions
12+
from apps.versions import Versions
1313

1414

1515
def _get_userinfo(user, version=Versions.NOT_AN_API_VERSION):
@@ -46,8 +46,6 @@ def _get_userinfo(user, version=Versions.NOT_AN_API_VERSION):
4646
def _openidconnect_userinfo(request, version=Versions.NOT_AN_API_VERSION):
4747
# NOTE: The **kwargs are not used anywhere down the callchain, and are being ignored.
4848

49-
# BB2-4166-TODO: will the request have a version? do we get here from redirects or is this
50-
# a straight url that we need to get the version from the url (like we do in the fhir app)
5149
return JsonResponse(_get_userinfo(request.resource_owner, version))
5250

5351

apps/authorization/permissions.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.conf import settings
22
from rest_framework import (permissions, exceptions)
3+
from apps.versions import Versions, VersionNotMatched
34

45
from .models import DataAccessGrant
56

@@ -32,8 +33,10 @@ def has_object_permission(self, request, view, obj):
3233
# Patient resources were taken care of above
3334
# Return 404 on error to avoid notifying unauthorized user the object exists
3435

35-
# BB2-4166-TODO: this is hardcoded to be version 2
36-
return is_resource_for_patient(obj, request.crosswalk.fhir_id(2))
36+
if view.version in Versions.supported_versions():
37+
return is_resource_for_patient(obj, request.crosswalk.fhir_id(view.version))
38+
else:
39+
raise VersionNotMatched()
3740

3841

3942
def is_resource_for_patient(obj, patient_id):

apps/authorization/views.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
from oauth2_provider.views.base import OAuthLibMixin
1515
from oauth2_provider.views.generic import ClientProtectedResourceView
1616

17+
from apps.versions import VersionNotMatched, Versions
1718
from apps.dot_ext.authentication import SLSAuthentication
18-
from .models import DataAccessGrant
19-
from ..dot_ext.utils import get_application_from_meta
20-
from ..fhir.bluebutton.models import Crosswalk
19+
from apps.authorization.models import DataAccessGrant
20+
from apps.dot_ext.utils import get_application_from_meta, get_api_version_number_from_url
21+
from apps.fhir.bluebutton.models import Crosswalk
2122

2223

2324
Application = get_application_model()
@@ -31,7 +32,6 @@ class Meta:
3132
fields = ('id', 'name', 'logo_uri', 'tos_uri', 'policy_uri', 'contacts')
3233

3334
def get_contacts(self, obj):
34-
print(obj)
3535
application = Application.objects.get(id=obj.id)
3636
return application.support_email or ""
3737

@@ -68,9 +68,21 @@ class ExpireDataAccessGrantView(ClientProtectedResourceView, OAuthLibMixin):
6868
@staticmethod
6969
def post(request, *args, **kwargs):
7070
try:
71+
path_info = request.__dict__.get('path_info')
72+
version = get_api_version_number_from_url(path_info)
7173
patient_id = kwargs.pop('patient_id', None)
72-
# BB2-4166-TODO: currently hardcoded for v2, might need to not be static
73-
user = Crosswalk.objects.get(fhir_id_v2=patient_id).user
74+
75+
# V1 is treated as the same as V2 since their pathways are very similar and use the same fhir_id_v2 despite the name
76+
match version:
77+
case Versions.V1:
78+
user = Crosswalk.objects.get(fhir_id_v2=patient_id).user
79+
case Versions.V2:
80+
user = Crosswalk.objects.get(fhir_id_v2=patient_id).user
81+
case Versions.V3:
82+
user = Crosswalk.objects.get(fhir_id_v3=patient_id).user
83+
case _:
84+
raise VersionNotMatched(f"{version} is not a valid version constant")
85+
7486
client = get_application_from_meta(request)
7587
DataAccessGrant.objects.get(beneficiary=user.id, application=client).delete()
7688
except Crosswalk.DoesNotExist:

apps/bb2_tools/admin.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
DummyAdminObject,
2222
UserStats,
2323
)
24-
from apps.fhir.bluebutton.utils import get_patient_by_id
24+
from apps.fhir.bluebutton.utils import get_v2_patient_by_id
2525

2626
ADMIN_PREPEND = getattr(settings, "ADMIN_PREPEND_URL", "")
2727
BB2_TOOLS_PATH = (
@@ -332,8 +332,7 @@ class BeneficiaryDashboardAdmin(ReadOnlyAdmin):
332332
"get_connected_applications",
333333
"date_created",
334334
)
335-
# BB2-4166-TODO: add support for v3
336-
search_fields = ('user__username', 'fhir_id_v2', '_user_id_hash', '_user_mbi')
335+
search_fields = ('user__username', 'fhir_id_v2', 'fhir_id_v3', '_user_id_hash', '_user_mbi')
337336
readonly_fields = ('date_created',)
338337
raw_id_fields = ('user',)
339338

@@ -361,10 +360,9 @@ def get_access_tokens(self, obj):
361360
ordering="MyIdentities",
362361
)
363362
def get_identities(self, obj):
364-
# BB2-4166-TODO: add support for v3
365363
return format_html(
366-
'<div><ul><li>FHIR_ID_V2:{}</li><li>HICN HASH:{}</li><li>MBI:{}</li>'.format(
367-
obj.fhir_id(2), obj.user_hicn_hash, obj.user_mbi
364+
'<div><ul><li>FHIR_ID_V2:{}</li><li>FHIR_ID_V3:{}</li><li>HICN HASH:{}</li><li>MBI:{}</li>'.format(
365+
obj.fhir_id(2), obj.fhir_id(3), obj.user_hicn_hash, obj.user_mbi
368366
)
369367
)
370368

@@ -416,10 +414,9 @@ def change_view(self, request, object_id, form_url="", extra_context=None):
416414
crosswalk = BeneficiaryDashboard.objects.get(pk=int(object_id))
417415

418416
json_resp = None
419-
420417
try:
421-
# BB2-4166-TODO: this is hardcoded to be version 2
422-
json_resp = get_patient_by_id(crosswalk.fhir_id(2), request)
418+
# DEPRECATE_V2: If we ever deprecate v2, this function and call need to be updated
419+
json_resp = get_v2_patient_by_id(crosswalk.fhir_id(2), request)
423420
except Exception as e:
424421
json_resp = {"backend_error": str(e)}
425422

apps/core/management/commands/create_test_feature_switches.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def handle(self, *args, **options):
3434
for switch in WAFFLE_FEATURE_SWITCHES:
3535
try:
3636
Switch.objects.get(name=switch[0])
37-
self._log("Feature switch already exists: %s" % (str(switch)))
3837
except Switch.DoesNotExist:
3938
Switch.objects.create(name=switch[0], active=switch[1], note=switch[2])
4039
self._log("Feature switch created: %s" % (str(switch)))
@@ -46,7 +45,6 @@ def handle(self, *args, **options):
4645

4746
try:
4847
flag_obj = Flag.objects.get(name=flag[0])
49-
self._log("Feature flag already exists: %s" % (str(flag_obj)))
5048
except Flag.DoesNotExist:
5149
flag_obj = Flag.objects.create(name=flag[0])
5250
self._log("Feature flag created: %s" % (str(flag[0])))
@@ -62,7 +60,6 @@ def handle(self, *args, **options):
6260
flag_obj.save()
6361
self._log("User {} added to feature flag: {}".format(u, flag))
6462
except Exception as e:
65-
print(e)
6663
self._log("Exception when adding user {} to feature flag: {}".format(u, flag))
6764
except User.DoesNotExist:
6865
# assuming test users exist before creating flags associated with them

apps/dot_ext/oauth2_backends.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from ..fhir.bluebutton.models import Crosswalk
55
from .loggers import (clear_session_auth_flow_trace, update_session_auth_flow_trace_from_code,
66
set_session_auth_flow_trace_value)
7+
from apps.dot_ext.utils import get_api_version_number_from_url
78

89

910
class OAuthLibSMARTonFHIR(OAuthLibCore):
@@ -31,8 +32,8 @@ def create_token_response(self, request):
3132
if Crosswalk.objects.filter(user=token.user).exists():
3233
fhir_body = json.loads(body)
3334
cw = Crosswalk.objects.get(user=token.user)
34-
# BB2-4166-TODO: this is hardcoded to be version 2
35-
fhir_body["patient"] = cw.fhir_id(2)
35+
version = get_api_version_number_from_url(request.path)
36+
fhir_body['patient'] = cw.fhir_id(version)
3637
body = json.dumps(fhir_body)
3738

3839
return uri, headers, body, status

apps/dot_ext/tests/test_api_error_codes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from apps.authorization.models import (
77
DataAccessGrant,
88
)
9-
from apps.constants import AccessType
9+
from apps.versions import AccessType
1010
from datetime import datetime
1111
from dateutil.relativedelta import relativedelta
1212
from unittest import mock

apps/dot_ext/tests/test_utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.test import TestCase
2+
3+
from apps.versions import VersionNotMatched
4+
from ..utils import get_api_version_number_from_url
5+
6+
SUPPORTED_VERSION_TEST_CASES = [
7+
{'url_path': '/v2/fhir/Patient/', 'expected': 2},
8+
# return 0 because v2 does not have a leading /
9+
{'url_path': 'v2/fhir/Patient/', 'expected': 0},
10+
{'url_path': '/v3/fhir/Coverage/', 'expected': 3},
11+
{'url_path': '/v3/fhir/Coverage/v2/', 'expected': 3},
12+
]
13+
14+
15+
class TestDOTUtils(TestCase):
16+
def test_get_api_version_number(self):
17+
for test in SUPPORTED_VERSION_TEST_CASES:
18+
result = get_api_version_number_from_url(test['url_path'])
19+
assert result == test['expected']
20+
21+
def test_get_api_version_number_unsupported_version(self):
22+
# unsupported version will raise an exception
23+
with self.assertRaises(VersionNotMatched, msg='4 extracted from /v4/fhir/Coverage/'):
24+
get_api_version_number_from_url('/v4/fhir/Coverage/')

apps/dot_ext/tests/test_views.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
SCOPES_TO_URL_BASE_PATH,
2525
)
2626

27+
import os
28+
2729
from hhs_oauth_server.settings.base import MOCK_FHIR_ENDPOINT_HOSTNAME
2830

2931

@@ -576,16 +578,15 @@ def test_delete_token_success(self):
576578

577579
# This assertion is incorrectly crafted - it actually requires a local server started
578580
# so that the fhir fetch data is called and hence generate cert file not found error.
579-
# TODO: refactor test to not depend on a server up and running.
580-
581-
# Post Django 2.2: An OSError exception is expected when trying to reach the
582-
# backend FHIR server and proves authentication worked.
583-
with self.assertRaisesRegexp(
584-
OSError, 'Could not find the TLS certificate file'
585-
):
586-
response = self.client.get(
587-
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + anna_token.token}
588-
)
581+
# 20251120 This test is now gated on a variable; if the variable does not exist, or
582+
# is not set, the test will run. This is the desired behavior.
583+
if os.getenv("RUNNING_IN_LOCAL_STACK", None) != "true":
584+
with self.assertRaisesRegexp(
585+
OSError, 'Could not find the TLS certificate file'
586+
):
587+
response = self.client.get(
588+
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + anna_token.token}
589+
)
589590

590591
bob_tkn = self._create_test_token(bob, bob_application)
591592
self.assertTrue(
@@ -638,24 +639,26 @@ def test_delete_token_success(self):
638639

639640
# Post Django 2.2: An OSError exception is expected when trying to reach the
640641
# backend FHIR server and proves authentication worked.
641-
with self.assertRaisesRegexp(
642-
OSError, 'Could not find the TLS certificate file'
643-
):
644-
response = self.client.get(
645-
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + bob_tkn.token}
646-
)
642+
if os.getenv("RUNNING_IN_LOCAL_STACK", None) != "true":
643+
with self.assertRaisesRegexp(
644+
OSError, 'Could not find the TLS certificate file'
645+
):
646+
response = self.client.get(
647+
'/v1/fhir/Patient', headers={'authorization': 'Bearer ' + bob_tkn.token}
648+
)
647649

648650
next_tkn = self._create_test_token(anna, anna_application)
649651

650652
# Post Django 2.2: An OSError exception is expected when trying to reach the
651653
# backend FHIR server and proves authentication worked.
652-
with self.assertRaisesRegexp(
653-
OSError, 'Could not find the TLS certificate file'
654-
):
655-
response = self.client.get(
656-
'/v1/fhir/Patient',
657-
headers={'authorization': 'Bearer ' + next_tkn.token},
658-
)
654+
if os.getenv("RUNNING_IN_LOCAL_STACK", None) != "true":
655+
with self.assertRaisesRegexp(
656+
OSError, 'Could not find the TLS certificate file'
657+
):
658+
response = self.client.get(
659+
'/v1/fhir/Patient',
660+
headers={'authorization': 'Bearer ' + next_tkn.token},
661+
)
659662

660663
# self.assertEqual(next_tkn.token, tkn.token)
661664
self.assertTrue(

0 commit comments

Comments
 (0)