-
Notifications
You must be signed in to change notification settings - Fork 28
BB2-4250: Make v3_endpoints waffle switch app specific #1429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
eb64ae9
499d00a
0699b7d
423bb30
801d5ca
62c3d1c
5c35c40
916de16
ebd6c64
7657a4d
8345457
d89d507
0b3f054
1f83723
1e184af
ef0efae
eb306ce
73e0cb5
314ab24
8b69aea
1268a71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,30 +4,36 @@ | |
| from functools import wraps | ||
| from time import strftime | ||
|
|
||
| from django.conf import settings | ||
| from django.contrib.auth import get_user_model | ||
| from django.contrib.auth.views import redirect_to_login | ||
| from django.http import JsonResponse | ||
| from django.http.response import HttpResponse, HttpResponseBadRequest | ||
| from django.template.response import TemplateResponse | ||
| from django.core.exceptions import ObjectDoesNotExist, PermissionDenied | ||
| from django.utils.decorators import method_decorator | ||
| from django.views.decorators.csrf import csrf_exempt | ||
| from django.views.decorators.debug import sensitive_post_parameters | ||
| from apps.dot_ext.constants import TOKEN_ENDPOINT_V3_KEY | ||
| from oauthlib.oauth2.rfc6749.errors import AccessDeniedError as AccessDeniedTokenCustomError | ||
| from oauth2_provider.exceptions import OAuthToolkitError | ||
| from oauth2_provider.views.base import app_authorized, get_access_token_model | ||
| from oauth2_provider.views.base import app_authorized | ||
| from oauth2_provider.models import get_refresh_token_model, get_access_token_model | ||
| from oauth2_provider.views.base import AuthorizationView as DotAuthorizationView | ||
| from oauth2_provider.views.base import TokenView as DotTokenView | ||
| from oauth2_provider.views.base import RevokeTokenView as DotRevokeTokenView | ||
| from oauth2_provider.views.introspect import ( | ||
| IntrospectTokenView as DotIntrospectTokenView, | ||
| ) | ||
| from waffle import switch_is_active | ||
| from waffle import switch_is_active, get_waffle_flag_model | ||
| from oauth2_provider.models import get_application_model | ||
| from oauthlib.oauth2 import AccessDeniedError | ||
| from oauthlib.oauth2.rfc6749.errors import InvalidClientError, InvalidGrantError, InvalidRequestError | ||
| from urllib.parse import urlparse, parse_qs | ||
| import html | ||
| from apps.dot_ext.scopes import CapabilitiesScopes | ||
| import apps.logging.request_logger as bb2logging | ||
| from apps.versions import Versions | ||
|
|
||
| from ..signals import beneficiary_authorized_application | ||
| from ..forms import SimpleAllowForm | ||
|
|
@@ -41,6 +47,7 @@ | |
| ) | ||
| from ..models import Approval | ||
| from ..utils import ( | ||
| get_api_version_number_from_url, | ||
| remove_application_user_pair_tokens_data_access, | ||
| validate_app_is_active, | ||
| json_response_from_oauth2_error, | ||
|
|
@@ -84,6 +91,8 @@ class AuthorizationView(DotAuthorizationView): | |
| # TODO: rename this so that it isn't the same as self.version (works but confusing) | ||
| # this needs to be here for urls.py as_view(version) calls, but don't use it | ||
| version = 0 | ||
| # Variable to help reduce the amount of times validate_v3_authorization_request is called | ||
| validate_v3_call = True | ||
| form_class = SimpleAllowForm | ||
| login_url = "/mymedicare/login" | ||
|
|
||
|
|
@@ -116,6 +125,8 @@ def _check_for_required_params(self, request): | |
| error_message = "State parameter should have a minimum of 16 characters" | ||
| return JsonResponse({"status_code": 400, "message": error_message}, status=400) | ||
|
|
||
| # BB2-4250: This code will not execute if the application is not in the v3_early_adopter flag | ||
| # so it will not be modified as part of BB2-4250 | ||
| if switch_is_active('v3_endpoints') and v3: | ||
| if 'scope' not in request.GET: | ||
| missing_params.append("scope") | ||
|
|
@@ -140,6 +151,11 @@ def dispatch(self, request, *args, **kwargs): | |
| initially create an AuthFlowUuid object for authorization | ||
| flow tracing in logs. | ||
| """ | ||
| path_info = self.request.__dict__.get('path_info') | ||
| version = get_api_version_number_from_url(path_info) | ||
| # If it is not version 3, we don't need to check anything, just return | ||
| if version == Versions.V3 and self.validate_v3_call: | ||
| self.validate_v3_authorization_request() | ||
|
||
| # TODO: Should the client_id match a valid application here before continuing, instead of after matching to FHIR_ID? | ||
| if not kwargs.get('is_subclass_approvalview', False): | ||
| # Create new authorization flow trace UUID in session and AuthFlowUuid instance, if subclass is not ApprovalView | ||
|
|
@@ -224,6 +240,31 @@ def get(self, request, *args, **kwargs): | |
| kwargs['code_challenge_method'] = request.GET.get('code_challenge_method', None) | ||
| return super().get(request, *args, **kwargs) | ||
|
|
||
| def validate_v3_authorization_request(self): | ||
| flag = get_waffle_flag_model().get('v3_early_adopter') | ||
| req_meta = self.request.META | ||
| url_query = parse_qs(req_meta.get('QUERY_STRING')) | ||
| client_id = url_query.get('client_id', [None]) | ||
| try: | ||
| application = get_application_model().objects.get(client_id=client_id[0]) | ||
| application_user = get_user_model().objects.get(id=application.user_id) | ||
|
|
||
| if flag.id is None or flag.is_active_for_user(application_user): | ||
| # Update the class variable to ensure subsequent calls to dispatch don't call this function | ||
| # more times than is needed | ||
| self.validate_v3_call = False | ||
| return | ||
| else: | ||
| raise AccessDeniedTokenCustomError( | ||
| description=settings.APPLICATION_DOES_NOT_HAVE_V3_ENABLED_YET.format(application.name) | ||
| ) | ||
| except ObjectDoesNotExist: | ||
| # 4250-TODO Do we need this? | ||
| return JsonResponse( | ||
| {'status_code': 500, 'message': 'Error retrieving data'}, | ||
| status=500, | ||
| ) | ||
JamesDemeryNava marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def form_valid(self, form): | ||
| client_id = form.cleaned_data["client_id"] | ||
| application = get_application_model().objects.get(client_id=client_id) | ||
|
|
@@ -262,6 +303,7 @@ def form_valid(self, form): | |
| refresh_token_delete_cnt = 0 | ||
|
|
||
| try: | ||
|
|
||
| if not scopes: | ||
| # Since the create_authorization_response will re-inject scopes even when none are | ||
| # valid, we want to pre-emptively treat this as an error case | ||
|
|
@@ -410,13 +452,49 @@ def validate_token_endpoint_request_body(self, request): | |
| description=f"Invalid parameters in request: {invalid_parameters}" | ||
| ) | ||
|
|
||
| def validate_v3_token_call(self, request) -> None: | ||
| flag = get_waffle_flag_model().get('v3_early_adopter') | ||
|
|
||
| try: | ||
| url_query = parse_qs(request._body.decode('utf-8')) | ||
| refresh_token_from_request = url_query.get('refresh_token', [None]) | ||
| refresh_token = get_refresh_token_model().objects.get(token=refresh_token_from_request[0]) | ||
| application = get_application_model().objects.get(id=refresh_token.application_id) | ||
| application_user = get_user_model().objects.get(id=application.user_id) | ||
|
|
||
| if flag.id is None or flag.is_active_for_user(application_user): | ||
| return | ||
| else: | ||
| raise PermissionDenied( | ||
| settings.APPLICATION_DOES_NOT_HAVE_V3_ENABLED_YET.format(application.name) | ||
| ) | ||
| except ObjectDoesNotExist: | ||
| # 4250-TODO Do we need this? | ||
| return JsonResponse( | ||
| {'status_code': 500, 'message': 'Error retrieving data'}, | ||
| status=500, | ||
| ) | ||
JamesDemeryNava marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| @method_decorator(sensitive_post_parameters("password")) | ||
| def post(self, request, *args, **kwargs): | ||
| path_info = self.request.__dict__.get('path_info') | ||
| version = get_api_version_number_from_url(path_info) | ||
| # If it is not version 3, we don't need to check anything, just return | ||
| try: | ||
| if version == Versions.V3: | ||
| self.validate_v3_token_call(request) | ||
| self.validate_token_endpoint_request_body(request) | ||
| app = validate_app_is_active(request) | ||
| except (InvalidClientError, InvalidGrantError, InvalidRequestError) as error: | ||
| return json_response_from_oauth2_error(error) | ||
| except PermissionDenied: | ||
| log.exception('Permission denied during token endpoint processing.') | ||
| # This error will not match other errors thrown by this waffle_flag as Github raised | ||
| # a security concern about it, but only here. | ||
| return JsonResponse( | ||
| {'status_code': 403, 'message': 'You do not have permission to perform this action.'}, | ||
| status=403, | ||
| ) | ||
|
|
||
| url, headers, body, status = self.create_token_response(request) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.