Accept several boundaries in the granular token shared example

What does this MR do and why?

A GraphQL request can return types that check their permissions on different boundaries: PipelineExecutionProjectSchedule checks read_pipeline_execution_project_schedule on the policy management project, and the project it is read through checks read_project on itself. The shared example made a token for one boundary, so the spec of that type uses one project as both (!243215 (merged)), where a type that checks the wrong project still passes.

The shared example now takes additional_boundaries:, which that spec passes:

it_behaves_like 'authorizing granular token permissions for GraphQL', :read_project,
  additional_boundaries: { schedule_policy_management_project: :read_pipeline_execution_project_schedule } do
  let(:boundary_object) { schedule_project }
  let(:request) { post_graphql(query, token: { personal_access_token: pat }, variables: variables) }
end
  • Each key names the let of another project or group, or is :user, :instance or :all_memberships; each value is the permissions checked there. The token holds a scope for every boundary.
  • For each additional boundary, a new example checks that a token without its scope gets every root field, but less data than one with every scope. It sends the request twice, so it suits queries: a mutation would fail it or run twice.
  • Callers that do not pass it keep their examples. REST refuses it, since a REST endpoint's boundaries: are alternatives.

No permission or check changes, only the token the shared example builds; this is not the additional_scopes: keyword !254851 (merged) removed.

The organization work item types spec passes the user boundary it put in an additional_scope_requirements let, which the shared example stopped reading in that MR before the let reached master. Its one-scope context goes; its two-scope one stays, with read_work_item_type, as only it checks that a granular token gets a non-empty list.

The pipeline execution schedule policy spec gains a context where the policy management project is another project. The one-project context stays: a project can be its own policy management project, and the skipped child type examples use it.

The GraphQL implementation guide documents it.

References

Closes #605811

This came up while working on gitlab-mcp-server.

Screenshots or screen recordings

No UI change.

How to set up and validate locally

Nothing to see in a running GitLab: only specs and a development page change.

I ran the checks below locally.

Checks I ran (Ruby 3.3.11)
  • bundle exec rspec ee/spec/requests/api/graphql/project/security_orchestration/pipeline_execution_schedule_policy_spec.rb spec/requests/api/graphql/organizations/work_item_types_spec.rb: 29 examples, 0 failures, 4 pending (the shared example's cases for a top-level group and a public resource, which do not apply to these boundaries). Both new examples pass.
  • Ten callers that do not pass the keyword, the shared example's contexts only (-e 'personal access token'): a mutation with error_boundary_object, REST on the user boundary, expected_success_status, legacy_token_scopes, GraphQL on the user boundary, callers that set their own assignables, and a public project. 138 examples, 0 failures, 38 pending, all of them the shared example's own skips.
  • One planted defect at a time, restored after each:
    • In ee/app/graphql/types/security/pipeline_execution_project_schedule_type.rb, boundary: :project instead of boundary: :policy_management_project: on this branch the new example fails, alone (19 examples, 1 failure; both tokens get upcomingSchedules: { nodes: [] }). With master's spec and shared example, where the project is its own policy management project: 13 examples, 0 failures.
    • The shared example leaving the additional scopes off the token: the two new examples fail, alone (29 examples, 2 failures).
    • Organization.workItemTypes without its granular_scope_directive: on this branch the new example fails, alone (10 examples, 1 failure). With master's spec and shared example, the one-scope context "returns the organization but not its work item types", removed here, fails (10 examples, 1 failure).
    • Organization.workItemTypes checking its permission on the instance instead of the user: the new example and the two-scope context fail (10 examples, 2 failures).
    • The Organization type itself checking the user boundary, so a token without the user scope gets organization: null: the new example fails its root check (10 examples, 1 failure). With the shared example before the review fixes it passed (10 examples, 0 failures), and only the removed one-scope context caught it (11 examples, 1 failure).
    • WorkItemType checking read_work_item_type on the instance instead of leaving it to its parent, so a token with every scope gets an empty list: only the two-scope context fails (10 examples, 1 failure). Without it, nothing does (9 examples, 0 failures).
  • additional_boundaries: on a REST invocation raises ArgumentError when the group is defined (a throwaway spec, not committed).
  • RuboCop, the commit linter and Danger's linters pass, and so do markdownlint, rumdl and Vale in the image the docs lint job uses. Left to the pipeline: Ruby 3.4 and the FOSS run.

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.

Edited by José M. Requena Plens

Merge request reports

Loading
Loading