Skip to content

Commit d6fd7da

Browse files
BLUEBUTTON-448 Allow Applications to have custom redirect schemes (#638)
* Allow Applications to have custom redirect schemes - Allows applications to define any uri scheme for redirects * Allow custom redirect_uri schemes from Admin
1 parent 91aa59d commit d6fd7da

File tree

3 files changed

+84
-4
lines changed

3 files changed

+84
-4
lines changed

apps/dot_ext/models.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import uuid
55
from datetime import datetime
6+
from urllib.parse import urlparse
67

78
from django.utils.dateparse import parse_duration
89
from django.core.urlresolvers import reverse
@@ -14,8 +15,6 @@
1415
from oauth2_provider.models import AbstractApplication
1516
from django.conf import settings
1617

17-
from apps.dot_ext.validators import validate_uris
18-
1918
logger = logging.getLogger('hhs_server.%s' % __name__)
2019

2120

@@ -30,7 +29,7 @@ class Application(AbstractApplication):
3029
help_text="This is typically a homepage for the application.")
3130
help_text = _('Allowed redirect URIs. Space or new line separated.')
3231
redirect_uris = models.TextField(help_text=help_text,
33-
validators=[validate_uris], blank=True)
32+
blank=True)
3433
logo_uri = models.CharField(
3534
default="", blank=True, max_length=512, verbose_name="Logo URI")
3635
tos_uri = models.CharField(
@@ -69,6 +68,14 @@ def allow_scopes(self, scopes):
6968
def get_absolute_url(self):
7069
return reverse('oauth2_provider:detail', args=[str(self.id)])
7170

71+
def get_allowed_schemes(self):
72+
allowed_schemes = []
73+
redirect_uris = self.redirect_uris.strip().split()
74+
for uri in redirect_uris:
75+
scheme = urlparse(uri).scheme
76+
allowed_schemes.append(scheme)
77+
return allowed_schemes
78+
7279
def save(self, commit=True, **kwargs):
7380
if commit:
7481
# Write the TOS that the app developer agreed to.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from oauth2_provider.compat import parse_qs, urlparse
2+
from django.core.urlresolvers import reverse
3+
4+
from apps.test import BaseApiTest
5+
from ..models import Application
6+
7+
8+
class TestAuthorizeWithCustomScheme(BaseApiTest):
9+
def test_post_with_valid_non_standard_scheme(self):
10+
redirect_uri = 'com.custom.bluebutton://example.it'
11+
# create a user
12+
self._create_user('anna', '123456')
13+
capability_a = self._create_capability('Capability A', [])
14+
capability_b = self._create_capability('Capability B', [])
15+
# create an application and add capabilities
16+
application = self._create_application(
17+
'an app',
18+
grant_type=Application.GRANT_AUTHORIZATION_CODE,
19+
redirect_uris=redirect_uri)
20+
application.scope.add(capability_a, capability_b)
21+
# user logs in
22+
self.client.login(username='anna', password='123456')
23+
# post the authorization form with only one scope selected
24+
payload = {
25+
'client_id': application.client_id,
26+
'response_type': 'code',
27+
'redirect_uri': redirect_uri,
28+
'scope': ['capability-a'],
29+
'expires_in': 86400,
30+
'allow': True,
31+
}
32+
response = self.client.post(reverse('oauth2_provider:authorize'), data=payload)
33+
34+
self.assertEqual(response.status_code, 302)
35+
# now extract the authorization code and use it to request an access_token
36+
query_dict = parse_qs(urlparse(response['Location']).query)
37+
authorization_code = query_dict.pop('code')
38+
token_request_data = {
39+
'grant_type': 'authorization_code',
40+
'code': authorization_code,
41+
'redirect_uri': redirect_uri,
42+
'client_id': application.client_id,
43+
}
44+
response = self.client.post(reverse('oauth2_provider:token'), data=token_request_data)
45+
self.assertEqual(response.status_code, 200)
46+
47+
def test_post_with_invalid_non_standard_scheme(self):
48+
redirect_uri = 'com.custom.bluebutton://example.it'
49+
bad_redirect_uri = 'com.custom.bad://example.it'
50+
# create a user
51+
self._create_user('anna', '123456')
52+
capability_a = self._create_capability('Capability A', [])
53+
capability_b = self._create_capability('Capability B', [])
54+
# create an application and add capabilities
55+
application = self._create_application(
56+
'an app',
57+
grant_type=Application.GRANT_AUTHORIZATION_CODE,
58+
redirect_uris=redirect_uri)
59+
application.scope.add(capability_a, capability_b)
60+
# user logs in
61+
self.client.login(username='anna', password='123456')
62+
# post the authorization form with only one scope selected
63+
payload = {
64+
'client_id': application.client_id,
65+
'response_type': 'code',
66+
'redirect_uri': bad_redirect_uri,
67+
'scope': ['capability-a'],
68+
'expires_in': 86400,
69+
'allow': True,
70+
}
71+
response = self.client.post(reverse('oauth2_provider:authorize'), data=payload)
72+
73+
self.assertEqual(response.status_code, 400)

apps/dot_ext/validators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __call__(self, value):
1717
scheme, netloc, path, query, fragment = urlsplit(value)
1818

1919
if scheme.lower() not in self.allowed_schemes:
20-
raise ValidationError('Invalid Redirect URI scheme: %s' % scheme.lower())
20+
raise ValidationError('Invalid Redirect URI scheme: %s, Must be one of %s' % (scheme.lower(), self.allowed_schemes))
2121

2222

2323
def validate_uris(value):

0 commit comments

Comments
 (0)