Skip to content

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.

  1. A policy or an exception. Each action carries an [Authorize(<policy>)] whose policy the host registers (ProjectAuthorization, StageAuthorization, ApplicationAuthorization or the named parity-read policy), or it has an entry in EndpointAuthorizationExceptions with a category and a reason. The global authenticated-user filter alone is not a classification.
  2. Anonymous and public endpoints are pinned by their response fields. For Anonymous and PublicMetadata entries 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 untyped IActionResult cannot be pinned: its entry must say ResponseFields.Undeclared and describe the body in its reason (or declare [ProducesResponseType]).
  3. Gaps stay honest. A KnownGap entry 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.
  4. The catalogue is consistent.
  5. Every project and stage policy resolves to an activity enum member with a deployed ResourceSecurity.json default. A policy that does not resolve denies everyone silently; a missing default is a 500.
  6. Every activity has a default, and every default names a known activity, once.
  7. Every application policy has a default.
  8. StageView and StageDelete are the two known unresolvable stage policies (#3335 D3).
  9. 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.

Adding an endpoint

  • Project or stage data: use the matching Project…Policy/Stage…Policy on a route with {projectId} (and {stageId}), and bind every body or route ID you touch to that project inside the action (404 if 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 its ProjectActivity/StageActivity member (appended, never inserted) and a ResourceSecurity.json default.
  • Application-wide administration: an Application…Policy whose activity is in the PM allowlist (application-authority mode).
  • Otherwise: add an EndpointAuthorizationExceptions entry. Use AuthenticatedSelf only when the action reads or writes nothing but the caller's own records. Use InActionAuthority when the action decides through an authority gate in its body. Use TrustedService for workload identities, never human sessions. Anonymous and PublicMetadata entries 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.
dotnet test src/services/api/SyRF.API.Endpoint.Tests --filter "FullyQualifiedName~EndpointAuthorizationCatalogTests"