From 4b1667a56a5ec2aa9fa2f2e5040ade47175fb23e Mon Sep 17 00:00:00 2001 From: Duo Developer Date: Thu, 18 Dec 2025 03:00:18 +0000 Subject: [PATCH] feat: Add granular PAT permissions for Approval Rules API Implement granular Personal Access Token permissions for Approval Rules REST API endpoints at both project and group levels. This enables fine-grained access control for approval rule operations. - Create 4 permission definitions (read, create, update, delete) - Add route_setting decorators to 8 API endpoints - Implement comprehensive authorization test coverage - Support both project and group boundary contexts --- .../permissions/approval_rule/create.yml | 8 ++++ .../permissions/approval_rule/delete.yml | 7 ++++ .../authz/permissions/approval_rule/read.yml | 8 ++++ .../permissions/approval_rule/update.yml | 8 ++++ ee/lib/api/group_approval_rules.rb | 4 +- ee/lib/api/project_approval_rules.rb | 5 +++ .../requests/api/group_approval_rules_spec.rb | 24 +++++++++++ .../api/project_approval_rules_spec.rb | 40 +++++++++++++++++++ 8 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 config/authz/permissions/approval_rule/create.yml create mode 100644 config/authz/permissions/approval_rule/delete.yml create mode 100644 config/authz/permissions/approval_rule/read.yml create mode 100644 config/authz/permissions/approval_rule/update.yml diff --git a/config/authz/permissions/approval_rule/create.yml b/config/authz/permissions/approval_rule/create.yml new file mode 100644 index 00000000000000..71f1be13eb96b7 --- /dev/null +++ b/config/authz/permissions/approval_rule/create.yml @@ -0,0 +1,8 @@ +--- +name: create_approval_rule +description: Grants the ability to create approval rules +feature_category: source_code_management +available_for_tokens: true +boundaries: + - project + - group diff --git a/config/authz/permissions/approval_rule/delete.yml b/config/authz/permissions/approval_rule/delete.yml new file mode 100644 index 00000000000000..d39d134236bef6 --- /dev/null +++ b/config/authz/permissions/approval_rule/delete.yml @@ -0,0 +1,7 @@ +--- +name: delete_approval_rule +description: Grants the ability to delete approval rules +feature_category: source_code_management +available_for_tokens: true +boundaries: + - project diff --git a/config/authz/permissions/approval_rule/read.yml b/config/authz/permissions/approval_rule/read.yml new file mode 100644 index 00000000000000..929133881340ba --- /dev/null +++ b/config/authz/permissions/approval_rule/read.yml @@ -0,0 +1,8 @@ +--- +name: read_approval_rule +description: Grants the ability to read approval rules +feature_category: source_code_management +available_for_tokens: true +boundaries: + - project + - group diff --git a/config/authz/permissions/approval_rule/update.yml b/config/authz/permissions/approval_rule/update.yml new file mode 100644 index 00000000000000..65760e05c61434 --- /dev/null +++ b/config/authz/permissions/approval_rule/update.yml @@ -0,0 +1,8 @@ +--- +name: update_approval_rule +description: Grants the ability to update approval rules +feature_category: source_code_management +available_for_tokens: true +boundaries: + - project + - group diff --git a/ee/lib/api/group_approval_rules.rb b/ee/lib/api/group_approval_rules.rb index 46ec7f02d0ce49..af9d591bee2169 100644 --- a/ee/lib/api/group_approval_rules.rb +++ b/ee/lib/api/group_approval_rules.rb @@ -23,6 +23,7 @@ class GroupApprovalRules < ::API::Base params do use :pagination end + route_setting :authorization, permissions: :read_approval_rule, boundary_type: :group get do authorize_group_approval_rule! @@ -39,6 +40,7 @@ class GroupApprovalRules < ::API::Base requires :approvals_required, type: Integer, desc: 'The number of required approvals for this rule' use :group_approval_rule end + route_setting :authorization, permissions: :create_approval_rule, boundary_type: :group post do create_group_approval_rule(present_with: ::API::Entities::GroupApprovalRule) end @@ -48,7 +50,7 @@ class GroupApprovalRules < ::API::Base optional :approvals_required, type: Integer, desc: 'The number of required approvals for this rule' use :group_approval_rule end - + route_setting :authorization, permissions: :update_approval_rule, boundary_type: :group put ':approval_rule_id' do update_group_approval_rule(present_with: ::API::Entities::GroupApprovalRule) end diff --git a/ee/lib/api/project_approval_rules.rb b/ee/lib/api/project_approval_rules.rb index 9206c1b334eabd..50c8e5e8cd2dcb 100644 --- a/ee/lib/api/project_approval_rules.rb +++ b/ee/lib/api/project_approval_rules.rb @@ -23,6 +23,7 @@ class ProjectApprovalRules < ::API::Base params do use :pagination end + route_setting :authorization, permissions: :read_approval_rule, boundary_type: :project get do authorize_read_project_approval_rule! @@ -36,6 +37,7 @@ class ProjectApprovalRules < ::API::Base params do use :create_project_approval_rule end + route_setting :authorization, permissions: :create_approval_rule, boundary_type: :project post do create_project_approval_rule(present_with: ::API::Entities::ProjectApprovalRule) end @@ -45,6 +47,7 @@ class ProjectApprovalRules < ::API::Base success ::API::Entities::ProjectApprovalRule tags %w[project_approval_rules] end + route_setting :authorization, permissions: :read_approval_rule, boundary_type: :project get do authorize_read_project_approval_rule! @@ -60,6 +63,7 @@ class ProjectApprovalRules < ::API::Base params do use :update_project_approval_rule end + route_setting :authorization, permissions: :update_approval_rule, boundary_type: :project put do update_project_approval_rule(present_with: ::API::Entities::ProjectApprovalRule) end @@ -71,6 +75,7 @@ class ProjectApprovalRules < ::API::Base params do use :delete_project_approval_rule end + route_setting :authorization, permissions: :delete_approval_rule, boundary_type: :project delete do destroy_project_approval_rule end diff --git a/ee/spec/requests/api/group_approval_rules_spec.rb b/ee/spec/requests/api/group_approval_rules_spec.rb index 0ddd2f8407533a..3420813e47bd47 100644 --- a/ee/spec/requests/api/group_approval_rules_spec.rb +++ b/ee/spec/requests/api/group_approval_rules_spec.rb @@ -51,6 +51,14 @@ it_behaves_like 'check for approval_group_rule feature flag' it_behaves_like 'check that user can update approval rules' + it_behaves_like 'authorizing granular token permissions', :read_approval_rule do + let(:boundary_object) { group } + let(:user) { user_with_access } + let(:request) do + get api("/groups/#{group.id}/approval_rules", personal_access_token: pat) + end + end + it 'returns the group approval rules' do request @@ -88,6 +96,14 @@ it_behaves_like 'check for approval_group_rule feature flag' it_behaves_like 'check that user can update approval rules' + it_behaves_like 'authorizing granular token permissions', :create_approval_rule do + let(:boundary_object) { group } + let(:user) { user_with_access } + let(:request) do + post api("/groups/#{group.id}/approval_rules", personal_access_token: pat), params: { name: 'security', approvals_required: 10 } + end + end + it 'returns 201 status' do request @@ -179,6 +195,14 @@ it_behaves_like 'check for approval_group_rule feature flag' it_behaves_like 'check that user can update approval rules' + it_behaves_like 'authorizing granular token permissions', :update_approval_rule do + let(:boundary_object) { group } + let(:user) { user_with_access } + let(:request) do + put api("/groups/#{group.id}/approval_rules/#{approval_group_rule.id}", personal_access_token: pat), params: { name: 'updated' } + end + end + it 'returns 200 status' do request diff --git a/ee/spec/requests/api/project_approval_rules_spec.rb b/ee/spec/requests/api/project_approval_rules_spec.rb index 98cd632736741f..c67fa26b01da1e 100644 --- a/ee/spec/requests/api/project_approval_rules_spec.rb +++ b/ee/spec/requests/api/project_approval_rules_spec.rb @@ -22,6 +22,14 @@ let!(:approval_rule) { create(:approval_project_rule, project: private_project) } let(:url) { "/projects/#{private_project.id}/approval_rules/#{approval_rule.id}" } + it_behaves_like 'authorizing granular token permissions', :read_approval_rule do + let(:boundary_object) { private_project } + let(:user) { user } + let(:request) do + get api("/projects/#{private_project.id}/approval_rules/#{approval_rule.id}", personal_access_token: pat) + end + end + context 'when the request is correct' do it 'matches the response schema' do get api(url, user) @@ -188,12 +196,28 @@ expect(response).to have_gitlab_http_status(:forbidden) end end + + it_behaves_like 'authorizing granular token permissions', :read_approval_rule do + let(:boundary_object) { project } + let(:user) { user } + let(:request) do + get api("/projects/#{project.id}/approval_rules", personal_access_token: pat) + end + end end describe 'POST /projects/:id/approval_rules' do let(:schema) { 'public_api/v4/project_approval_rule' } let(:url) { "/projects/#{project.id}/approval_rules" } + it_behaves_like 'authorizing granular token permissions', :create_approval_rule do + let(:boundary_object) { project } + let(:user) { user } + let(:request) do + post api("/projects/#{project.id}/approval_rules", personal_access_token: pat), params: { name: 'security', approvals_required: 10 } + end + end + it_behaves_like 'an API endpoint for creating project approval rule' end @@ -202,6 +226,14 @@ let(:schema) { 'public_api/v4/project_approval_rule' } let(:url) { "/projects/#{project.id}/approval_rules/#{approval_rule.id}" } + it_behaves_like 'authorizing granular token permissions', :update_approval_rule do + let(:boundary_object) { project } + let(:user) { user } + let(:request) do + put api("/projects/#{project.id}/approval_rules/#{approval_rule.id}", personal_access_token: pat), params: { name: 'updated' } + end + end + it_behaves_like 'an API endpoint for updating project approval rule' end @@ -209,6 +241,14 @@ let!(:approval_rule) { create(:approval_project_rule, project: project) } let(:url) { "/projects/#{project.id}/approval_rules/#{approval_rule.id}" } + it_behaves_like 'authorizing granular token permissions', :delete_approval_rule do + let(:boundary_object) { project } + let(:user) { user } + let(:request) do + delete api("/projects/#{project.id}/approval_rules/#{approval_rule.id}", personal_access_token: pat) + end + end + it_behaves_like 'an API endpoint for deleting project approval rule' end end -- GitLab