Make next url authentication backend dependent (#1609)

* Make next url authentication backend dependent

* Rename authentication next_url to get_next_url.

* Add test for custom authentication backend get_next_url.

* Fix typo in docstring of authentication backend get_next_url.
This commit is contained in:
Maico Timmerman
2020-03-15 11:05:57 +01:00
committed by GitHub
parent ca0407a133
commit 9a32668ee1
5 changed files with 35 additions and 10 deletions

View File

@@ -85,6 +85,16 @@ class BaseAuthBackend:
"""
return
def get_next_url(self, request):
"""
This method will be called after a successful login to determine the next URL. Pretix in general uses the
``'next'`` query parameter. However, external authentication methods could use custom attributes with hardcoded
names for security purposes. For example, OAuth uses ``'state'`` for keeping track of application state.
"""
if "next" in request.GET:
return request.GET.get("next")
return None
class NativeAuthBackend(BaseAuthBackend):
identifier = 'native'

View File

@@ -39,18 +39,19 @@ def process_login(request, user, keep_logged_in):
:return: This method returns a ``HttpResponse``.
"""
request.session['pretix_auth_long_session'] = settings.PRETIX_LONG_SESSIONS and keep_logged_in
next_url = get_auth_backends()[user.auth_backend].get_next_url(request)
if user.require_2fa:
request.session['pretix_auth_2fa_user'] = user.pk
request.session['pretix_auth_2fa_time'] = str(int(time.time()))
twofa_url = reverse('control:auth.login.2fa')
if "next" in request.GET and is_safe_url(request.GET.get("next"), allowed_hosts=None):
twofa_url += '?next=' + quote(request.GET.get('next'))
if next_url and is_safe_url(next_url, allowed_hosts=None):
twofa_url += '?next=' + quote(next_url)
return redirect(twofa_url)
else:
auth_login(request, user)
request.session['pretix_auth_login_time'] = int(time.time())
if "next" in request.GET and is_safe_url(request.GET.get("next"), allowed_hosts=None):
return redirect(request.GET.get("next"))
if next_url and is_safe_url(next_url, allowed_hosts=None):
return redirect(next_url)
return redirect(reverse('control:index'))
@@ -72,7 +73,8 @@ def login(request):
if not backend.visible:
backend = [b for b in backends if b.visible][0]
if request.user.is_authenticated:
return redirect(request.GET.get("next", 'control:index'))
next_url = backend.get_next_url(request) or 'control:index'
return redirect(next_url)
if request.method == 'POST':
form = LoginForm(backend=backend, data=request.POST)
if form.is_valid() and form.user_cache and form.user_cache.auth_backend == backend.identifier:

View File

@@ -101,18 +101,21 @@ class ReauthView(TemplateView):
t = int(time.time())
request.session['pretix_auth_login_time'] = t
request.session['pretix_auth_last_used'] = t
if "next" in request.GET and is_safe_url(request.GET.get("next"), allowed_hosts=None):
return redirect(request.GET.get("next"))
next_url = get_auth_backends()[request.user.auth_backend].get_next_url(request)
if next_url and is_safe_url(next_url, allowed_hosts=None):
return redirect(next_url)
return redirect(reverse('control:index'))
else:
messages.error(request, _('The password you entered was invalid, please try again.'))
return self.get(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
u = get_auth_backends()[request.user.auth_backend].request_authenticate(request)
backend = get_auth_backends()[request.user.auth_backend]
u = backend.request_authenticate(request)
if u and u == request.user:
if "next" in request.GET and is_safe_url(request.GET.get("next"), allowed_hosts=None):
return redirect(request.GET.get("next"))
next_url = backend.get_next_url(request)
if next_url and is_safe_url(next_url, allowed_hosts=None):
return redirect(next_url)
return redirect(reverse('control:index'))
return super().get(request, *args, **kwargs)

View File

@@ -148,6 +148,11 @@ class LoginFormTest(TestCase):
response = self.client.get('/control/')
assert b'hallo@example.org' in response.content
def test_custom_get_next_url(self):
response = self.client.get('/control/login?state=/control/events/', HTTP_X_LOGIN_EMAIL='hallo@example.org')
self.assertEqual(response.status_code, 302)
self.assertIn('/control/events/', response['Location'])
class RegistrationFormTest(TestCase):

View File

@@ -36,3 +36,8 @@ class TestRequestAuthBackend(BaseAuthBackend):
email=request.headers['X-Login-Email'],
auth_backend='test_request'
)[0]
def get_next_url(self, request):
if 'state' in request.GET:
return request.GET.get('state')
return None