Skip to content

Commit a315d46

Browse files
committed
Merge pull request #4 from RealGeeks/issue-3
redirect to same url you were on with auth failed
2 parents 93e60cc + aa12f45 commit a315d46

File tree

3 files changed

+105
-5
lines changed

3 files changed

+105
-5
lines changed

oauthadmin/views.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from urllib import quote_plus
66

77
from django.shortcuts import redirect
8-
from django.core.urlresolvers import reverse
8+
from django.core.urlresolvers import reverse, NoReverseMatch
99
from django.http import HttpResponseRedirect
1010

1111
from oauthadmin.utils import import_by_path
@@ -22,9 +22,32 @@ def destroy_session(request):
2222
pass
2323

2424
def login(request):
25+
# this view can be called directly by django admin site from
26+
# any url, or can be accessed by the login url if the urls
27+
# from this app were included
28+
try:
29+
login_url = reverse('oauthadmin.views.login')
30+
except NoReverseMatch:
31+
login_uri = None
32+
33+
if request.path == login_url:
34+
# if this view is being accessed from login url look for 'next'
35+
# in query string to use as destination after the login is complete
36+
next = request.GET.get('next')
37+
else:
38+
# otherwise the django admin site called this view from another view.
39+
# Django admin doesn't redirect to login url if login is required, it
40+
# calls the view directly (django 1.7 fixed this and redirects and we
41+
# don't support it yet)
42+
next = request.get_full_path()
43+
44+
redirect_uri = request.build_absolute_uri(reverse('oauthadmin.views.callback'))
45+
if next:
46+
redirect_uri += '?next='+next
47+
2548
oauth = OAuth2Session(
2649
client_id=app_setting('CLIENT_ID'),
27-
redirect_uri=request.build_absolute_uri(reverse('oauthadmin.views.callback')),
50+
redirect_uri=redirect_uri,
2851
scope=["default"],
2952
)
3053
authorization_url, state = oauth.authorization_url(app_setting('AUTH_URL'))
@@ -53,7 +76,9 @@ def callback(request):
5376
request.session['oauth_token'] = token
5477
request.session['user'] = user
5578

56-
return redirect(request.build_absolute_uri('/admin'))
79+
next = request.GET.get('next', '/admin')
80+
81+
return redirect(request.build_absolute_uri(next))
5782

5883

5984
def logout(request):

settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
ROOT_URLCONF = "oauthadmin.urls"
88
SECRET_KEY = "secret"
99
ALLOWED_HOSTS = ['testserver']
10+
11+
OAUTHADMIN_CLIENT_ID = 'test-client-id'

test/test_views.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from oauthadmin.views import destroy_session, login, callback, logout
44
from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, InvalidGrantError
55
from django.test.client import RequestFactory
6+
from django.core.urlresolvers import reverse
67

78

89
SESSION_VARIABLES = ['oauth_state', 'oauth_token', 'uid', 'user']
@@ -40,16 +41,69 @@ def test_login(app_setting, OAuth2Session, request_factory):
4041
OAuth2Session.return_value = mock.Mock(
4142
authorization_url = mock.Mock(return_value = ('https://foo', 'state-variable'))
4243
)
43-
request = request_factory.post('/')
44+
request = request_factory.get(reverse('oauthadmin.views.login'))
4445
request.session = {}
4546
request.build_absolute_uri = mock.Mock(return_value='https://test.com/construct-redirect')
4647

4748
app_setting.return_value = 'app-setting'
4849

4950
resp = login(request)
5051
assert resp.status_code == 302
52+
assert resp['location'] == 'https://foo'
5153
assert request.session.get('oauth_state') == 'state-variable'
5254

