Classify an API endpoint's authorization (the M3b catalogue test)¶
Every routed API action and every public NotificationHub method must be catalogued. CI fails otherwise
(EndpointAuthorizationCatalogTests in SyRF.API.Endpoint.Tests, part of application-authority milestone M3b of the
transition plan).
What the test enforces¶
The inventory is MVC's own action discovery (the same application parts AddControllers maps), so a new controller
or action is picked up without anyone listing it.
- A policy or an exception. Each action carries an
[Authorize(<policy>)]whose policy the host registers (ProjectAuthorization,StageAuthorization,ApplicationAuthorization, the search-read policy or the named parity-read policy), or it has an entry inEndpointAuthorizationExceptionswith a category and a reason. The global authenticated-user filter alone is not a classification. - Anonymous and public endpoints are pinned by their response fields. For
AnonymousandPublicMetadataentries the test computes every JSON field path of the declared success response ([ProducesResponseType], or the return type) and compares it with the listed paths. Adding a field to a public response fails CI until someone decides it is public. An action returning an untypedIActionResultcannot be pinned: its entry must sayResponseFields.Undeclaredand describe the body in its reason (or declare[ProducesResponseType]). - Gaps stay honest. A
KnownGapentry must name a tracking reference (#…) and must still lack a policy: fixing it fails the test until the entry is deleted. An entry for an action that now has a policy, or no longer exists, also fails. - The catalogue is consistent.
- Every project and stage policy resolves to an activity enum member with a deployed
ResourceSecurity.jsondefault. A policy that does not resolve denies everyone silently; a missing default is a500. - Every activity has a default, and every default names a known activity, once.
- Every application policy has a default.
StageViewandStageDeleteare the two known unresolvable stage policies (#3335 D3).ProjectSearchReadPolicynames no single activity: each of its activities must be catalogued with a default.- Named protected workloads (export creation and download, administrative email sends, batch Risk of Bias, PDF corrections, RoB writes, bulk-PDF report and path downloads) must carry a resource policy. Moving one into the exception table is itself a failure.
Search reads¶
Project View is open to every signed-in user, so it covers public project metadata only. A search's description and
original reference files are not public metadata (SM-13).
- Any action that answers
SearchDtos needsProjectSearchReadPolicy(ProjectSearchReadAuthorization), or theViewSearches,ImportSearchorRemoveSearchpolicy.SearchContentActionsNeedASearchActivityPolicyfails otherwise.ProjectSearchReadPolicyadmits an active member who holds any one of those three activities. Each activity is decided exactly as its own policy would decide it, in every application-authority mode, and the deleted-project and absent-project answers are unchanged. It changes no default grant. - Project details are built for every member. There
ProjectJobFamilyPermissionFiltertrims each search withSearchDto.WithoutSourceDetail()for a caller without one of those activities: id, name, study count and the living-search link stay; the description and reference files do not. The filter runs inside the mapping, so the project endpoints, the realtime dispatcher and the project hub all apply it, including searches preloaded from the materialised search-population read. - With
studiesWorkspaceon, the same filter withholds even names and counts unless the caller holdsViewSearchOverview("Who can see search names", study management W4): such a caller gets no searches at all.ViewSearchOverviewhas no default grant and no policy of its own, because no endpoint is authorized by it; it is granted per project, to groups or to all members, and it never admitsProjectSearchReadPolicy. With the flag off, project details are as described above.
Adding an endpoint¶
- Search data: see search reads; never
ProjectViewPolicyalone. - Project or stage data: use the matching
Project…Policy/Stage…Policyon a route with{projectId}(and{stageId}), and bind every body or route ID you touch to that project inside the action (404if it is foreign, writing nothing).StudyController's study and correction actions show the pattern; Calculate Risk of Bias authorization shows a new activity end to end. A new activity needs itsProjectActivity/StageActivitymember (appended, never inserted) and aResourceSecurity.jsondefault. - Application-wide administration: an
Application…Policywhose activity is in the PM allowlist (application-authority mode). - Otherwise: add an
EndpointAuthorizationExceptionsentry. UseAuthenticatedSelfonly when the action reads or writes nothing but the caller's own records. UseInActionAuthoritywhen the action decides through an authority gate in its body. UseTrustedServicefor workload identities, never human sessions.AnonymousandPublicMetadataentries list their response fields; copy the "actual" list from the failure message only after checking that every field is fit for an anonymous or non-member caller.