diff --git a/tests/test_views_login.py b/tests/test_views_login.py index 86441bde8..a43114845 100644 --- a/tests/test_views_login.py +++ b/tests/test_views_login.py @@ -38,7 +38,7 @@ def test_valid_login(self, mock_signal): response = self._post({'auth-username': 'bouke@example.com', 'auth-password': 'secret', 'login_view-current_step': 'auth'}) - self.assertRedirects(response, reverse('two_factor:setup')) + self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL)) # No signal should be fired for non-verified user logins. self.assertFalse(mock_signal.called) @@ -80,8 +80,7 @@ def test_valid_login_with_allowed_external_redirect(self): {'auth-username': 'bouke@example.com', 'auth-password': 'secret', 'login_view-current_step': 'auth'}) - self.assertEqual(self.client.session.get('next'), redirect_url) - self.assertRedirects(response, reverse('two_factor:setup'), fetch_redirect_response=False) + self.assertRedirects(response, redirect_url, fetch_redirect_response=False) def test_valid_login_with_disallowed_external_redirect(self): redirect_url = 'https://test.disallowed-success-url.com' @@ -91,7 +90,7 @@ def test_valid_login_with_disallowed_external_redirect(self): {'auth-username': 'bouke@example.com', 'auth-password': 'secret', 'login_view-current_step': 'auth'}) - self.assertRedirects(response, reverse('two_factor:setup'), fetch_redirect_response=False) + self.assertRedirects(response, reverse('two_factor:profile'), fetch_redirect_response=False) @mock.patch('two_factor.views.core.time') def test_valid_login_primary_key_stored(self, mock_time): @@ -396,12 +395,12 @@ def test_login_different_user_on_existing_session(self, mock_logger): response = self._post({'auth-username': 'bouke@example.com', 'auth-password': 'secret', 'login_view-current_step': 'auth'}) - self.assertRedirects(response, reverse('two_factor:setup')) + self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL)) response = self._post({'auth-username': 'vedran@example.com', 'auth-password': 'secret', 'login_view-current_step': 'auth'}) - self.assertRedirects(response, reverse('two_factor:setup')) + self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL)) def test_missing_management_data(self): # missing management data @@ -432,7 +431,7 @@ def test_login_different_user_with_otp_on_existing_session(self): response = self._post({'auth-username': 'bouke@example.com', 'auth-password': 'secret', 'login_view-current_step': 'auth'}) - self.assertRedirects(response, reverse('two_factor:setup')) + self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL)) response = self._post({'auth-username': 'vedran@example.com', 'auth-password': 'secret', diff --git a/two_factor/views/core.py b/two_factor/views/core.py index 1defd0fe5..1cd517706 100644 --- a/two_factor/views/core.py +++ b/two_factor/views/core.py @@ -18,7 +18,7 @@ from django.forms import Form, ValidationError from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import redirect, resolve_url -from django.urls import reverse +from django.urls import reverse, resolve, Resolver404 from django.utils.decorators import method_decorator from django.utils.functional import cached_property from django.utils.http import url_has_allowed_host_and_scheme @@ -38,6 +38,7 @@ from two_factor.plugins.phonenumber.utils import get_available_phone_methods from two_factor.plugins.registry import registry from two_factor.utils import totp_digits +from two_factor.views.mixins import OTPRequiredMixin from ..forms import ( AuthenticationTokenForm, BackupTokenForm, DeviceValidationForm, MethodForm, @@ -154,6 +155,10 @@ def done(self, form_list, **kwargs): device = getattr(self.get_user(), 'otp_device', None) response = redirect(redirect_to) + try: + next_resolver_match = resolve(self.request.GET.get('next')) + except Resolver404: + next_resolver_match = None if device: signals.user_verified.send(sender=__name__, request=self.request, user=self.get_user(), device=device) @@ -173,16 +178,17 @@ def done(self, form_list, **kwargs): httponly=getattr(settings, 'TWO_FACTOR_REMEMBER_COOKIE_HTTPONLY', True), samesite=getattr(settings, 'TWO_FACTOR_REMEMBER_COOKIE_SAMESITE', 'Lax'), ) - return response # If the user does not have a device. - else: + elif next_resolver_match and issubclass(next_resolver_match.func.view_class, OTPRequiredMixin): if self.request.GET.get('next'): self.request.session['next'] = self.get_success_url() return redirect('two_factor:setup') - # Copied from django.contrib.auth.views.LoginView (Branch: stable/1.11.x) + return response + + # Copied from django.conrib.auth.views.LoginView (Branch: stable/1.11.x) # https://github.com/django/django/blob/58df8aa40fe88f753ba79e091a52f236246260b3/django/contrib/auth/views.py#L63 def get_success_url(self): url = self.get_redirect_url()