Skip to content

Commit 6b80829

Browse files
authored
BB2-3227: Add access grant expiry to access token responses (#1207)
* Adds access grant expiry to access token responses. * Changed field name for US english consistency. * Added missing timestamp call * Fixed dag.expiration_date cases. * Added tests and made more readable. Fix bug RE: dag ABOUT to exist. * Fix blank lines linting error * Removed unused variable * Loosen time test * Collect created dag reliably * More flexible test * Fixed one time flake * Use expires in instead of hardcoding
1 parent b2832e8 commit 6b80829

File tree

3 files changed

+106
-5
lines changed

3 files changed

+106
-5
lines changed

apps/dot_ext/tests/test_authorization.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import json
22
import base64
3+
from time import strftime
4+
35
import pytz
46
from datetime import datetime
57
from dateutil.relativedelta import relativedelta
@@ -520,6 +522,62 @@ def test_refresh_with_one_time_access_retrieve_app_from_auth_header(self):
520522
)
521523
self.assertEqual(response.status_code, 400)
522524

525+
@override_flag('limit_data_access', active=True)
526+
def test_dag_expiration_exists(self):
527+
assert flag_is_active('limit_data_access')
528+
redirect_uri = 'http://localhost'
529+
530+
# create a user
531+
user = self._create_user('anna', '123456')
532+
533+
# create an application and add capabilities
534+
application = self._create_application(
535+
'an app',
536+
grant_type=Application.GRANT_AUTHORIZATION_CODE,
537+
client_type=Application.CLIENT_CONFIDENTIAL,
538+
redirect_uris=redirect_uri,
539+
data_access_type="THIRTEEN_MONTH",
540+
)
541+
capability_a = self._create_capability('Capability A', [])
542+
application.scope.add(capability_a)
543+
544+
# create a data access grant
545+
expiration_date = datetime.now() + relativedelta(months=+13)
546+
dag = DataAccessGrant(beneficiary=user, application=application, expiration_date=expiration_date)
547+
dag.save()
548+
549+
# user logs in
550+
request = HttpRequest()
551+
self.client.login(request=request, username='anna', password='123456')
552+
553+
# post the authorization form with only one scope selected
554+
payload = {
555+
'client_id': application.client_id,
556+
'response_type': 'code',
557+
'redirect_uri': redirect_uri,
558+
'scope': ['capability-a'],
559+
'expires_in': 86400,
560+
'allow': True,
561+
}
562+
response = self.client.post(reverse('oauth2_provider:authorize'), data=payload)
563+
self.client.logout()
564+
565+
# now extract the authorization code and use it to request an access_token
566+
query_dict = parse_qs(urlparse(response['Location']).query)
567+
authorization_code = query_dict.pop('code')
568+
token_request_data = {
569+
'grant_type': 'authorization_code',
570+
'code': authorization_code,
571+
'redirect_uri': redirect_uri,
572+
'client_id': application.client_id,
573+
'client_secret': application.client_secret_plain,
574+
}
575+
c = Client()
576+
response = c.post('/v1/o/token/', data=token_request_data)
577+
tkn = response.json()
578+
expiration_date_string = strftime('%Y-%m-%d %H:%M:%SZ', expiration_date.timetuple())
579+
self.assertEqual(tkn["access_grant_expiration"][:-4], expiration_date_string[:-4])
580+
523581
def test_refresh_with_revoked_token(self):
524582
redirect_uri = 'http://localhost'
525583
# create a user

apps/dot_ext/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ def validate_app_is_active(request):
9595
Utility function to check that an application
9696
is an active, valid application.
9797
This method will pull the application from the
98-
request and then check the active flag.
98+
request and then check the active flag and the
99+
data access grant (dag) validity.
99100
RETURN:
100101
application or None
101102
"""

apps/dot_ext/views/authorization.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
import json
12
import logging
3+
from datetime import datetime, timedelta
4+
from time import strftime
5+
26
import waffle
37
from waffle import get_waffle_flag_model
48

5-
from django.http.response import HttpResponseBadRequest
9+
from django.http.response import HttpResponse, HttpResponseBadRequest
610
from django.template.response import TemplateResponse
711
from django.utils.decorators import method_decorator
812
from django.views.decorators.csrf import csrf_exempt
913
from django.views.decorators.debug import sensitive_post_parameters
1014
from oauth2_provider.exceptions import OAuthToolkitError
15+
from oauth2_provider.views.base import app_authorized, get_access_token_model
1116
from oauth2_provider.views.base import AuthorizationView as DotAuthorizationView
1217
from oauth2_provider.views.base import TokenView as DotTokenView
1318
from oauth2_provider.views.base import RevokeTokenView as DotRevokeTokenView
@@ -37,7 +42,7 @@
3742
validate_app_is_active,
3843
json_response_from_oauth2_error,
3944
)
40-
45+
from ...authorization.models import DataAccessGrant
4146

4247
log = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
4348

@@ -292,11 +297,48 @@ class TokenView(DotTokenView):
292297
@method_decorator(sensitive_post_parameters("password"))
293298
def post(self, request, *args, **kwargs):
294299
try:
295-
validate_app_is_active(request)
300+
app = validate_app_is_active(request)
296301
except (InvalidClientError, InvalidGrantError) as error:
297302
return json_response_from_oauth2_error(error)
298303

299-
return super().post(request, args, kwargs)
304+
url, headers, body, status = self.create_token_response(request)
305+
306+
if status == 200:
307+
body = json.loads(body)
308+
access_token = body.get("access_token")
309+
310+
dag_expiry = ""
311+
if access_token is not None:
312+
token = get_access_token_model().objects.get(
313+
token=access_token)
314+
app_authorized.send(
315+
sender=self, request=request,
316+
token=token)
317+
318+
if app.data_access_type == "THIRTEEN_MONTH":
319+
try:
320+
dag = DataAccessGrant.objects.get(
321+
beneficiary=token.user,
322+
application=app
323+
)
324+
if dag.expiration_date is not None:
325+
dag_expiry = strftime('%Y-%m-%d %H:%M:%SZ', dag.expiration_date.timetuple())
326+
except DataAccessGrant.DoesNotExist:
327+
dag_expiry = ""
328+
329+
elif app.data_access_type == "ONE_TIME":
330+
expires_at = datetime.utcnow() + timedelta(seconds=body['expires_in'])
331+
dag_expiry = expires_at.strftime('%Y-%m-%d %H:%M:%SZ')
332+
elif app.data_access_type == "RESEARCH_STUDY":
333+
dag_expiry = ""
334+
335+
body['access_grant_expiration'] = dag_expiry
336+
body = json.dumps(body)
337+
338+
response = HttpResponse(content=body, status=status)
339+
for k, v in headers.items():
340+
response[k] = v
341+
return response
300342

301343

302344
@method_decorator(csrf_exempt, name="dispatch")

0 commit comments

Comments
 (0)