Add granular token authorization to Incident management & alerting mutations

What does this MR do?

Adds granular Personal Access Token (gPAT) authorization to all 27 GraphQL mutations in the Incident management & alerting group (Group 7 of the GraphQL mutation authorization effort).

Each mutation gets an authorize_granular_token directive, backed by raw and assignable permission definitions and a request-spec authorization test (it_behaves_like 'authorizing granular token permissions for GraphQL'). The 27 corresponding mutation: lines are removed from config/authz/graphql/authorization_todo.txt.

All 27 resources are project-scoped, so every directive uses a :project boundary — resolved either directly from a project_path full-path argument, or from a GlobalID argument via the resolved record's project.

Mutation → permission → boundary

Mutation Permission (raw) Boundary
AlertSetAssignees update_alert project (project_path)
UpdateAlertStatus update_alert project (project_path)
AlertTodoCreate create_todo (reused) project (project_path)
CreateAlertIssue create_issue (reused) + update_alert project (project_path)
HttpIntegrationCreate create_http_integration project (project_path)
HttpIntegrationUpdate update_http_integration project (idproject)
HttpIntegrationResetToken update_http_integration project (idproject)
HttpIntegrationDestroy delete_http_integration project (idproject)
PrometheusIntegrationCreate create_http_integration (inherited) project (project_path)
PrometheusIntegrationUpdate update_http_integration (inherited) project (idproject)
PrometheusIntegrationResetToken update_http_integration (inherited) project (idproject)
TimelineEventCreate create_timeline_event project (incident_idproject)
TimelineEventPromoteFromNote create_timeline_event project (note_idproject)
TimelineEventUpdate update_timeline_event project (idproject)
TimelineEventDestroy delete_timeline_event project (idproject)
TimelineEventTagCreate create_timeline_event_tag project (project_path)
EscalationPolicyCreate create_escalation_policy project (project_path)
EscalationPolicyUpdate update_escalation_policy project (idproject)
EscalationPolicyDestroy delete_escalation_policy project (idproject)
IssuableResourceLinkCreate create_issuable_resource_link project (id [Issue] → project)
IssuableResourceLinkDestroy delete_issuable_resource_link project (id [link] → project)
OncallRotationCreate create_oncall_rotation project (project_path)
OncallRotationUpdate update_oncall_rotation project (idproject)
OncallRotationDestroy delete_oncall_rotation project (project_path)
OncallScheduleCreate create_oncall_schedule project (project_path)
OncallScheduleUpdate update_oncall_schedule project (project_path)
OncallScheduleDestroy delete_oncall_schedule project (project_path)

Permissions: created vs reused

Reused (no new permission):

  • create_todo (existing create_todo assignable) for AlertTodoCreate
  • create_issue (existing create_work_item assignable) for CreateAlertIssue, which also requires the new update_alert (see judgment calls)
  • create_http_integration / update_http_integration for the three Prometheus integration mutations (see judgment call below)

New raw permissions (19) under config/authz/permissions/: update_alert; create/update/delete_escalation_policy; create/update/delete_http_integration; create/update/delete_oncall_schedule; create/update/delete_oncall_rotation; create/update/delete_timeline_event; create_timeline_event_tag; create/delete_issuable_resource_link.

New assignable permissions (15) — all under the existing monitoring category, matching the pre-existing per-resource-per-action pattern there (e.g. alert_metric_image, escalation_policy/read). There were essentially no existing incident-management assignable permissions to reuse, so these capabilities had to be introduced; the escalation-policy ones extend the existing monitoring/escalation_policy resource that already had read.

Nested resources are folded into their parent's per-verb assignable bundles rather than getting standalone bundles. Raw permission names and mutation directives are unchanged — only assignable membership moves:

  • create/update/delete_oncall_rotation are bundled into the matching create/update/delete_oncall_schedule assignables (rotations are nested under schedules).
  • create_timeline_event_tag is bundled into the create_timeline_event assignable (tags are adjuncts of timeline events).

Judgment calls

  • CreateAlertIssue requires both create_issue and update_alert. The mutation inherits authorize :update_alert_management_alert from Mutations::AlertManagement::Base, and AlertManagement::CreateAlertIssueService links the new issue to the alert and posts a system note on it. RBAC already gates the mutation on both abilities, so requiring create_issue alone would have been narrower than the role check. Permission arrays are AND semantics, and both assignables (Work Item: Create, Alert: Update) are grantable at the project boundary.

  • Prometheus integrations reuse HTTP integration permissions. The three PrometheusIntegration* mutations subclass the HttpIntegration* mutations and operate on the same AlertManagement::HttpIntegration records (PrometheusIntegrationCreate creates a prometheus-type HTTP integration; update/reset-token resolve the project's HTTP integration). They inherit the parent's directive, so reusing create/update_http_integration is both the honest domain mapping and required by the framework: enforcement reads the first directive, and a subclass's own directive cannot override an inherited one. This also avoids adding user-facing permissions for a deprecated feature.

  • IssuableResourceLink#project association. IssuableResourceLinkDestroy only receives the link's GlobalID, and the model had no single method returning a Project (only belongs_to :issue). A has_one :project, through: :issue association was added so the boundary resolves and the gPAT boundary preloader can batch-load it via reflect_on_association. Safe here because issuable_resource_links, issues, and projects are all gitlab_main_org, so the join does not cross database schemas.

  • Oncall rotations vs schedules keep distinct raw permissions even though they share the admin_incident_management_oncall_schedule ability, but the rotation permissions are folded into the schedule assignable bundles since rotations are nested under schedules.

Skipped

None. All 27 mutations are authorized.

Validation

  • bundle exec rake gitlab:permissions:validate passes (permission definitions, assignable permissions, REST, and GraphQL all valid — every declared permission/boundary has its authorization test).
  • bundle exec rake gitlab:permissions:graphql:compile_docs regenerated doc/auth/tokens/fine_grained_access_tokens_graphql.md (committed).
  • Request specs run in CI (not locally).

See the GraphQL implementation guide.

Changelog: added

Edited by Alex Buijs

Merge request reports

Loading
Loading