Skip to content

Commit

Permalink
BLUEBUTTON-720 R29 patch (#698)
Browse files Browse the repository at this point in the history
* Specify explicit migrations in order for Grant (#686)

* Order grant migrations to work against r27 (#689)

* Correct metrics view for data access grants (#693)

* Recreate client_uri field to work with db (#692)

* Recreate client_uri field to work with db

* Remove Removal of client_uri migration

* Make application info fields null-able

* Document depreciation of client_uri (#694)

* Trigger app_authorized signal on bene interaction (#695)

* Trigger app_authorized signal on bene interaction

* Correct migration order from r29 patch
  • Loading branch information
whytheplatypus authored Jan 28, 2019
1 parent 432fefd commit d4a5e0e
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 30 deletions.
4 changes: 2 additions & 2 deletions apps/authorization/migrations/0003_auto_20181203_1843.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class Migration(migrations.Migration):
atomic = False

dependencies = [
('oauth2_provider', '__latest__'),
('dot_ext', '__latest__'),
('oauth2_provider', '0006_auto_20171214_2232'),
('dot_ext', '0013_auto_20181221_2114'),
('authorization', '0002_auto_20181203_1542'),
]

Expand Down
11 changes: 8 additions & 3 deletions apps/authorization/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
AccessToken = get_access_token_model()


def app_authorized_record_grant(sender, request, token, **kwargs):
def app_authorized_record_grant(sender, request, token, application=None, **kwargs):
bene = request.user
if token is not None:
bene = token.user
application = token.application

DataAccessGrant.objects.get_or_create(
beneficiary=token.user,
application=token.application,
beneficiary=bene,
application=application,
)


Expand Down
4 changes: 0 additions & 4 deletions apps/dot_ext/migrations/0010_auto_20181219_2117.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ class Migration(migrations.Migration):
]

operations = [
migrations.RemoveField(
model_name='application',
name='client_uri',
),
migrations.AddField(
model_name='application',
name='website_uri',
Expand Down
31 changes: 31 additions & 0 deletions apps/dot_ext/migrations/0014_auto_20190117_1319.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 2.1.2 on 2019-01-17 13:19

import apps.dot_ext.validators
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('dot_ext', '0013_auto_20181221_2114'),
]

operations = [
migrations.AlterField(
model_name='application',
name='client_uri',
field=models.URLField(blank=True, default='', help_text='This is typically a home/download website for the application. For example, https://www.example.org or http://www.example.org .', max_length=512, null=True, verbose_name='Website URI'),
),
migrations.AlterField(
model_name='application',
name='description',
field=models.TextField(blank=True, default='', help_text='This is plain-text up to 1000 characters in length.', max_length=1000, null=True, validators=[apps.dot_ext.validators.validate_notags], verbose_name='Application Description'),
),
migrations.AlterField(
model_name='application',
name='website_uri',
field=models.URLField(blank=True, default='', help_text='This is typically a home/download website for the application. For example, https://www.example.org or http://www.example.org .', max_length=512, null=True, verbose_name='Website URI'),
),
]
2 changes: 1 addition & 1 deletion apps/dot_ext/migrations/0014_auto_20190121_1345.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class Migration(migrations.Migration):

dependencies = [
('dot_ext', '0013_auto_20181221_2114'),
('dot_ext', '0014_auto_20190117_1319'),
]

operations = [
Expand Down
10 changes: 8 additions & 2 deletions apps/dot_ext/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ class Application(AbstractApplication):
updated = models.DateTimeField(auto_now=True)
op_tos_uri = models.CharField(default=settings.TOS_URI, blank=True, max_length=512)
op_policy_uri = models.CharField(default="", blank=True, max_length=512)
website_uri = models.URLField(default="", blank=True, max_length=512, verbose_name="Website URI",

# client_uri is depreciated but will continued to be referenced until it can be removed safely
client_uri = models.URLField(default="", blank=True, null=True, max_length=512, verbose_name="Client URI",
help_text="This is typically a home/download website for the application. "
"For example, https://www.example.org or http://www.example.org .")

website_uri = models.URLField(default="", blank=True, null=True, max_length=512, verbose_name="Website URI",
help_text="This is typically a home/download website for the application. "
"For example, https://www.example.org or http://www.example.org .")
help_text = _('Allowed redirect URIs. Space or new line separated.')
Expand Down Expand Up @@ -62,7 +68,7 @@ class Application(AbstractApplication):
blank=True,
null=True)

description = models.TextField(default="", blank=True, max_length=1000,
description = models.TextField(default="", blank=True, null=True, max_length=1000,
verbose_name="Application Description",
help_text="This is plain-text up to 1000 characters in length.",
validators=[validate_notags])
Expand Down
72 changes: 57 additions & 15 deletions apps/dot_ext/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from apps.test import BaseApiTest
from ..models import Application
from apps.authorization.models import DataAccessGrant


class TestApplicationUpdateView(BaseApiTest):
Expand Down Expand Up @@ -91,24 +92,32 @@ def _create_test_token(self, user, application):
'expires_in': 86400,
'allow': True,
}
response = self.client.post(reverse('oauth2_provider:authorize'), data=payload)
if application.authorization_grant_type == Application.GRANT_IMPLICIT:
payload['response_type'] = 'token'
response = self.client.post('/v1/o/authorize/', data=payload)
self.client.logout()
if response.status_code != 302:
raise Exception(response.context_data)
self.assertEqual(response.status_code, 302)
# now extract the authorization code and use it to request an access_token
query_dict = parse_qs(urlparse(response['Location']).query)
authorization_code = query_dict.pop('code')
token_request_data = {
'grant_type': 'authorization_code',
'code': authorization_code,
'redirect_uri': application.redirect_uris,
'client_id': application.client_id,
'client_secret': application.client_secret,
}
if application.authorization_grant_type == Application.GRANT_IMPLICIT:
fragment = parse_qs(urlparse(response['Location']).fragment)
tkn = fragment.pop('access_token')[0]
else:
query_dict = parse_qs(urlparse(response['Location']).query)
authorization_code = query_dict.pop('code')
token_request_data = {
'grant_type': 'authorization_code',
'code': authorization_code,
'redirect_uri': application.redirect_uris,
'client_id': application.client_id,
'client_secret': application.client_secret,
}

response = self.client.post('/v1/o/token/', data=token_request_data)
self.assertEqual(response.status_code, 200)
# Now we have a token and refresh token
tkn = response.json()['access_token']
response = self.client.post('/v1/o/token/', data=token_request_data)
self.assertEqual(response.status_code, 200)
# Now we have a token and refresh token
tkn = response.json()['access_token']

t = AccessToken.objects.get(token=tkn)
return t
Expand Down Expand Up @@ -160,12 +169,29 @@ def test_delete_token_success(self):
'an app', grant_type=Application.GRANT_AUTHORIZATION_CODE,
redirect_uris='http://example.it')
application.scope.add(capability_a)
other_application = self._create_application(
'another app', grant_type=Application.GRANT_IMPLICIT,
client_type=Application.CLIENT_PUBLIC,
redirect_uris='http://example.it',
user=application.user,
)
other_application.scope.add(capability_a)
tkn = self._create_test_token(anna, application)

self.assertTrue(DataAccessGrant.objects.filter(
beneficiary=anna,
application=application,
).exists())

response = self.client.get('/v1/fhir/Patient',
HTTP_AUTHORIZATION="Bearer " + tkn.token)
self.assertEqual(response.status_code, 403)

bob_tkn = self._create_test_token(bob, application)
bob_tkn = self._create_test_token(bob, other_application)
self.assertTrue(DataAccessGrant.objects.filter(
beneficiary=bob,
application=other_application,
).exists())

response = self.client.get(reverse('token_management:token-list'),
HTTP_AUTHORIZATION=self._create_authorization_header(application.client_id,
Expand All @@ -186,11 +212,27 @@ def test_delete_token_success(self):
response = self.client.get('/v1/fhir/Patient',
HTTP_AUTHORIZATION="Bearer " + tkn.token)
self.assertEqual(response.status_code, 401)

self.assertFalse(DataAccessGrant.objects.filter(
beneficiary=anna,
application=application,
).exists())

response = self.client.get('/v1/fhir/Patient',
HTTP_AUTHORIZATION="Bearer " + bob_tkn.token)
# 403 is an expected response if the token is ok but the test can't reach the data server (which it shouldn't)
self.assertEqual(response.status_code, 403)

next_tkn = self._create_test_token(anna, application)
response = self.client.get('/v1/fhir/Patient',
HTTP_AUTHORIZATION="Bearer " + next_tkn.token)
self.assertEqual(response.status_code, 403)
# self.assertEqual(next_tkn.token, tkn.token)
self.assertTrue(DataAccessGrant.objects.filter(
beneficiary=anna,
application=application,
).exists())

def test_create_token_fail(self):
self._create_user(self.test_username, '123456')

Expand Down
7 changes: 7 additions & 0 deletions apps/dot_ext/views/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from oauth2_provider.views.base import AuthorizationView as DotAuthorizationView
from oauth2_provider.models import get_application_model
from oauth2_provider.exceptions import OAuthToolkitError
from oauth2_provider.signals import app_authorized
from ..forms import SimpleAllowForm
from ..models import Approval

Expand Down Expand Up @@ -49,6 +50,12 @@ def form_valid(self, form):
except OAuthToolkitError as error:
return self.error_response(error, application)

app_authorized.send(
sender=self,
request=self.request,
token=None,
application=application)

self.success_url = uri
log.debug("Success url for the request: {0}".format(self.success_url))
return self.redirect(self.success_url, application)
Expand Down
6 changes: 3 additions & 3 deletions apps/metrics/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,12 @@ class Meta:


class ArchivedDataAccessGrantSerializer(ModelSerializer):
user = UserSerializer(read_only=True)
beneficiary = UserSerializer(read_only=True)
application = ApplicationSerializer(read_only=True)

class Meta:
model = ArchivedDataAccessGrant
fields = ('beneficiary', 'application', 'created_at', 'archived_at', )
fields = ('beneficiary', 'application', 'created_at', 'archived_at', 'id', )


class ArchivedDataAccessGrantView(ListAPIView):
Expand Down Expand Up @@ -289,7 +289,7 @@ class DataAccessGrantSerializer(ModelSerializer):

class Meta:
model = DataAccessGrant
fields = ('beneficiary', 'application', 'created_at', )
fields = ('beneficiary', 'application', 'created_at', 'id', )


class DataAccessGrantView(ListAPIView):
Expand Down

0 comments on commit d4a5e0e

Please sign in to comment.