Serve bypass-admitted granular token requests as anonymous

What does this MR do and why?

A fine-grained PAT without a matching scope reaches a public project or group through the public access rule in Authz::BoundaryPolicy. The endpoint then authorized and filtered for the token's user, so a member's token got the member view: confidential issues, pipelines hidden when Public pipelines is off, pipeline schedule variables, and the same data in GraphQL.

This MR serves those requests as anonymous:

  • AuthorizeGranularScopesService reports when only the public access rule admitted the token.
  • REST: current_user returns nil for those requests, and unauthorized! denies them with insufficient_granular_scope instead of 401 Unauthorized. Logs and audit events still record the token's user.
  • GraphQL: context[:current_user] is nil for the object the rule admitted and everything resolved below it. A child whose own permission the token holds gets the user back. A mutation admitted only by the rule is denied.
  • GraphQL: the merge request count batch is keyed on the current user. Without the key, one query that read an issue the token holds read_issue for and an issue on a public project counted both with the member's view. No other batch loader mixes users this way.
  • The granular token shared example now checks that a scopeless token gets the same response as an anonymous request on public boundaries, for a non-member and for the spec's user.

On public projects, a non-member's scopeless token now gets 403 on endpoints that require authentication, such as pipeline schedules, where it used to get 200.

In GraphQL, a field on an object admitted by the public access rule resolves as anonymous even when the token holds the permission of the field's type. That denies more than needed but leaks nothing.

PSIRT approved fixing this in public, so this replaces the earlier MR on the security fork.

References

Screenshots or screen recordings

Not applicable, no UI change.

How to set up and validate locally

  1. Make sure the granular_personal_access_tokens feature flag is enabled.
  2. As a member of a public project that has a confidential issue, create a fine-grained personal access token with no permissions.
  3. List the confidential issues with the token:
    curl --header "PRIVATE-TOKEN: <token>" "https://gdk.test:3000/api/v4/projects/<project_id>/issues?confidential=true"
    On master the confidential issue is returned. With this MR the response is empty, the same as a request without a token.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading
Loading