diff --git a/tests/test_admin.py b/tests/test_admin.py index 022d01b57..6659868b3 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -6,19 +6,6 @@ @override_settings(ROOT_URLCONF='tests.urls_admin') -class AdminSiteTest(UserMixin, TestCase): - - def setUp(self): - super().setUp() - self.user = self.create_superuser() - self.login_user() - - def test_default_admin(self): - response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) - - -@override_settings(ROOT_URLCONF='tests.urls_otp_admin') class OTPAdminSiteTest(UserMixin, TestCase): """ otp_admin is admin console that needs OTP for access. @@ -26,41 +13,67 @@ class OTPAdminSiteTest(UserMixin, TestCase): with OTP can access it. """ - def test_admin_not_authenticated_with_otp_enabled(self): - response = self.client.get('/otp_admin/', follow=True) - redirect_to = '%s?next=/otp_admin/' % reverse('admin:login') + def test_anonymous_get_admin_index_redirects_to_admin_login(self): + index_url = reverse('admin:index') + login_url = reverse('admin:login') + response = self.client.get(index_url, follow=True) + redirect_to = '%s?next=%s' % (login_url, index_url) self.assertRedirects(response, redirect_to) - def test_otp_admin_without_otp(self): + def test_anonymous_get_admin_logout_redirects_to_admin_index(self): + # see: django.tests.admin_views.test_client_logout_url_can_be_used_to_login + index_url = reverse('admin:index') + logout_url = reverse('admin:logout') + response = self.client.get(logout_url) + self.assertEqual( + response.status_code, 302 + ) + self.assertEqual(response.headers.get('Location'), index_url) + + def test_anonymous_get_admin_login(self): + index_url = reverse('admin:index') + login_url = reverse('admin:login') + + response = self.client.get(login_url, follow=True) + self.assertEqual(response.status_code, 200) + redirect_to = '%s?next=%s' % (login_url, index_url) + self.assertRedirects(response, redirect_to) + + def test_is_staff_not_verified_not_setup_get_admin_index_redirects_to_setup(self): """ admins without MFA setup should be redirected to the setup page. """ + index_url = reverse('admin:index') + setup_url = reverse('two_factor:setup') self.user = self.create_superuser() self.login_user() - response = self.client.get('/otp_admin/', follow=True) - redirect_to = '%s?next=/admin/' % reverse('two_factor:setup') + response = self.client.get(index_url, follow=True) + redirect_to = '%s?next=%s' % (setup_url, index_url) self.assertRedirects(response, redirect_to) - def test_otp_admin_without_otp_named_url(self): + def test_is_staff_not_verified_not_setup_get_admin_login_redirects_to_setup(self): + index_url = reverse('admin:index') + login_url = reverse('admin:login') + setup_url = reverse('two_factor:setup') self.user = self.create_superuser() self.login_user() - response = self.client.get('/otp_admin/', follow=True) - redirect_to = '%s?next=/admin/' % reverse('two_factor:setup') + response = self.client.get(login_url, follow=True) + redirect_to = '%s?next=%s' % (setup_url, index_url) self.assertRedirects(response, redirect_to) - def test_otp_admin_with_otp(self): + def test_is_staff_is_verified_get_admin_index(self): + index_url = reverse('admin:index') self.user = self.create_superuser() self.enable_otp(self.user) self.login_user() - response = self.client.get('/otp_admin/') + response = self.client.get(index_url) self.assertEqual(response.status_code, 200) - def test_client_logout_url_can_be_used_to_login(self): - # see: django.tests.admin_views.test_client_logout_url_can_be_used_to_login - admin_logout_url = reverse('admin:logout') - response = self.client.get(admin_logout_url) - self.assertEqual( - response.status_code, 302 - ) - admin_index_url = reverse('admin:index') - self.assertEqual(response.headers.get('Location'), admin_index_url) + def test_is_staff_is_verified_get_admin_login_redirects_to_admin_index(self): + login_url = reverse('admin:login') + index_url = reverse('admin:index') + self.user = self.create_superuser() + self.enable_otp(self.user) + self.login_user() + response = self.client.get(login_url) + self.assertEqual(response.headers.get('Location'), index_url) diff --git a/tests/urls_admin.py b/tests/urls_admin.py index 64a073faa..881215767 100644 --- a/tests/urls_admin.py +++ b/tests/urls_admin.py @@ -1,8 +1,9 @@ -from django.contrib import admin from django.urls import path +from two_factor.admin import TwoFactorAdminSite + from .urls import urlpatterns urlpatterns += [ - path('admin/', admin.site.urls), + path('admin/', TwoFactorAdminSite().urls), ] diff --git a/tests/urls_otp_admin.py b/tests/urls_otp_admin.py deleted file mode 100644 index 32210cb68..000000000 --- a/tests/urls_otp_admin.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path - -from two_factor.admin import AdminSiteOTPRequired - -from .urls import urlpatterns - -otp_admin_site = AdminSiteOTPRequired() - -urlpatterns += [ - path('otp_admin/', otp_admin_site.urls), -] diff --git a/two_factor/admin.py b/two_factor/admin.py index 9bbedf2d4..6220e7ec3 100644 --- a/two_factor/admin.py +++ b/two_factor/admin.py @@ -67,14 +67,6 @@ def login(self, request, extra_context=None): if self.has_admin_permission(request): if not self.has_mfa_setup(request): return self.redirect_to_mfa_setup(request) - else: - # A user can get here if they logged in through the site - # in a way that didn't require OTP. This can happen when sites - # require MFA for only some pages. At his point a user should be - # prompted for MFA verification. - # TODO: Determine if OTP verification needs to be implemented or - # if the login form will happily go straight to - pass # Since this module gets imported in the application's root package, # it cannot import models from other applications at the module level, @@ -94,9 +86,12 @@ def login(self, request, extra_context=None): ): context[REDIRECT_FIELD_NAME] = reverse("admin:index", current_app=self.name) context.update(extra_context or {}) + defaults = { + 'extra_context': context, + } request.current_app = self.name - return LoginView.as_view()(request) + return LoginView.as_view(**defaults)(request, context) def admin_view(self, view, cacheable=False): """