From efb0d28f7683cbd3cef0b1bc5bb43822e09ddf76 Mon Sep 17 00:00:00 2001 From: Duo Developer Date: Sun, 14 Dec 2025 19:59:25 +0000 Subject: [PATCH 1/4] feat: Add granular PAT permissions for Protected Tags API Implement read_protected_tag, create_protected_tag, and delete_protected_tag permissions to enable fine-grained access control for Protected Tags REST API endpoints. This allows administrators to grant specific PAT permissions for protected tag operations without requiring full repository access. - Add permission definitions and assignable permission groups - Apply authorization decorators to all Protected Tags API endpoints - Add comprehensive test coverage for authorization checks --- .../repositories/protected_tag/create.yml | 8 ++++++ .../repositories/protected_tag/delete.yml | 8 ++++++ .../repositories/protected_tag/read.yml | 8 ++++++ .../permissions/protected_tag/create.yml | 6 +++++ .../permissions/protected_tag/delete.yml | 6 +++++ .../authz/permissions/protected_tag/read.yml | 6 +++++ lib/api/protected_tags.rb | 4 +++ spec/requests/api/protected_tags_spec.rb | 27 +++++++++++++++++++ 8 files changed, 73 insertions(+) create mode 100644 config/authz/permission_groups/assignable_permissions/repositories/protected_tag/create.yml create mode 100644 config/authz/permission_groups/assignable_permissions/repositories/protected_tag/delete.yml create mode 100644 config/authz/permission_groups/assignable_permissions/repositories/protected_tag/read.yml create mode 100644 config/authz/permissions/protected_tag/create.yml create mode 100644 config/authz/permissions/protected_tag/delete.yml create mode 100644 config/authz/permissions/protected_tag/read.yml diff --git a/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/create.yml b/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/create.yml new file mode 100644 index 00000000000000..1207d1d5980967 --- /dev/null +++ b/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/create.yml @@ -0,0 +1,8 @@ +--- +name: create_protected_tag +description: Grants the ability to create protected tags +feature_category: source_code_management +permissions: + - create_protected_tag +boundaries: + - project diff --git a/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/delete.yml b/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/delete.yml new file mode 100644 index 00000000000000..f291e9e9ad662b --- /dev/null +++ b/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/delete.yml @@ -0,0 +1,8 @@ +--- +name: delete_protected_tag +description: Grants the ability to delete protected tags +feature_category: source_code_management +permissions: + - delete_protected_tag +boundaries: + - project diff --git a/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/read.yml b/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/read.yml new file mode 100644 index 00000000000000..eddda1623c2797 --- /dev/null +++ b/config/authz/permission_groups/assignable_permissions/repositories/protected_tag/read.yml @@ -0,0 +1,8 @@ +--- +name: read_protected_tag +description: Grants the ability to read protected tags +feature_category: source_code_management +permissions: + - read_protected_tag +boundaries: + - project diff --git a/config/authz/permissions/protected_tag/create.yml b/config/authz/permissions/protected_tag/create.yml new file mode 100644 index 00000000000000..c4ee2379855ca1 --- /dev/null +++ b/config/authz/permissions/protected_tag/create.yml @@ -0,0 +1,6 @@ +--- +name: create_protected_tag +description: Grants the ability to create protected tags +feature_category: source_code_management +boundaries: + - project diff --git a/config/authz/permissions/protected_tag/delete.yml b/config/authz/permissions/protected_tag/delete.yml new file mode 100644 index 00000000000000..4e7e8858e6175e --- /dev/null +++ b/config/authz/permissions/protected_tag/delete.yml @@ -0,0 +1,6 @@ +--- +name: delete_protected_tag +description: Grants the ability to delete protected tags +feature_category: source_code_management +boundaries: + - project diff --git a/config/authz/permissions/protected_tag/read.yml b/config/authz/permissions/protected_tag/read.yml new file mode 100644 index 00000000000000..c94300a2b25d01 --- /dev/null +++ b/config/authz/permissions/protected_tag/read.yml @@ -0,0 +1,6 @@ +--- +name: read_protected_tag +description: Grants the ability to read protected tags +feature_category: source_code_management +boundaries: + - project diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb index 7f9f0259d9a115..01092953e094aa 100644 --- a/lib/api/protected_tags.rb +++ b/lib/api/protected_tags.rb @@ -28,6 +28,7 @@ class ProtectedTags < ::API::Base use :pagination end # rubocop: disable CodeReuse/ActiveRecord + route_setting :authorization, permissions: :read_protected_tag, boundary_type: :project get ':id/protected_tags' do authorize!(:read_protected_tags, user_project) protected_tags = user_project.protected_tags.preload(:create_access_levels) @@ -49,6 +50,7 @@ class ProtectedTags < ::API::Base requires :name, type: String, desc: 'The name of the tag or wildcard', documentation: { example: 'release*' } end # rubocop: disable CodeReuse/ActiveRecord + route_setting :authorization, permissions: :read_protected_tag, boundary_type: :project get ':id/protected_tags/:name', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize!(:read_protected_tags, user_project) protected_tag = user_project.protected_tags.find_by!(name: params[:name]) @@ -76,6 +78,7 @@ class ProtectedTags < ::API::Base documentation: { example: 30 } use :optional_params_ee end + route_setting :authorization, permissions: :create_protected_tag, boundary_type: :project post ':id/protected_tags' do authorize!(:create_protected_tags, user_project) protected_tags_params = { @@ -108,6 +111,7 @@ class ProtectedTags < ::API::Base requires :name, type: String, desc: 'The name of the protected tag', documentation: { example: 'release-1-0' } end # rubocop: disable CodeReuse/ActiveRecord + route_setting :authorization, permissions: :delete_protected_tag, boundary_type: :project delete ':id/protected_tags/:name', requirements: TAG_ENDPOINT_REQUIREMENTS do authorize!(:destroy_protected_tags, user_project) diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb index e939f14cb6acdd..7d0ba64b2c74a5 100644 --- a/spec/requests/api/protected_tags_spec.rb +++ b/spec/requests/api/protected_tags_spec.rb @@ -36,6 +36,12 @@ end it_behaves_like 'protected tags' + + it_behaves_like 'authorizing granular token permissions', :read_protected_tag do + let(:user) { user } + let(:boundary_object) { project } + let(:request) { get api(route, personal_access_token: pat), params: { per_page: 100 } } + end end context 'when authenticated as a guest' do @@ -115,6 +121,12 @@ ) end end + + it_behaves_like 'authorizing granular token permissions', :read_protected_tag do + let(:user) { user } + let(:boundary_object) { project } + let(:request) { get api(route, personal_access_token: pat) } + end end context 'when authenticated as a guest' do @@ -193,6 +205,15 @@ expect(json_response['create_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) end end + + it_behaves_like 'authorizing granular token permissions', :create_protected_tag do + let(:user) { user } + let(:boundary_object) { project } + let(:request) do + post api("/projects/#{project.id}/protected_tags", personal_access_token: pat), + params: { name: 'new_protected_tag' } + end + end end context 'when authenticated as a guest' do @@ -259,6 +280,12 @@ expect(response).to have_gitlab_http_status(:no_content) end end + + it_behaves_like 'authorizing granular token permissions', :delete_protected_tag do + let(:user) { user } + let(:boundary_object) { project } + let(:request) { delete api("/projects/#{project.id}/protected_tags/#{tag_name}", personal_access_token: pat) } + end end end end -- GitLab From c127d0da437b0c27336ff7c60ca045c48e12a211 Mon Sep 17 00:00:00 2001 From: Matthew MacRae-Bovell Date: Tue, 16 Dec 2025 04:46:01 -0500 Subject: [PATCH 2/4] Remove redefining user --- spec/requests/api/protected_tags_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb index 7d0ba64b2c74a5..2e39e327784f18 100644 --- a/spec/requests/api/protected_tags_spec.rb +++ b/spec/requests/api/protected_tags_spec.rb @@ -38,7 +38,6 @@ it_behaves_like 'protected tags' it_behaves_like 'authorizing granular token permissions', :read_protected_tag do - let(:user) { user } let(:boundary_object) { project } let(:request) { get api(route, personal_access_token: pat), params: { per_page: 100 } } end @@ -123,7 +122,6 @@ end it_behaves_like 'authorizing granular token permissions', :read_protected_tag do - let(:user) { user } let(:boundary_object) { project } let(:request) { get api(route, personal_access_token: pat) } end -- GitLab From 568f80db6989595380106e87ecddb6c08754933f Mon Sep 17 00:00:00 2001 From: Matthew MacRae-Bovell Date: Tue, 16 Dec 2025 15:42:37 -0500 Subject: [PATCH 3/4] Add metadata yml file --- config/authz/permissions/protected_tag/_metadata.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 config/authz/permissions/protected_tag/_metadata.yml diff --git a/config/authz/permissions/protected_tag/_metadata.yml b/config/authz/permissions/protected_tag/_metadata.yml new file mode 100644 index 00000000000000..8fe5b8de7a2906 --- /dev/null +++ b/config/authz/permissions/protected_tag/_metadata.yml @@ -0,0 +1 @@ +feature_category: source_code_management -- GitLab From 913870c8f4d2ccd02c2b338e837199818ac4125f Mon Sep 17 00:00:00 2001 From: Matthew MacRae-Bovell Date: Tue, 16 Dec 2025 15:48:02 -0500 Subject: [PATCH 4/4] Remove recursive user call --- spec/requests/api/protected_tags_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb index 2e39e327784f18..cde99dc19844eb 100644 --- a/spec/requests/api/protected_tags_spec.rb +++ b/spec/requests/api/protected_tags_spec.rb @@ -205,7 +205,6 @@ end it_behaves_like 'authorizing granular token permissions', :create_protected_tag do - let(:user) { user } let(:boundary_object) { project } let(:request) do post api("/projects/#{project.id}/protected_tags", personal_access_token: pat), @@ -280,7 +279,6 @@ end it_behaves_like 'authorizing granular token permissions', :delete_protected_tag do - let(:user) { user } let(:boundary_object) { project } let(:request) { delete api("/projects/#{project.id}/protected_tags/#{tag_name}", personal_access_token: pat) } end -- GitLab