Skip to content

Commit 423bb30

Browse files
Ensure token and auth flows function or throw 403s depending on values in the flag. Add 403 handling for userinfo v3
1 parent 0699b7d commit 423bb30

File tree

4 files changed

+102
-67
lines changed

4 files changed

+102
-67
lines changed

apps/accounts/views/oauth2_profile.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
from collections import OrderedDict
2-
from django.conf import settings
3-
from django.contrib.auth import get_user_model
42
from django.http import JsonResponse
53
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
64
from oauth2_provider.decorators import protected_resource
7-
from oauth2_provider.models import get_access_token_model, get_application_model
85
from rest_framework.decorators import api_view, permission_classes, authentication_classes
9-
from waffle import get_waffle_flag_model
106

117
from apps.authorization.permissions import DataAccessGrantPermission
128
from apps.capabilities.permissions import TokenHasProtectedCapability
139
from apps.fhir.bluebutton.models import Crosswalk
1410
from apps.fhir.bluebutton.permissions import ApplicationActivePermission
1511

1612
from apps.versions import Versions
13+
from apps.wellknown.permissions import V3EarlyAdopterWellKnownPermission
1714

1815

1916
def _get_userinfo(user, version=Versions.NOT_AN_API_VERSION):
@@ -45,23 +42,11 @@ def _get_userinfo(user, version=Versions.NOT_AN_API_VERSION):
4542
@authentication_classes([OAuth2Authentication])
4643
@permission_classes([ApplicationActivePermission,
4744
TokenHasProtectedCapability,
48-
DataAccessGrantPermission])
45+
DataAccessGrantPermission,
46+
V3EarlyAdopterWellKnownPermission])
4947
@protected_resource() # Django OAuth Toolkit -> resource_owner = AccessToken
5048
def _openidconnect_userinfo(request, version=Versions.NOT_AN_API_VERSION):
5149
# NOTE: The **kwargs are not used anywhere down the callchain, and are being ignored.
52-
# 4250: Handling to ensure this only returns successfully if the flag is enabled for the application
53-
# associated with the user making the call
54-
if version == Versions.V3:
55-
user = get_user_model().objects.get(username=request.resource_owner)
56-
access_token = get_access_token_model().objects.get(user_id=user.id)
57-
application = get_application_model().objects.get(id=access_token.application_id)
58-
application_user = get_user_model().objects.get(id=application.user_id)
59-
flag = get_waffle_flag_model().get('v3_early_adopter')
60-
if flag.id is None or not flag.is_active_for_user(application_user):
61-
return JsonResponse(
62-
{'status_code': 403, 'message': settings.APPLICATION_DOES_NOT_HAVE_V3_ENABLED_YET.format(application.name)},
63-
status=403,
64-
)
6550

6651
return JsonResponse(_get_userinfo(request.resource_owner, version))
6752

@@ -75,8 +60,6 @@ def openidconnect_userinfo_v2(request):
7560

7661

7762
def openidconnect_userinfo_v3(request):
78-
print("openidconnect_userinfo_v3 request: ", request.__dict__)
79-
print("openidconnect_userinfo_v3 user: ", request.user.__dict__)
8063
return _openidconnect_userinfo(request, version=Versions.V3)
8164

8265

apps/dot_ext/views/authorization.py

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
from django.http import JsonResponse
1111
from django.http.response import HttpResponse, HttpResponseBadRequest
1212
from django.template.response import TemplateResponse
13-
from django.core.exceptions import ObjectDoesNotExist
13+
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
1414
from django.utils.decorators import method_decorator
1515
from django.views.decorators.csrf import csrf_exempt
1616
from django.views.decorators.debug import sensitive_post_parameters
1717
from apps.dot_ext.constants import TOKEN_ENDPOINT_V3_KEY
18+
from oauthlib.oauth2.rfc6749.errors import AccessDeniedError as AccessDeniedTokenCustomError
1819
from oauth2_provider.exceptions import OAuthToolkitError
1920
from oauth2_provider.views.base import app_authorized
2021
from oauth2_provider.models import get_refresh_token_model, get_access_token_model
@@ -79,50 +80,8 @@ def _wrapped(request, *args, **kwargs):
7980
return _wrapped
8081

8182

