Add read_ai_usage_metric granular token permission
What does this MR do and why?
Wires the aiUserMetrics GraphQL surface into the granular personal access token authorization framework, replacing its config/authz/graphql/authorization_todo.txt exemptions with real authorization. Follow-up agreed with @imand3r on !244736 (thread) and grounded in @alexbuijs's analysis on !241206 (merged) (thread).
Closes #604498 (closed).
The shape
| Change | Files |
|---|---|
Define read_enterprise_ai_analytics (the ability UserMetricsResolver already authorizes) and remove it from definitions_todo.txt |
config/authz/permissions/enterprise_ai_analytics/ |
Add the read_ai_usage_metric assignable permission (category duo, boundaries group and project) granting it |
config/authz/permission_groups/assignable_permissions/duo/ai_usage_metric/read.yml |
Granular scope directive on both aiUserMetrics field definitions, with the parent as the boundary |
ee/app/graphql/ee/types/group_type.rb, ee/app/graphql/ee/types/project_type.rb |
authorize_granular_token skip_reason: :parent_authorizes on the metrics object types |
ee/app/graphql/types/analytics/ai_metrics/user_metrics_type.rb, .../feature_user_metric_type.rb |
Remove the 13 now-covered authorization_todo.txt entries |
config/authz/graphql/authorization_todo.txt |
| Regenerated granular token GraphQL reference | doc/auth/tokens/fine_grained_access_tokens_graphql.md |
| Request spec: the granular-token shared example plus field-level discriminators (parent-only token nulls the field; token with the assignable resolves it) | ee/spec/requests/api/graphql/analytics/ai_analytics/ai_user_metrics_spec.rb |
Why the directive is on the fields, not the types
The AiUserMetrics / <feature>UserMetrics objects are per-user ClickHouse aggregation rows (hash-backed) with no .group/.project, so a type-level boundary: :group passes static validation but resolves to [] at runtime (@alexbuijs's analysis in the thread above). The boundary that actually exists at runtime is the parent Group/Project the field hangs off, so the directive lives on the two field definitions with boundary: :itself (the parent object at field-check time; precedents: the same parent-object boundary on the aggregation fields in ee/app/graphql/types/analytics/analytics_type.rb, and field-level directive syntax on Issue.createNoteEmail in app/graphql/types/issue_type.rb), and the object types beneath skip with parent_authorizes (precedent: the vulnerability location and identifier types). The skip inside the FeatureUserMetricType factory covers every generated <feature>UserMetrics type, including ones added by in-flight MRs.
Why the assignable is named read_ai_usage_metric
The policy ability being promoted is read_enterprise_ai_analytics, which is what the resolver enforces. The assignable wraps it under the name suggested in the !244736 thread: it describes what a token holder actually gets (the per-user Duo usage metrics surface), and finer-grained permissions can later be swapped inside without renaming the user-facing assignable.
Scope notes
- The parallel
Group/Project.aiMetricsfamily (namespaceMetrics,mcpMetrics,codeReviewMetrics, ...) resolves through its own types and resolvers and keeps its todo entries; it can take the same treatment as a follow-up. - Path traversal for granular tokens is governed by the existing type-level
read_group/read_projectdirectives, unchanged here. The request spec therefore passes both permissions, same shape asspec/requests/api/graphql/work_item_spec.rb. - !244736 (in flight) adds three more
*UserMetricsentries toauthorization_todo.txt; whichever MR merges second absorbs the difference. The factory skip already covers those types, so only the todo lines collide.
How to set up and validate locally
Validated on a source checkout at current master:
bundle exec rake gitlab:permissions:validatepasses in full (permission definitions, assignable definitions, REST and GraphQL permissions, both generated doc pages).bundle exec rubocopon the five changed Ruby files: no offenses.doc/auth/tokens/fine_grained_access_tokens_graphql.mdregenerated withbundle exec rake gitlab:permissions:graphql:compile_docs.bundle exec rake gitlab:graphql:compile_docsproduces no diff: the directives do not change the public schema surface.
To exercise manually: create a granular personal access token with the Ai Usage Metric: Read permission on a group or project boundary and query group(fullPath: ...) { aiUserMetrics { nodes { user { id } } } }; confirm a granular token without the permission gets null for the field while a legacy api-scoped token still works.
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
- I have evaluated the MR acceptance checklist for this MR.