Skip to content

Commit 044f074

Browse files
committed
WIP
1 parent 39bcb1e commit 044f074

File tree

2 files changed

+75
-8
lines changed

2 files changed

+75
-8
lines changed

tests/test_admin.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.test.utils import override_settings
55

66
from two_factor.admin import patch_admin, unpatch_admin
7+
from two_factor.utils import default_device
78

89
from .utils import UserMixin
910

@@ -52,26 +53,32 @@ class OTPAdminSiteTest(UserMixin, TestCase):
5253

5354
def setUp(self):
5455
super().setUp()
55-
self.user = self.create_superuser()
56-
self.login_user()
56+
5757

5858
def test_otp_admin_without_otp(self):
5959
"""
60-
if user has admin permissions (is_staff and is_active)
61-
but doesnt have OTP setup, redirect the user to OTP setup page
60+
admins without MFA setup should be redirected to the setup page.
6261
"""
62+
self.user = self.create_superuser()
63+
self.login_user()
64+
print("user", self.user.is_active, self.user.is_staff)
6365
response = self.client.get('/otp_admin/', follow=True)
6466
redirect_to = reverse('two_factor:setup')
6567
self.assertRedirects(response, redirect_to)
6668

6769
@override_settings(LOGIN_URL='two_factor:login')
6870
def test_otp_admin_without_otp_named_url(self):
71+
self.user = self.create_superuser()
72+
self.login_user()
73+
print("user", self.user.is_active, self.user.is_staff)
6974
response = self.client.get('/otp_admin/', follow=True)
7075
redirect_to = reverse('two_factor:setup')
7176
self.assertRedirects(response, redirect_to)
7277

7378
def test_otp_admin_with_otp(self):
79+
self.user = self.create_superuser()
7480
self.enable_otp()
7581
self.login_user()
82+
print("user", self.user.is_active, self.user.is_staff)
7683
response = self.client.get('/otp_admin/')
7784
self.assertEqual(response.status_code, 200)

two_factor/admin.py

+64-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from functools import update_wrapper
12

23
from django.conf import settings
34
from django.contrib.admin import AdminSite
@@ -6,6 +7,9 @@
67
from django.http import HttpResponseRedirect
78
from django.shortcuts import resolve_url
89
from django.urls import reverse
10+
from django.views.decorators.cache import never_cache
11+
from django.views.decorators.csrf import csrf_protect
12+
913

1014
from .utils import monkeypatch_method
1115

@@ -25,33 +29,89 @@ class AdminSiteOTPRequiredMixin:
2529
use :meth:`has_permission` in order to secure those views.
2630
"""
2731

32+
def has_admin_permission(self, request):
33+
print('has_admin_permission, super', super().has_permission(request))
34+
print('has_admin_permission, AdminSite', AdminSite().has_permission(request) )
35+
return AdminSite().has_permission(request)
36+
2837
def has_permission(self, request):
2938
"""
3039
Returns True if the given HttpRequest has permission to view
3140
*at least one* page in the admin site.
3241
"""
33-
if not super().has_permission(request):
34-
return False
35-
return request.user.is_verified()
42+
print("AdminSiteOTPRequiredMixin.super", self.has_admin_permission(), request.user.is_verified())
43+
return self.has_admin_permission(request) and request.user.is_verified()
44+
45+
def admin_view(self, view, cacheable=False):
46+
"""
47+
Decorator to create an admin view attached to this ``AdminSite``. This
48+
wraps the view and provides permission checking by calling
49+
``self.has_permission``.
50+
51+
You'll want to use this from within ``AdminSite.get_urls()``:
3652
53+
class MyAdminSite(AdminSite):
54+
55+
def get_urls(self):
56+
from django.urls import path
57+
58+
urls = super().get_urls()
59+
urls += [
60+
path('my_view/', self.admin_view(some_view))
61+
]
62+
return urls
63+
64+
By default, admin_views are marked non-cacheable using the
65+
``never_cache`` decorator. If the view can be safely cached, set
66+
cacheable=True.
67+
"""
68+
def inner(request, *args, **kwargs):
69+
print("AdminSiteOTPRequiredMixin.admin_view.inner", )
70+
if not self.has_permission(request):
71+
if request.path == reverse('admin:logout', current_app=self.name):
72+
index_path = reverse('admin:index', current_app=self.name)
73+
return HttpResponseRedirect(index_path)
74+
# Inner import to prevent django.contrib.admin (app) from
75+
# importing django.contrib.auth.models.User (unrelated model).
76+
from django.contrib.auth.views import redirect_to_login
77+
return redirect_to_login(
78+
request.get_full_path(),
79+
reverse('admin:login', current_app=self.name)
80+
)
81+
return view(request, *args, **kwargs)
82+
if not cacheable:
83+
inner = never_cache(inner)
84+
# We add csrf_protect here so this function can be used as a utility
85+
# function for any view, without having to repeat 'csrf_protect'.
86+
if not getattr(view, 'csrf_exempt', False):
87+
inner = csrf_protect(inner)
88+
return update_wrapper(inner, view)
89+
90+
@never_cache
3791
def login(self, request, extra_context=None):
3892
"""
3993
Redirects to the site login page for the given HttpRequest.
4094
If user has admin permissions but 2FA not setup, then redirect to
4195
2FA setup page.
4296
"""
97+
print("AdminSiteOTPRequiredMixin.login")
98+
4399
# redirect to admin page after login
44100
redirect_to = request.POST.get(REDIRECT_FIELD_NAME, request.GET.get(REDIRECT_FIELD_NAME))
101+
has_admin_access = AdminSite().has_permission(request)
102+
print("AdminSiteOTPRequiredMixin.login", redirect_to, request.method, has_admin_access)
45103

46104
# if user (is_active and is_staff)
47-
if request.method == "GET" and AdminSite().has_permission(request):
105+
if request.method == "GET" and has_admin_access:
48106

49107
# if user has 2FA setup, go to admin homepage
50108
if request.user.is_verified():
109+
print("User is verified, going to normal index.")
51110
index_path = reverse("admin:index", current_app=self.name)
52111

53112
# 2FA not setup. redirect to 2FA setup page
54113
else:
114+
print("User is not verified. redirecting to two_factor setup.")
55115
index_path = reverse("two_factor:setup", current_app=self.name)
56116

57117
return HttpResponseRedirect(index_path)

0 commit comments

Comments
 (0)