Extract privilege-escalation check into a shared guard

Overview

This MR is part of a short series bringing GitLab's REST API up to parity with GraphQL for granular personal access tokens (gPATs). Related issue: #589500 (closed)

  • This MR (1 of 2) — extracts a check GraphQL already had into shared code, so REST can reuse it. No user-facing change.
  • !243428 (merged) (2 of 2) — teaches REST to actually create granular tokens, for all 5 access types.
  • Follow-up (not started yet) — expose granular_scopes when listing/reading/rotating tokens via REST. Right now REST only shows a granular: true flag, not the actual scopes.

What does this MR do?

Moves a security check out of the GraphQL PersonalAccessTokenCreate mutation into its own reusable class, Authz::Tokens::PrivilegeEscalationGuard.

The check itself is not new: when a granular token is used to create another granular token, we verify the new token doesn't ask for more permissions than the token creating it already has (so a token can't be used to mint a "more powerful" token than itself). That logic used to live as a handful of private methods directly inside the GraphQL mutation code, with no way for anything outside GraphQL to call it.

This MR only moves that logic — it does not change what it does. The GraphQL mutation now calls the extracted class instead of its own private methods, but produces identical results.

Why are we doing this?

The next MR (!243428 (merged)) adds the same "create a granular token" ability to REST, and REST needs this exact same escalation check. Rather than copy-pasting the logic into REST, we're pulling it out into shared code first — this keeps !243428 (merged) focused purely on the new REST behavior, and means GraphQL and REST can never drift apart on this specific security rule.

How to test

This is an internal refactor with no user-visible behavior — there isn't a UI/API flow to click through. Verification is via the test suite:

  • bin/rspec spec/services/authz/tokens/privilege_escalation_guard_spec.rb — new unit spec covering the extracted class directly (all access-type combinations, escalation allowed/blocked cases).
  • bin/rspec spec/requests/api/graphql/mutations/users/personal_access_tokens/create_spec.rb — confirms the existing GraphQL mutation behavior is unchanged after the extraction.

Both pass with no regressions; the second spec file was also trimmed to remove test cases now covered directly by the new unit spec, rather than duplicated at the request-spec level.

Edited by Ayush Billore

Merge request reports

Loading
Loading