Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion oauth2_provider/tests/test_authorization_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def setUp(self):

self.application = Application(
name="Test Application",
redirect_uris="http://localhost http://example.com http://example.it",
redirect_uris="http://localhost http://example.com http://example.it mobile-app://callback",
user=self.dev_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
Expand Down Expand Up @@ -312,6 +312,27 @@ def test_code_post_auth_forbidden_redirect_uri(self):
response = self.client.post(reverse('oauth2_provider:authorize'), data=form_data)
self.assertEqual(response.status_code, 400)

def test_code_post_auth_custom_scheme_redirect_uri(self):
"""
Test non-default (http, https, ftp) schemes in redirect_uri are allowed
"""
self.client.login(username="test_user", password="123456")

form_data = {
'client_id': self.application.client_id,
'state': 'random_state_string',
'scope': 'read write',
'redirect_uri': 'mobile-app://callback',
'response_type': 'code',
'allow': True,
}

response = self.client.post(reverse('oauth2_provider:authorize'), data=form_data)
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].startswith('mobile-app://callback?'))
self.assertIn('state=random_state_string', response['Location'])
self.assertIn('code=', response['Location'])

def test_code_post_auth_malicious_redirect_uri(self):
"""
Test validation of a malicious redirect_uri
Expand Down
12 changes: 7 additions & 5 deletions oauth2_provider/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ..exceptions import OAuthToolkitError
from ..forms import AllowForm
from ..models import get_application_model
from .http import UnsafeHttpResponseRedirect
from .mixins import OAuthLibMixin

Application = get_application_model()
Expand Down Expand Up @@ -102,9 +103,8 @@ def form_valid(self, form):
allow = form.cleaned_data.get('allow')
uri, headers, body, status = self.create_authorization_response(
request=self.request, scopes=scopes, credentials=credentials, allow=allow)
self.success_url = uri
log.debug("Success url for the request: {0}".format(self.success_url))
return super(AuthorizationView, self).form_valid(form)
# uri is already safety-checked by create_authorization_response()
return UnsafeHttpResponseRedirect(uri)

except OAuthToolkitError as error:
return self.error_response(error)
Expand Down Expand Up @@ -135,7 +135,8 @@ def get(self, request, *args, **kwargs):
uri, headers, body, status = self.create_authorization_response(
request=self.request, scopes=" ".join(scopes),
credentials=credentials, allow=True)
return HttpResponseRedirect(uri)
# uri is already safety-checked by create_authorization_response()
return UnsafeHttpResponseRedirect(uri)

elif require_approval == 'auto':
tokens = request.user.accesstoken_set.filter(application=kwargs['application'],
Expand All @@ -146,7 +147,8 @@ def get(self, request, *args, **kwargs):
uri, headers, body, status = self.create_authorization_response(
request=self.request, scopes=" ".join(scopes),
credentials=credentials, allow=True)
return HttpResponseRedirect(uri)
# uri is already safety-checked by create_authorization_response()
return UnsafeHttpResponseRedirect(uri)

return self.render_to_response(self.get_context_data(**kwargs))

Expand Down
23 changes: 23 additions & 0 deletions oauth2_provider/views/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.http import HttpResponse
from django.utils.encoding import iri_to_uri


class UnsafeHttpResponseRedirectBase(HttpResponse):
"""
HttpResponseRedirectBase-like class that does not check the URI scheme.
You should validate user-controlled URIs before redirecting to them through
this class.
"""
def __init__(self, redirect_to, *args, **kwargs):
super(UnsafeHttpResponseRedirectBase, self).__init__(*args, **kwargs)
self['Location'] = iri_to_uri(redirect_to)

url = property(lambda self: self['Location'])


class UnsafeHttpResponseRedirect(UnsafeHttpResponseRedirectBase):
status_code = 302


class UnsafeHttpResponsePermanentRedirect(UnsafeHttpResponseRedirectBase):
status_code = 301