feat: grant staff access to superusers automatically#38848
Open
Abdul-Muqadim-Arbisoft wants to merge 3 commits into
Open
feat: grant staff access to superusers automatically#38848Abdul-Muqadim-Arbisoft wants to merge 3 commits into
Abdul-Muqadim-Arbisoft wants to merge 3 commits into
Conversation
Django's is_superuser and is_staff are independent flags, so a superuser does not get the is_staff access that gates the Django admin and various staff-only Studio/LMS views. Since a superuser can grant itself is_staff at any time, that split offers no protection and is only surprising. Add a pre_save signal on the User model that marks any superuser as staff, and a data migration to backfill existing superusers. Discussion: https://discuss.openedx.org/t/shouldnt-superuser-automatically-inherit-staff-access-in-open-edx/18657
f5defd9 to
8eccf6e
Compare
awais786
reviewed
Jul 6, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes Django superusers automatically gain staff access (is_staff=True) to avoid the confusing (and security-meaningless) state where a superuser cannot access Django admin or other staff-gated views until is_staff is manually set.
Changes:
- Add a
pre_savesignal receiver that forcesis_staff=Truewheneveris_superuser=True(skipping raw fixture loads). - Add a one-time data migration that bulk-updates existing
is_superuser=True, is_staff=Falseusers tois_staff=True. - Add unit tests covering the receiver’s behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| common/djangoapps/student/tests/test_receivers.py | Adds tests for the new pre_save receiver that grants staff to superusers. |
| common/djangoapps/student/signals/receivers.py | Adds the pre_save receiver that grants is_staff to superusers on save. |
| common/djangoapps/student/migrations/0050_grant_staff_to_existing_superusers.py | Backfills is_staff=True for existing superusers via a bulk update. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
awais786
reviewed
Jul 6, 2026
- Reword the receiver, migration, and test docstrings to describe the behaviour as grant-only rather than keeping is_staff "in sync" with is_superuser, since demoting a superuser never revokes is_staff. - Exercise the pre_save receiver with an explicit save() in the non-superuser test instead of relying only on factory creation. - Add test_demotion_does_not_remove_staff to lock in the one-directional contract.
awais786
approved these changes
Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Django's
is_superuserandis_staffare independent flags. A superuser bypasses Django's permission checks, but it isis_staffthat actually gates the Django admin and a number of staff-only Studio/LMS views. As a result, a superuser can end up unable to reach the admin or those views until someone also setsis_staff, which is confusing and offers no real protection, since a superuser can grant itselfis_staffat any time.This change makes every superuser a staff user automatically:
pre_savesignal (common/djangoapps/student/signals/receivers.py),grant_staff_access_to_superuserssetsis_staff = Trueon any user being saved withis_superuser = True. It runs before the DB write, so there is noextra query and no signal recursion. Fixture (
raw) loads are skipped.student/migrations/0050_grant_staff_to_existing_superusers.py), backfills existing superusers (is_superuser=True, is_staff=False → is_staff=True) in a single bulk update, since the signal only affects future saves. Thereverse operation is intentionally a no-op.
Behavior is one-directional by design: it grants staff to superusers but does not remove staff when a user stops being a superuser.
Supporting information
Forum discussion: https://discuss.openedx.org/t/shouldnt-superuser-automatically-inherit-staff-access-in-open-edx/18657
Feanil suggested "a small signal handler or something that would just grant all superusers the
staffrole as well", this implements exactly that, plus a one-time backfill for existing installs.Testing instructions
is_superuser=Trueandis_staff=False, save it, and confirmis_staffis nowTrue.is_staffvalue is left untouched.