Add granular token directives to MCP note, diff and wiki types

What does this MR do and why?

Closes #626742

With a fine-grained PAT, any GraphQL object type without an authorize_granular_token directive resolves to null, because Gitlab::Graphql::Authz::GranularScopeAuthorization denies when no directive is declared. GitLab's MCP server runs GraphQL queries on behalf of the token. Once the token is passed into the MCP GraphQL tools (#631630 (closed)), the note, merge request, commit, repository tree and wiki tools return no data.

This MR was split out of !261599 (merged) to make review easier. That MR keeps the work item changes. The two are independent and can merge in either order. They only both touch config/authz/graphql/authorization_todo.txt and the generated doc/auth/tokens/fine_grained_access_tokens_graphql.md.

Changes (see the implementation guide):

  1. Real directives:
    • Discussion: read_note at the project and group boundaries, mirroring Note.
    • WikiPage: read_wiki at the project and group boundaries via the wiki container. This also covers the root Query.wikiPage field.
  2. skip_reason: :parent_authorizes on value types that have no policy permission of their own. Following the interim guidance in https://gitlab.com/gitlab-org/gitlab/-/work_items/636258, these are all the fields that return them, with the permission each parent requires:
    • Diff: Commit.diffs (read_code) and MergeRequest.diffs (read_merge_request)
    • DiffStats and DiffStatsSummary: Commit.diffStats and Commit.diffStatsSummary (read_code), MergeRequest.diffStats and MergeRequest.diffStatsSummary (read_merge_request)
    • DiffPosition: Note.position (read_note)
    • TreeEntry and Submodule: Tree.trees and Tree.submodules (read_repository_tree)
    • AccessLevel: Project.maxAccessLevel (read_project), Group.maxAccessLevel (read_group), and ProjectMember.accessLevel and GroupMember.accessLevel (read_member). The other parents (MemberApproval, MemberRole, the pending members, protected environments and UsersQueuedForRolePromotion) have no directive yet, so fine-grained tokens are denied there.
  3. The todo lines for these nine types are removed and the docs are regenerated.
  4. Specs: request specs for each directive and boundary, and for each skipped type through its MCP path, using the shared examples 'authorizing granular token permissions for GraphQL' and '... with a skipped child type'.

Behavior for legacy PATs is unchanged. Granular checks only run for fine-grained tokens (or namespaces that enforce them).

This MR also absorbs the DiffStats and DiffStatsSummary changes from !254455 (closed), and adds the commit path coverage its reviewers asked for. Thanks to its author for that work.

References

Screenshots or screen recordings

Not applicable, this MR only changes the backend.

How to set up and validate locally

  1. Create a fine-grained personal access token in the UI (User settings > Personal access tokens > Fine-grained) for a project, with the Project: Read, Merge request: Read and Work item: Read permissions. Work item: Read is the permission that grants read_note, the same as for Note today.

  2. Run this query against /api/graphql with the token, for example with curl:

    query { project(fullPath: "flightjs/Flight") { mergeRequest(iid: "1") { diffStatsSummary { additions } discussions { nodes { id } } } } }

    On master diffStatsSummary is null and discussions has no nodes. With this MR both are returned.

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.

🤖 Generated with Claude Code

Edited by Dennis Meister

Merge request reports

Loading
Loading