82-
def check_v3_endpoint_access(view_func):
83-
@wraps(view_func)
84-
def _wrapped(request, *args, **kwargs):
85-
# 4250-TODO how do we not call this so many times?
86-
path_info = request.__dict__.get('path_info')
87-
version = get_api_version_number_from_url(path_info)
88-
if version != Versions.V3:
89-
return view_func(request, *args, **kwargs)
90-
91-
flag = get_waffle_flag_model().get('v3_early_adopter')
92-
req_meta = request.META
93-
url_query = parse_qs(req_meta.get('QUERY_STRING'))
94-
client_id = url_query.get('client_id', [None])
95-
try:
96-
if client_id[0]:
97-
application = get_application_model().objects.get(client_id=client_id[0])
98-
else:
99-
url_query = parse_qs(request._body.decode('utf-8'))
100-
refresh_token_from_request = url_query.get('refresh_token', [None])
101-
refresh_token = get_refresh_token_model().objects.get(token=refresh_token_from_request[0])
102-
application = get_application_model().objects.get(id=refresh_token.application_id)
103-
104-
application_user = get_user_model().objects.get(id=application.user_id)
105-
106-
if flag.id is not None and flag.is_active_for_user(application_user):
107-
return view_func(request, *args, **kwargs)
108-
else:
109-
return JsonResponse(
110-
{'status_code': 403, 'message': settings.APPLICATION_DOES_NOT_HAVE_V3_ENABLED_YET.format(application.name)},
111-
status=403,
112-
)
113-
except ObjectDoesNotExist:
114-
# 4250-TODO Do we need this?
115-
return JsonResponse(
116-
{'status_code': 500, 'message': 'Error retrieving data'},
117-
status=500,
118-
)
119-
120-
return _wrapped
121-
122-
12383
@method_decorator(csrf_exempt, name="dispatch")
12484
@method_decorator(require_post_state_decorator, name="dispatch")
125-
@method_decorator(check_v3_endpoint_access, name="dispatch")
12685
class AuthorizationView(DotAuthorizationView):
12786
"""
12887
Override the base authorization view from dot to
@@ -274,6 +233,27 @@ def get(self, request, *args, **kwargs):
274233
kwargs['code_challenge_method'] = request.GET.get('code_challenge_method', None)
275234
return super().get(request, *args, **kwargs)
276235

236+
def validate_v3_authorization_request(self):
237+
flag = get_waffle_flag_model().get('v3_early_adopter')
238+
req_meta = self.request.META
239+
url_query = parse_qs(req_meta.get('QUERY_STRING'))
240+
client_id = url_query.get('client_id', [None])
241+
try:
242+
application = get_application_model().objects.get(client_id=client_id[0])
243+
application_user = get_user_model().objects.get(id=application.user_id)
244+
if flag.id is not None and flag.is_active_for_user(application_user):
245+
return
246+
else:
247+
raise AccessDeniedTokenCustomError(
248+
description=settings.APPLICATION_DOES_NOT_HAVE_V3_ENABLED_YET.format(application.name)
249+
)
250+
except ObjectDoesNotExist:
251+
# 4250-TODO Do we need this?
252+
return JsonResponse(
253+
{'status_code': 500, 'message': 'Error retrieving data'},
254+
status=500,
255+
)
256+
277257
def form_valid(self, form):
278258
client_id = form.cleaned_data["client_id"]
279259
application = get_application_model().objects.get(client_id=client_id)
@@ -312,6 +292,12 @@ def form_valid(self, form):
312292
refresh_token_delete_cnt = 0
313293

314294
try:
295+
path_info = self.request.__dict__.get('path_info')
296+
version = get_api_version_number_from_url(path_info)
297+
# If it is not version 3, we don't need to check anything, just return
298+
if version == Versions.V3:
299+
self.validate_v3_authorization_request()
300+
315301
if not scopes:
316302
# Since the create_authorization_response will re-inject scopes even when none are
317303
# valid, we want to pre-emptively treat this as an error case
@@ -441,7 +427,6 @@ def dispatch(self, request, uuid, *args, **kwargs):
441427
return result
442428

443429

444-
# @method_decorator(check_v3_endpoint_access, name="dispatch")
445430
@method_decorator(csrf_exempt, name="dispatch")
446431
class TokenView(DotTokenView):
447432

@@ -461,13 +446,47 @@ def validate_token_endpoint_request_body(self, request):
461446
description=f"Invalid parameters in request: {invalid_parameters}"
462447
)
463448

449+
def validate_v3_token_call(self, request) -> None:
450+
flag = get_waffle_flag_model().get('v3_early_adopter')
451+
req_meta = request.META
452+
url_query = parse_qs(req_meta.get('QUERY_STRING'))
453+
try:
454+
url_query = parse_qs(request._body.decode('utf-8'))
455+
refresh_token_from_request = url_query.get('refresh_token', [None])
456+
refresh_token = get_refresh_token_model().objects.get(token=refresh_token_from_request[0])
457+
application = get_application_model().objects.get(id=refresh_token.application_id)
458+
application_user = get_user_model().objects.get(id=application.user_id)
459+
460+
if flag.id is not None and flag.is_active_for_user(application_user):
461+
return
462+
else:
463+
raise PermissionDenied(
464+
settings.APPLICATION_DOES_NOT_HAVE_V3_ENABLED_YET.format(application.name)
465+
)
466+
except ObjectDoesNotExist:
467+
# 4250-TODO Do we need this?
468+
return JsonResponse(
469+
{'status_code': 500, 'message': 'Error retrieving data'},
470+
status=500,
471+
)
472+
464473
@method_decorator(sensitive_post_parameters("password"))
465474
def post(self, request, *args, **kwargs):
475+
path_info = self.request.__dict__.get('path_info')
476+
version = get_api_version_number_from_url(path_info)
477+
# If it is not version 3, we don't need to check anything, just return
466478
try:
479+
if version == Versions.V3:
480+
self.validate_v3_token_call(request)
467481
self.validate_token_endpoint_request_body(request)
468482
app = validate_app_is_active(request)
469483
except (InvalidClientError, InvalidGrantError, InvalidRequestError) as error:
470484
return json_response_from_oauth2_error(error)
485+
except PermissionDenied as e:
486+
return JsonResponse(
487+
{'status_code': 403, 'message': str(e)},
488+
status=403,
489+
)
471490

472491
url, headers, body, status = self.create_token_response(request)
473492

apps/fhir/bluebutton/permissions.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ def has_permission(self, request, view):
113113

114114
class V3EarlyAdopterPermission(permissions.BasePermission):
115115
def has_permission(self, request, view):
116-
print("IN HAS_PERMISSION OF V3EarlyAdopterPermission")
117-
print("V3EarlyAdopterPermission request: ", request.__dict__)
118-
print("V3EarlyAdopterPermission view: ", view.__dict__)
119116
# if it is not version 3, we do not need to check the waffle switch or flag
120117
if view.version < Versions.V3:
121118
return True

apps/wellknown/permissions.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import logging
2+
3+
from django.conf import settings
4+
from django.contrib.auth import get_user_model
5+
from oauth2_provider.views.base import get_access_token_model
6+
from oauth2_provider.models import get_application_model
7+
from rest_framework import permissions
8+
from rest_framework.exceptions import PermissionDenied
9+
from waffle import get_waffle_flag_model
10+
from apps.versions import Versions
11+
12+
import apps.logging.request_logger as bb2logging
13+
14+
logger = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
15+
16+
17+
class V3EarlyAdopterWellKnownPermission(permissions.BasePermission):
18+
# BB2-4250: Handling to ensure this only returns successfully if the flag is enabled for the application
19+
# associated with the user making the call
20+
def has_permission(self, request, view):
21+
version = view.kwargs.get('version')
22+
# if it is not version 3, we do not need to check the waffle switch or flag
23+
if version < Versions.V3:
24+
return True
25+
26+
token = get_access_token_model().objects.get(token=request._auth)
27+
application = get_application_model().objects.get(id=token.application_id)
28+
application_user = get_user_model().objects.get(id=application.user_id)
29+
flag = get_waffle_flag_model().get('v3_early_adopter')
30+
31+
if flag.id is not None and flag.is_active_for_user(application_user):
32+
return True
33+
else:
34+
raise PermissionDenied(
35+
settings.APPLICATION_DOES_NOT_HAVE_V3_ENABLED_YET.format(application.name)
36+
)

0 commit comments

Comments
 (0)