Staff user gets 403 retrieving/downloading an object on a project they have no membership in #23

Closed
opened 2026-10-31 18:47:46 +00:00 by Gilles · 0 comments
Owner

HasProjectRole.has_object_permission (apps/orgs/permissions.py, backing CanReadProject/CanWriteProject/CanManageSpace) never special-cases is_staff -- only get_queryset() in each viewset does. List/create therefore work fine for staff with no ProjectMembership, but any single-object action that goes through check_object_permissions (retrieve, update, delete, PdfExportJobViewSet.download) gets a 403.

Reproduced live: staff user gilles created PDF export job #6 on project "Bigb" (no membership there, staff only) -- POST /api/pdf-export-jobs/ succeeded (perform_create has its own explicit is_staff or ... check), but GET /api/pdf-export-jobs/6/ and GET /api/pdf-export-jobs/6/download/ both 403'd. Server logs show the same pattern on /api/specs/150/, so this isn't exports-specific -- it affects every model routed through HasProjectRole (Place/Spec/SpecVersion/SpecTest/Refinement too). CanValidateProject.has_object_permission has the identical gap.

Fix: add the is_staff bypass directly to both permission classes' has_object_permission, matching the bypass every get_queryset() already has.

`HasProjectRole.has_object_permission` (`apps/orgs/permissions.py`, backing `CanReadProject`/`CanWriteProject`/`CanManageSpace`) never special-cases `is_staff` -- only `get_queryset()` in each viewset does. List/create therefore work fine for staff with no `ProjectMembership`, but any single-object action that goes through `check_object_permissions` (retrieve, update, delete, `PdfExportJobViewSet.download`) gets a 403. Reproduced live: staff user `gilles` created PDF export job #6 on project "Bigb" (no membership there, staff only) -- `POST /api/pdf-export-jobs/` succeeded (`perform_create` has its own explicit `is_staff or ...` check), but `GET /api/pdf-export-jobs/6/` and `GET /api/pdf-export-jobs/6/download/` both 403'd. Server logs show the same pattern on `/api/specs/150/`, so this isn't exports-specific -- it affects every model routed through `HasProjectRole` (Place/Spec/SpecVersion/SpecTest/Refinement too). `CanValidateProject.has_object_permission` has the identical gap. Fix: add the `is_staff` bypass directly to both permission classes' `has_object_permission`, matching the bypass every `get_queryset()` already has.
Gilles 2026-10-31 18:48:36 +00:00
  • closed this issue
  • added the
    bug
    label
Sign in to join this conversation.
No description provided.