Skip to content

Commit 9949c72

Browse files
authored
Fix #277: Resolve LOGIN_REDIRECT_URL (#279)
1 parent 02f4a19 commit 9949c72

File tree

4 files changed

+76
-22
lines changed

4 files changed

+76
-22
lines changed

Diff for: djangosaml2/tests/__init__.py

+59-13
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,16 @@ def test_login_evil_redirect(self):
167167
idp_hosts=['idp.example.com'],
168168
metadata_file='remote_metadata_one_idp.xml',
169169
)
170-
response = self.client.get(
171-
reverse('saml2_login') + '?next=http://evil.com')
172-
url = urlparse(response['Location'])
173-
params = parse_qs(url.query)
174170

175-
self.assertEqual(params['RelayState'], [settings.LOGIN_REDIRECT_URL, ])
171+
for redirect_url in ['/dashboard/', 'testprofiles:dashboard']:
172+
with self.subTest(LOGIN_REDIRECT_URL=redirect_url):
173+
with override_settings(LOGIN_REDIRECT_URL=redirect_url):
174+
response = self.client.get(
175+
reverse('saml2_login') + '?next=http://evil.com')
176+
url = urlparse(response['Location'])
177+
params = parse_qs(url.query)
178+
179+
self.assertEqual(params['RelayState'], ['/dashboard/'])
176180

