Add fine-grained token support for project labels API endpoints
What does this MR do and why?
Add fine-grained token support for project labels API endpoints following the implementation guide
- Add authorization decorators
- Create assignable permissions for labels
- Add authorization tests for granular tokens
- Supports GET, POST, PUT, DELETE operations
This change will allow me fix the feedback I have raised regarding fine-grained tokens not supporting /projects/:id/labels endpoints, which will help me unblock token migration tasks such as https://gitlab.com/gitlab-com/gl-security/product-security/product-security-engineering/product-security-engineering-team/-/work_items/401
Project Label Permissions (NEW)
- Created project_label permissions to support granular token access without conflicting with other resources that use generic label permissions
- Implemented 4 permission types: read_project_label, create_project_label, update_project_label, delete_project_label
- Added corresponding assignable permissions for UI dropdown and token creation
Group Label Support
- authorization decorators already exist in
/api/group_labels.rbfrom before so this was partially supported already. - Created group_label assignable permissions to support granular token access for group label endpoints
- Implemented 3 assignable permissions:
read_group_label,write_group_label,delete_group_label
Work Item Cleanup
- Removed unused
create_label,update_label, anddelete_labelpermissions from work_item assignable permissions - Work items only
readand associate labels; actual label CRUD operations are exclusive to label API endpoints
API Authorization
- Added authorization decorators with appropriate
project_labelpermissions to all 9 project label endpoints (GET, POST, PUT, DELETE operations) - Added comprehensive authorization tests validating both legacy PATs and granular PATs work correctly
All 9 Label Endpoints Now Support Fine-Grained Tokens
READ Operations (read_project_label)
GET /projects/:id/labels- List project labelsGET /projects/:id/labels/:name- Get a single label by name
CREATE Operations (create_project_label)
POST /projects/:id/labels- Create a new label
UPDATE Operations (update_project_label)
PUT /projects/:id/labels/:name- Update an existing labelPUT /projects/:id/labels/:name/promote- Promote a label to group levelPUT /projects/:id/labels (deprecated)- Update label via deprecated endpoint
DELETE Operations (delete_project_label)
DELETE /projects/:id/labels/:name- Delete a labelDELETE /projects/:id/labels (deprecated)- Delete label via deprecated endpoint
References
Screenshots or screen recordings
| Before | After |
|---|---|
![]() |
![]() |
How to set up and validate locally
-
In rails console enable the feature flag
Feature.enable(:granular_personal_access_tokens) -
Create a gPAT via UI or rails console and select
READ,UPDATE,CREATE,DELETEforlabel. I created the token via UI:
- Run the following commands
# verify the token does not have access to perform any operation against other resources
curl -H "PRIVATE-TOKEN: <my local gPAT>" https://gdk.test:3443/api/v4/projects/2
# I got
{"error":"insufficient_granular_scope","error_description":"Access denied: This operation requires a fine-grained personal access token with the following project permissions: [Project: Read]."}% # verify the token can read project tokens
curl -H "PRIVATE-TOKEN: <gPAT>" https://gdk.test:3443/api/v4/projects/2/labels
# I got
[{"id":79,"name":"Civic","description":null,"text_color":"#1F1E24","description_html":"","color":"#a6e753","archived":false,"subscribed":false,"priority":null,"is_project_label":true},{"id":77,"name":"Elantra","description":null,"text_color":"#FFFFFF","description_html":"","color":"#597253","archived":false,"subscribed":false,"priority":null,"is_project_label":true},{"id":75,"name":"Equinox","description":null,"text_color":"#FFFFFF","description_html":"","color":"#b400a8","archived":false,"subscribed":false,"priority":null,"is_project_label":true},
...]# verify the token can UPDATE project label
curl -X PUT -H "PRIVATE-TOKEN: <gPAT>" \
https://gdk.test:3443/api/v4/projects/2/labels \
-d "name=Civic&new_name=Updated-Civic&color=%23FF0000"
# I got
{"id":79,"name":"Updated-Civic","description":null,"text_color":"#FFFFFF","description_html":"","color":"#FF0000","archived":false,"subscribed":false,"priority":null,"is_project_label":true}% # verify the token can DELETE project label
curl -X DELETE -H "PRIVATE-TOKEN: <gPAT>" \
https://gdk.test:3443/api/v4/projects/2/labels/110
# I was able to see the label disappear from the UI# verify projects/:id/labels/:name is supported
curl -X GET -H "PRIVATE-TOKEN: <gpat>" \
https://gdk.test:3443/api/v4/projects/2/labels/Equinox
# I got
{"id":75,"name":"Equinox","description":null,"text_color":"#FFFFFF","description_html":"","color":"#b400a8","archived":false,"subscribed":false,"priority":null,"is_project_label":true}% # verify the /projects/:id/labels/:name/prmote endpoint is supported
curl -X PUT -H "PRIVATE-TOKEN: <gPAT>" \
https://gdk.test:3443/api/v4/projects/2/labels/Rio/promote
# I was able to see the label being promoted from project label to a group label. See screenshots below- before promote:
- after promote:
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.



