Skip to content

Commit b740134

Browse files
committed
tighten /admin/ access to superusers only
1 parent b752d30 commit b740134

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

dojo/admin.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@
1414
TextQuestion,
1515
)
1616

17+
18+
# Django's default admin gate is `is_active and is_staff`. Under the legacy
19+
# OS auth model is_staff is already a near-superuser bypass for queryset
20+
# filters and most permission checks, so the standard UserAdmin change form
21+
# would let any is_staff user with auth.change_user tick is_superuser on
22+
# themselves. Rather than narrowing each ModelAdmin individually, restrict
23+
# the entire admin site to superusers — there is no DefectDojo OS role that
24+
# legitimately needs /admin/ access without being a superuser.
25+
def _admin_site_has_permission(request):
26+
return request.user.is_active and request.user.is_superuser
27+
28+
29+
admin.site.has_permission = _admin_site_has_permission
30+
1731
# Conditionally unregister LogEntry from auditlog if it's registered
1832
try:
1933
from auditlog.models import LogEntry

unittests/test_adminsite.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import django.apps
22
from django.contrib import admin
3+
from django.contrib.auth import get_user_model
4+
from django.urls import reverse
35

46
from .dojo_test_case import DojoTestCase
57

@@ -20,3 +22,43 @@ def test_is_model_defined(self):
2022
else:
2123
with self.subTest(type="tag", subclass=subclass):
2224
self.assertIn(subclass, admin.site._registry.keys(), f"{subclass} is not registered in 'tagulous.admin' in models.py")
25+
26+
27+
class AdminAccessGate(DojoTestCase):
28+
29+
"""
30+
is_staff is a near-superuser bypass under the legacy OS auth model, so
31+
/admin/ must require is_superuser. Django's default UserAdmin change
32+
form would otherwise let any is_staff user with auth.change_user tick
33+
is_superuser on themselves.
34+
"""
35+
36+
def test_staff_non_superuser_blocked_from_admin(self):
37+
User = get_user_model()
38+
staff = User.objects.create_user(
39+
username="staff-no-root", password="x", is_staff=True,
40+
)
41+
self.client.force_login(staff)
42+
43+
for url in (
44+
reverse("admin:index"),
45+
reverse("admin:auth_user_changelist"),
46+
reverse("admin:auth_user_change", args=[staff.id]),
47+
):
48+
with self.subTest(url=url):
49+
resp = self.client.get(url)
50+
self.assertEqual(resp.status_code, 302, url)
51+
self.assertIn("/admin/login/", resp["Location"], url)
52+
53+
def test_superuser_can_reach_admin(self):
54+
User = get_user_model()
55+
root = User.objects.create_superuser(
56+
username="root-test", email="r@example.com", password="x",
57+
)
58+
self.client.force_login(root)
59+
60+
self.assertEqual(self.client.get(reverse("admin:index")).status_code, 200)
61+
self.assertEqual(
62+
self.client.get(reverse("admin:auth_user_changelist")).status_code,
63+
200,
64+
)

0 commit comments

Comments
 (0)