177181
def test_no_redirect(self):
178182
"""
@@ -186,11 +190,30 @@ def test_no_redirect(self):
186190
idp_hosts=['idp.example.com'],
187191
metadata_file='remote_metadata_one_idp.xml',
188192
)
189-
response = self.client.get(reverse('saml2_login') + '?next=')
190-
url = urlparse(response['Location'])
191-
params = parse_qs(url.query)
192193

193-
self.assertEqual(params['RelayState'], [settings.LOGIN_REDIRECT_URL, ])
194+
for redirect_url in ['/dashboard/', 'testprofiles:dashboard']:
195+
with self.subTest(LOGIN_REDIRECT_URL=redirect_url):
196+
with override_settings(LOGIN_REDIRECT_URL=redirect_url):
197+
response = self.client.get(reverse('saml2_login') + '?next=')
198+
url = urlparse(response['Location'])
199+
params = parse_qs(url.query)
200+
201+
self.assertEqual(params['RelayState'], ['/dashboard/'])
202+
203+
@override_settings(SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN=True)
204+
def test_login_already_logged(self):
205+
self.client.force_login(User.objects.create(username='user', password='pass'))
206+
207+
for redirect_url in ['/dashboard/', 'testprofiles:dashboard']:
208+
with self.subTest(LOGIN_REDIRECT_URL=redirect_url):
209+
with override_settings(LOGIN_REDIRECT_URL=redirect_url):
210+
with self.subTest('no next url'):
211+
response = self.client.get(reverse('saml2_login'))
212+
self.assertRedirects(response, '/dashboard/')
213+
214+
with self.subTest('evil next url'):
215+
response = self.client.get(reverse('saml2_login') + '?next=http://evil.com')
216+
self.assertRedirects(response, '/dashboard/')
194217

195218
def test_unknown_idp(self):
196219
# monkey patch SAML configuration
@@ -277,6 +300,7 @@ def test_login_several_idps(self):
277300
self.assertIn('AuthnRequest xmlns', decode_base64_and_inflate(
278301
saml_request).decode('utf-8'))
279302

303+
@override_settings(LOGIN_REDIRECT_URL='testprofiles:dashboard')
280304
def test_assertion_consumer_service(self):
281305
# Get initial number of users
282306
initial_user_count = User.objects.count()
@@ -325,14 +349,36 @@ def test_assertion_consumer_service(self):
325349
'SAMLResponse': self.b64_for_post(saml_response),
326350
'RelayState': came_from,
327351
})
328-
self.assertEqual(response.status_code, 302)
329-
location = response['Location']
330352

331-
url = urlparse(location)
332353
# as the RelayState is empty we have redirect to LOGIN_REDIRECT_URL
333-
self.assertEqual(url.path, settings.LOGIN_REDIRECT_URL)
354+
self.assertRedirects(response, '/dashboard/')
334355
self.assertEqual(force_text(new_user.id), client.session[SESSION_KEY])
335356

357+
@override_settings(LOGIN_REDIRECT_URL='testprofiles:dashboard')
358+
def test_assertion_consumer_service_default_relay_state(self):
359+
settings.SAML_CONFIG = conf.create_conf(
360+
sp_host='sp.example.com',
361+
idp_hosts=['idp.example.com'],
362+
metadata_file='remote_metadata_one_idp.xml',
363+
)
364+
365+
new_user = User.objects.create(username='teacher', password='not-used')
366+
367+
response = self.client.get(reverse('saml2_login'))
368+
saml2_req = saml2_from_httpredirect_request(response.url)
369+
session_id = get_session_id_from_saml2(saml2_req)
370+
371+
saml_response = auth_response(session_id, 'teacher')
372+
self.add_outstanding_query(session_id, '/')
373+
response = self.client.post(reverse('saml2_acs'), {
374+
'SAMLResponse': self.b64_for_post(saml_response),
375+
})
376+
self.assertEqual(response.status_code, 302)
377+
378+
# The RelayState is missing, redirect to LOGIN_REDIRECT_URL
379+
self.assertRedirects(response, '/dashboard/')
380+
self.assertEqual(force_text(new_user.id), self.client.session[SESSION_KEY])
381+
336382
def test_assertion_consumer_service_already_logged_in_allowed(self):
337383
self.client.force_login(User.objects.create(
338384
username='user', password='pass'))

Diff for: djangosaml2/utils.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from django.conf import settings
2222
from django.core.exceptions import ImproperlyConfigured
2323
from django.http import HttpResponse, HttpResponseRedirect
24+
from django.shortcuts import resolve_url
2425
from django.utils.http import is_safe_url
2526
from saml2.config import SPConfig
2627
from saml2.s_utils import UnknownSystemEntity
@@ -91,7 +92,7 @@ def validate_referral_url(request, url):
9192
getattr(settings, 'SAML_ALLOWED_HOSTS', [request.get_host()]))
9293

9394
if not is_safe_url(url=url, allowed_hosts=saml_allowed_hosts):
94-
return settings.LOGIN_REDIRECT_URL
95+
return resolve_url(settings.LOGIN_REDIRECT_URL)
9596
return url
9697

9798

Diff for: djangosaml2/views.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from django.core.exceptions import PermissionDenied, SuspiciousOperation
2525
from django.http import (HttpRequest, HttpResponse, HttpResponseBadRequest,
2626
HttpResponseRedirect, HttpResponseServerError)
27-
from django.shortcuts import render
27+
from django.shortcuts import render, resolve_url
2828
from django.template import TemplateDoesNotExist
2929
from django.urls import reverse
3030
from django.utils.decorators import method_decorator
@@ -116,7 +116,7 @@ def get_next_path(self, request: HttpRequest) -> str:
116116
If the user is already logged in (and if allowed), he will redirect to there immediately.
117117
'''
118118

119-
next_path = settings.LOGIN_REDIRECT_URL
119+
next_path = resolve_url(settings.LOGIN_REDIRECT_URL)
120120
if 'next' in request.GET:
121121
next_path = request.GET['next']
122122
elif 'RelayState' in request.GET:
@@ -501,8 +501,8 @@ def build_relay_state(self) -> str:
501501
""" The relay state is a URL used to redirect the user to the view where they came from.
502502
"""
503503
login_redirect_url = get_custom_setting('LOGIN_REDIRECT_URL', '/')
504-
default_relay_state = get_custom_setting(
505-
'ACS_DEFAULT_REDIRECT_URL', login_redirect_url)
504+
default_relay_state = resolve_url(
505+
get_custom_setting('ACS_DEFAULT_REDIRECT_URL', login_redirect_url))
506506
relay_state = self.request.POST.get('RelayState', default_relay_state)
507507
relay_state = self.customize_relay_state(relay_state)
508508
if not relay_state:

Diff for: tests/testprofiles/urls.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
from django.conf.urls import include, url
1+
from django.urls import include, path
22
from django.contrib import admin
3+
from django.http import HttpResponse
34

4-
app_name='testprofiles'
5+
testpatterns = (
6+
[
7+
path('dashboard/', lambda request: HttpResponse(''), name='dashboard')
8+
],
9+
'testprofiles' # app_name
10+
)
511

612
urlpatterns = [
7-
url(r'^saml2/', include('djangosaml2.urls')),
8-
url(r'^admin/', admin.site.urls),
13+
path('saml2/', include('djangosaml2.urls')),
14+
path('admin/', admin.site.urls),
15+
path('', include(testpatterns))
916
]

0 commit comments

Comments
 (0)