55+
@mock.patch('oauthadmin.views.OAuth2Session')
56+
def test_login_redirect_uri(OAuth2Session, request_factory):
57+
OAuth2Session.return_value = mock.Mock(
58+
authorization_url = mock.Mock(return_value = ('https://foo', 'state-variable'))
59+
)
60+
request = request_factory.get(reverse('oauthadmin.views.login'))
61+
request.session = {}
62+
request.build_absolute_uri = mock.Mock(return_value='https://test.com/construct-redirect')
63+
64+
resp = login(request)
65+
66+
OAuth2Session.assert_called_once_with(
67+
client_id = 'test-client-id',
68+
redirect_uri = u'https://test.com/construct-redirect',
69+
scope = ['default'],
70+
)
71+
72+
@mock.patch('oauthadmin.views.OAuth2Session')
73+
def test_login_redirect_uri_with_next_from_url(OAuth2Session, request_factory):
74+
OAuth2Session.return_value = mock.Mock(
75+
authorization_url = mock.Mock(return_value = ('https://foo', 'state-variable'))
76+
)
77+
request = request_factory.get(reverse('oauthadmin.views.login') + '?next=/admin/content/')
78+
request.session = {}
79+
request.build_absolute_uri = mock.Mock(return_value='https://test.com/construct-redirect')
80+
81+
resp = login(request)
82+
83+
OAuth2Session.assert_called_once_with(
84+
redirect_uri = u'https://test.com/construct-redirect?next=/admin/content/',
85+
client_id = mock.ANY,
86+
scope = mock.ANY,
87+
)
88+
89+
@mock.patch('oauthadmin.views.OAuth2Session')
90+
def test_login_redirect_uri_with_next_as_current_url(OAuth2Session, request_factory):
91+
OAuth2Session.return_value = mock.Mock(
92+
authorization_url = mock.Mock(return_value = ('https://foo', 'state-variable'))
93+
)
94+
request = request_factory.get('/admin/content/')
95+
request.session = {}
96+
request.build_absolute_uri = mock.Mock(return_value='https://test.com/construct-redirect')
97+
98+
resp = login(request)
99+
100+
OAuth2Session.assert_called_once_with(
101+
redirect_uri = u'https://test.com/construct-redirect?next=/admin/content/',
102+
client_id = mock.ANY,
103+
scope = mock.ANY,
104+
)
105+
106+
53107
@mock.patch('oauthadmin.views.OAuth2Session')
54108
@mock.patch('oauthadmin.views.app_setting')
55109
@mock.patch('oauthadmin.views.import_by_path')
@@ -89,7 +143,7 @@ def test_callback_with_invalid_grant(import_by_path, app_setting, OAuth2Session,
89143
@mock.patch('oauthadmin.views.app_setting')
90144
@mock.patch('oauthadmin.views.import_by_path')
91145
def test_callback(import_by_path, app_setting, OAuth2Session, request_factory):
92-
request = request_factory.get('/')
146+
request = request_factory.get(reverse('oauthadmin.views.callback'))
93147
request.session = {'oauth_state': 'state-variable'}
94148
OAuth2Session.return_value = mock.Mock(
95149
fetch_token = mock.Mock(return_value = 'token')
@@ -101,9 +155,28 @@ def test_callback(import_by_path, app_setting, OAuth2Session, request_factory):
101155

102156
resp = callback(request)
103157
assert resp.status_code == 302
158+
assert resp['location'] == 'http://testserver/admin'
104159
assert request.session.get('oauth_token') == 'token'
105160
assert request.session.get('user') == 'test-user'
106161

162+
@mock.patch('oauthadmin.views.OAuth2Session')
163+
@mock.patch('oauthadmin.views.app_setting')
164+
@mock.patch('oauthadmin.views.import_by_path')
165+
def test_callback_redirect_to_next(import_by_path, app_setting, OAuth2Session, request_factory):
166+
request = request_factory.get(reverse('oauthadmin.views.callback') + '?next=/admin/content/')
167+
request.session = {'oauth_state': 'state-variable'}
168+
OAuth2Session.return_value = mock.Mock(
169+
fetch_token = mock.Mock(return_value = 'token')
170+
)
171+
app_setting.return_value = 'app-setting'
172+
ibp = mock.Mock()
173+
ibp.return_value = 'test-user'
174+
import_by_path.return_value = ibp
175+
176+
resp = callback(request)
177+
assert resp.status_code == 302
178+
assert resp['location'] == 'http://testserver/admin/content/'
179+
107180
@mock.patch('oauthadmin.views.OAuth2Session')
108181
@mock.patch('oauthadmin.views.app_setting')
109182
def test_logout(app_setting, OAuth2Session, request_factory):

0 commit comments

Comments
 (0)