Skip to content

Study read, risk-of-bias and PDF-correction authorization

Seven actions on StudyController (/api/projects/{projectId}/studies) had no resource policy. The global authenticated-user filter was their only gate, so any signed-in user could, for any project:

  • read a study by ID (GET {studyId}), or every study's risk-of-bias fields (GET getRobStudies);
  • write risk-of-bias fields into any study (POST addRob, POST addRobs), including studies of a different project from the one in the route;
  • submit a PDF correction with a body-supplied project, study, search and submitter, and approve or reject any correction (POST {studyId}/Pdf/Correction, PUT …/{correctionId}/Approve|Reject).

The study and correction IDs were never compared with the route project. These were #3335's "entry points lacking a resource policy", confirmed open on main during application-authority milestone M3b.

The rule now

Action Policy (deployed default grantees)
GET {studyId}, GET getRobStudies ProjectViewStudiesPolicy (every active member, project Administrator group)
POST addRob, POST addRobs ProjectBulkUpdateStudiesPolicy (project Administrator group)
POST {studyId}/Pdf/Correction ProjectSubmitPdfCorrectionsPolicy (every active member, project Administrator group)
PUT …/Approve, PUT …/Reject ProjectApprovePdfCorrectionsPolicy (project Administrator group)
  • The two correction policies are new constants for activities (SubmitPdfCorrections, ApprovePdfCorrections) whose ResourceSecurity.json defaults already existed but which no endpoint used. The registration loop in Program.cs registers them; no host change is needed.
  • Risk-of-bias writes use the bulk study-update grant: they change stored study data in bulk. Starting a batch Risk of Bias job is a separate activity, CalculateRob (Calculate Risk of Bias authorization); both default to the project Administrator group.
  • Every study or correction an action touches must belong to the route project (and, for a correction, to the route study). Otherwise the answer is 404 and nothing is written. A batch naming one foreign or unknown study writes nothing: all studies are checked before any is changed.
  • A submitted correction takes its project and study from the route, its search from the study, and its submitter from the signed-in user. The body's ProjectId, StudyId, SearchId and InvestigatorId are ignored.
  • Approve and Reject now return the study they resolved (they previously mapped an un-awaited task).

The web app calls none of these actions. Callers that relied on sign-in alone now receive 403 (not permitted on the route project) or 404 (ID not in the route project).

Validation and rollout

StudyEndpointAuthorizationTests drives real HTTP requests through the global authentication filter, the policies built by the same loop as Program.cs, the production authorization handler and the deployed ResourceSecurity.json. For every action it covers owner, project administrator, ordinary member and non-member; anonymous 401 with no read; refusal with no study or correction read and no write; foreign-study and foreign-correction 404 with no write; an all-or-nothing batch; and server-side attribution of a submitted correction. 28 of its 46 cases fail against the previous controller.

This security correction is unflagged: a runtime switch must not reopen the hole. It needs no data migration. In enforced application-authority mode these project policies are decided like every other project policy. Deploy with the normal API release.