Configure granular as a Doorkeeper scope for OAuth applications
What does this MR do and why?
Part of #608636. Stacked on !260049 (merged) and targeting its branch until that merges. This MR is one of several split out of !257024 (closed). The controller change in !260062 (merged) depends on it.
granular is a configured Doorkeeper scope again through Gitlab::Auth.optional_scopes, partially reverting !212804 (merged). Doorkeeper 5.9 refreshes a token with the intersection of the old token's scopes and the application's configured scopes when the client sends no scope. So granular must be on the application's scopes column and therefore configured, or a refresh yields a token with no scopes.
The granular scope stays hidden from the application form (excluded_scopes). It is removed from the OIDC discovery scopes_supported list in JwksController, and its label and description are added to config/locales/doorkeeper.en.yml.
A new before_validation on Authn::OauthApplication keeps granular in sync with the declared set. It adds granular when the application declares a granular scope set and removes it otherwise. This applies to every path that saves an application: the application form, the admin POST /api/v4/applications, the user POST /api/v4/user/applications, and anything added later. Adding is needed because Doorkeeper rejects an authorization request for a scope the application does not list (invalid_scope). It also keeps granular when the form, which hides it, saves an edit. granular? now checks oauth_application_granular_scopes, so it also sees declarations built before the application is saved. A granular scope set must be declared through the application and saved with it. A join row inserted on its own leaves the application without granular, and authorization fails with invalid_scope.
The development seed now creates the application together with its granular scope set (granular_scopes:), so the model adds granular.
This is not behind a feature flag. Nothing mints granular OAuth tokens today, so the only visible effects are the hidden form scope and the discovery list.
Database
No migrations. The only new query is Authn::OauthApplication#granular?, now a lookup on the join table only. It runs whenever an existing application is saved (applications are saved rarely: create, edit, secret renewal), and on authorization once !260062 (merged) lands. For a new application it does not query.
The plan below ran on a Database Lab clone of gitlab-production-main (session). oauth_application_granular_scopes comes from !257023 (merged), so it was created on the clone from db/structure.sql on this branch. It was seeded with 20k synthetic rows across 10k applications and analyzed.
Declared set check. Index-only scan on idx_oauth_application_granular_scopes_on_app_id_scope_id: 6 shared buffer hits, 0.06 ms.
SELECT 1 AS one FROM "oauth_application_granular_scopes" WHERE "oauth_application_granular_scopes"."application_id" = 1000 LIMIT 1;Plan
Limit (cost=0.29..2.13 rows=1 width=4) (actual time=0.054..0.055 rows=1 loops=1)
Buffers: shared hit=6
I/O Timings: read=0.000 write=0.000
-> Index Only Scan using idx_oauth_application_granular_scopes_on_app_id_scope_id on public.oauth_application_granular_scopes (cost=0.29..3.98 rows=2 width=4) (actual time=0.053..0.053 rows=1 loops=1)
Index Cond: (oauth_application_granular_scopes.application_id = 1000)
Heap Fetches: 1
Buffers: shared hit=6
I/O Timings: read=0.000 write=0.000
Settings: random_page_cost = '1.5', seq_page_cost = '4', effective_cache_size = '472585MB', work_mem = '230MB', jit = 'off'
Query ID: -5537924746706453906References
- Work item: #608636
- Technical design: https://gitlab.com/gitlab-org/gitlab/-/work_items/605869
- Stack base: !257023 (merged)
- Original MR: !257024 (closed)
- Controller change: !260062 (merged)
- Feature flag: !260049 (merged)
- Consent grant services: !260051 (merged)
Screenshots or screen recordings
Not applicable. The only UI change is that granular is not offered on the application form.
How to set up and validate locally
- Create an application in the UI and confirm
granularis not offered. - In a Rails console, give an application a declared set, update its scopes without
granular, and confirmgranularis kept. - Create an application through
POST /api/v4/user/applicationswithscopes=api granularand confirm the saved application only hasapi. - Open
/.well-known/openid-configurationand confirmscopes_supporteddoes not listgranular.
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.