Skip to content

Commit 3f49182

Browse files
committed
fix(authorization): restore is_staff bypass for configuration permissions
Under legacy authorization, is_staff is the catch-all bypass for object and global permissions (see dojo/authorization/authorization.py docstring + the existing user_has_global_permission code path). The configuration-permission helper was the odd one out — it delegated straight to Django's user.has_perm() and ignored is_staff, so a staff user without explicit auth.* grants was 403'd by views and viewsets gated on user_is_configuration_authorized / user_has_configuration_- permission_or_403 (e.g. /user, /group, /api/v2/users/, etc.). Add the is_staff (and is_superuser) bypass so configuration permissions match the rest of the legacy contract. Django's user.has_perm() stays as the fallback so explicit grants on non-staff users continue to work. Pro overrides this function at runtime via pro/apps.py:_shadow_authorization_symbols, so the OS bypass does not leak into Pro deployments where RBAC governs configuration access.
1 parent 83d2ff0 commit 3f49182

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

dojo/authorization/authorization.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,21 @@
5959

6060

6161
def user_has_configuration_permission(user: Dojo_User, permission: str):
62+
"""
63+
Legacy: configuration permissions reduce to is_superuser / is_staff,
64+
matching the rest of the legacy auth model. ``user.has_perm`` is
65+
still consulted as a fallback so explicit Django permission grants
66+
(e.g. ``auth.add_user`` granted via Django Admin) keep working for
67+
non-staff users. Pro overrides this function at runtime via
68+
pro/apps.py:_shadow_authorization_symbols, so this OS bypass does
69+
not affect Pro deployments.
70+
"""
6271
if not user:
6372
return False
6473
if user.is_anonymous:
6574
return False
75+
if user.is_superuser or user.is_staff:
76+
return True
6677
return user.has_perm(permission)
6778

6879

unittests/authorization/test_authorization.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,11 @@ def test_dojo_add_product_type_carveout(self):
291291

292292
class TestUserHasConfigurationPermission(LegacyAuthorizationBaseTestCase):
293293

294-
"""Configuration permissions delegate to Django's user.has_perm()."""
294+
"""
295+
Configuration permissions reduce to is_superuser / is_staff under
296+
legacy authorization, with Django's ``user.has_perm`` consulted as
297+
a fallback for explicit grants on non-staff users.
298+
"""
295299

296300
def test_anonymous_denied(self):
297301
self.assertFalse(user_has_configuration_permission(None, "dojo.add_product_type"))
@@ -304,6 +308,21 @@ def test_user_without_perm(self):
304308
self.member.has_perm = Mock(return_value=False)
305309
self.assertFalse(user_has_configuration_permission(self.member, "dojo.add_product_type"))
306310

311+
def test_staff_bypasses_without_django_perm(self):
312+
# is_staff is the legacy bypass for configuration permissions —
313+
# mirrors the pre-2020 behavior where staff was an absolute
314+
# bypass on every perm_type. has_perm is not consulted here.
315+
self.staff.has_perm = Mock(return_value=False)
316+
self.assertTrue(user_has_configuration_permission(self.staff, "auth.view_user"))
317+
self.assertTrue(user_has_configuration_permission(self.staff, "auth.delete_user"))
318+
self.assertTrue(user_has_configuration_permission(self.staff, "auth.view_group"))
319+
self.staff.has_perm.assert_not_called()
320+
321+
def test_superuser_bypasses_without_django_perm(self):
322+
self.superuser.has_perm = Mock(return_value=False)
323+
self.assertTrue(user_has_configuration_permission(self.superuser, "auth.view_user"))
324+
self.superuser.has_perm.assert_not_called()
325+
307326

308327
class TestRoleHelpersAreInertUnderLegacy(DojoTestCase):
309328

0 commit comments

Comments
 (0)