From c35b02f87c30562f7879927aad0f8f0619777755 Mon Sep 17 00:00:00 2001 From: vjain-gl Date: Mon, 17 Nov 2025 19:13:36 +0530 Subject: [PATCH 1/5] Fix: Planner role unable to read_code/download_code on private projects with default permissions --- ee/app/policies/ee/project_policy.rb | 7 ++++++- spec/policies/project_policy_spec.rb | 2 +- .../policies/project_policy_shared_examples.rb | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 6d8d0002d3d571..48b5b318a19a4c 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -907,7 +907,12 @@ module ProjectPolicy rule { can?(:read_merge_request) & code_review_analytics_enabled }.enable :read_code_review_analytics - rule { private_project & planner }.prevent :read_code_review_analytics + rule { private_project & planner }.policy do + prevent :read_code_review_analytics + prevent :create_merge_request_in + enable :read_code + enable :download_code + end rule { (admin | reporter) & dora4_analytics_available } .enable :read_dora4_analytics diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index d533737845bcac..fec6989ae24707 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -3700,7 +3700,7 @@ def permissions_abilities(role) :maintainer | true :developer | true :reporter | true - :planner | false + :planner | true :guest | false end diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index abb669430192c2..e611028e9d01c7 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -216,7 +216,7 @@ disallowed_reporter_public_permissions + %i[ fork_project read_commit_status read_container_image read_deployment - read_environment create_merge_request_in download_code + read_environment create_merge_request_in ] end @@ -244,6 +244,7 @@ specify do expect_allowed(*guest_permissions) expect_allowed(*planner_permissions) + expect_allowed(:read_code, :download_code) expect_allowed(*(base_reporter_permissions - disallowed_reporter_permissions)) expect_disallowed(*disallowed_reporter_permissions) expect_disallowed(*(developer_permissions - [:create_wiki])) @@ -300,6 +301,7 @@ specify do expect_allowed(*guest_permissions) expect_allowed(*planner_permissions) + expect_allowed(:read_code, :download_code) expect_allowed(*(base_reporter_permissions - disallowed_reporter_permissions)) expect_disallowed(*disallowed_reporter_permissions) expect_disallowed(*(developer_permissions - [:create_wiki])) -- GitLab From 79c585e304f44985c787d47ad36f7a93afab5410 Mon Sep 17 00:00:00 2001 From: vjain-gl Date: Tue, 18 Nov 2025 12:09:38 +0530 Subject: [PATCH 2/5] Fix the specs --- spec/policies/project_policy_spec.rb | 22 +++++++++++-------- .../project_policy_shared_examples.rb | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index fec6989ae24707..1bd5a526d3a46a 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -260,12 +260,6 @@ def set_access_level(access_level) it { is_expected.not_to be_allowed(:create_merge_request_in) } end - context 'when the current_user is planner' do - let(:current_user) { planner } - - it { is_expected.not_to be_allowed(:create_merge_request_in) } - end - context 'when the current_user is reporter or above' do let(:current_user) { reporter } @@ -283,7 +277,7 @@ def set_access_level(access_level) context 'when project is public' do let(:project) { public_project } - %w[guest planner].each do |role| + %w[guest].each do |role| context "when the current_user is #{role}" do let(:current_user) { send(role) } @@ -295,7 +289,7 @@ def set_access_level(access_level) context 'when project is internal' do let(:project) { internal_project } - %w[guest planner].each do |role| + %w[guest].each do |role| context "when the current_user is #{role}" do let(:current_user) { send(role) } @@ -307,7 +301,7 @@ def set_access_level(access_level) context 'when project is private' do let(:project) { private_project } - %w[guest planner].each do |role| + %w[guest].each do |role| context "when the current_user is #{role}" do let(:current_user) { send(role) } @@ -322,6 +316,16 @@ def set_access_level(access_level) end end end + + context 'when the current_user is planner in private project' do + let(:project) { private_project } + let(:current_user) { planner } + + it 'cannot create merge requests even though they can download code' do + expect_allowed(:read_code, :download_code) + expect_disallowed(:create_merge_request_in) + end + end end context 'pipeline feature' do diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index e611028e9d01c7..f6953cd2698fca 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -246,6 +246,7 @@ expect_allowed(*planner_permissions) expect_allowed(:read_code, :download_code) expect_allowed(*(base_reporter_permissions - disallowed_reporter_permissions)) + expect_disallowed(:create_merge_request_in) expect_disallowed(*disallowed_reporter_permissions) expect_disallowed(*(developer_permissions - [:create_wiki])) expect_disallowed(*(maintainer_permissions - [:admin_wiki])) @@ -303,6 +304,7 @@ expect_allowed(*planner_permissions) expect_allowed(:read_code, :download_code) expect_allowed(*(base_reporter_permissions - disallowed_reporter_permissions)) + expect_disallowed(:create_merge_request_in) expect_disallowed(*disallowed_reporter_permissions) expect_disallowed(*(developer_permissions - [:create_wiki])) expect_disallowed(*(maintainer_permissions - [:admin_wiki])) -- GitLab From 42c517fd3bcd70cde9845186a948148a32a05882 Mon Sep 17 00:00:00 2001 From: vjain-gl Date: Thu, 20 Nov 2025 15:17:26 +0530 Subject: [PATCH 3/5] Move the permissions to CE and update read_code yml --- app/policies/project_policy.rb | 6 ++++++ ee/app/policies/ee/project_policy.rb | 7 +------ ee/config/custom_abilities/read_code.yml | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index b5cbe7b7a857bd..ec5ac68103a4b8 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -1280,6 +1280,12 @@ class ProjectPolicy < BasePolicy rule { can?(:read_project) }.enable :read_attestation + rule { private_project & planner }.policy do + prevent :create_merge_request_in + enable :read_code + enable :download_code + end + private def team_member? diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 48b5b318a19a4c..6d8d0002d3d571 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -907,12 +907,7 @@ module ProjectPolicy rule { can?(:read_merge_request) & code_review_analytics_enabled }.enable :read_code_review_analytics - rule { private_project & planner }.policy do - prevent :read_code_review_analytics - prevent :create_merge_request_in - enable :read_code - enable :download_code - end + rule { private_project & planner }.prevent :read_code_review_analytics rule { (admin | reporter) & dora4_analytics_available } .enable :read_dora4_analytics diff --git a/ee/config/custom_abilities/read_code.yml b/ee/config/custom_abilities/read_code.yml index b5abbec4eebf5c..b0911a924b41d9 100644 --- a/ee/config/custom_abilities/read_code.yml +++ b/ee/config/custom_abilities/read_code.yml @@ -7,8 +7,8 @@ introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/issues/376180 feature_category: source_code_management milestone: '15.7' group_ability: true -enabled_for_group_access_levels: [20, 30, 40, 50] +enabled_for_group_access_levels: [15, 20, 30, 40, 50] project_ability: true -enabled_for_project_access_levels: [20, 30, 40, 50] +enabled_for_project_access_levels: [15, 20, 30, 40, 50] skip_seat_consumption: true -available_from_access_level: 20 +available_from_access_level: 15 -- GitLab From 74f20ef844efd860d6a2bc02038dc0a3f59e5c4d Mon Sep 17 00:00:00 2001 From: vjain-gl Date: Tue, 25 Nov 2025 16:52:23 +0530 Subject: [PATCH 4/5] Updated the doc --- doc/user/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/permissions.md b/doc/user/permissions.md index fb78fc324abf5b..3a3334cec91081 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -780,7 +780,7 @@ Project permissions for [repository](project/repository/_index.md) features incl Ultimate license can view private repository content if an administrator (on GitLab Self-Managed or GitLab Dedicated) or group owner (on GitLab.com) gives those users permission. The administrator or group owner can create a [custom role](custom_roles/_index.md) through the API or UI and assign - that role to the users. + that role to the users. In GitLab 18.7 and later, users with the Planner role can view private repository content. 2. On GitLab Self-Managed, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users -- GitLab From 367dfaa257bbc27a27ed84ccd22d3c53b345c101 Mon Sep 17 00:00:00 2001 From: vjain-gl Date: Thu, 27 Nov 2025 13:25:44 +0530 Subject: [PATCH 5/5] Add enable policy to planne_access block and update the specs --- app/policies/project_policy.rb | 12 ++++++------ spec/policies/project_policy_spec.rb | 24 +++++++++--------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index ec5ac68103a4b8..6ba559d3e820c0 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -437,6 +437,12 @@ class ProjectPolicy < BasePolicy enable :read_internal_note enable :read_merge_request enable :export_work_items + enable :read_code + enable :download_code + end + + rule { private_project & planner }.policy do + prevent :create_merge_request_in end rule { can?(:reporter_access) & can?(:create_issue) }.enable :create_incident @@ -1280,12 +1286,6 @@ class ProjectPolicy < BasePolicy rule { can?(:read_project) }.enable :read_attestation - rule { private_project & planner }.policy do - prevent :create_merge_request_in - enable :read_code - enable :download_code - end - private def team_member? diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 1bd5a526d3a46a..e64f322321cd2b 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -277,36 +277,30 @@ def set_access_level(access_level) context 'when project is public' do let(:project) { public_project } - %w[guest].each do |role| - context "when the current_user is #{role}" do - let(:current_user) { send(role) } + context 'when the current_user is guest' do + let(:current_user) { guest } - it { is_expected.not_to be_allowed(:create_merge_request_in) } - end + it { is_expected.not_to be_allowed(:create_merge_request_in) } end end context 'when project is internal' do let(:project) { internal_project } - %w[guest].each do |role| - context "when the current_user is #{role}" do - let(:current_user) { send(role) } + context 'when the current_user is guest' do + let(:current_user) { guest } - it { is_expected.not_to be_allowed(:create_merge_request_in) } - end + it { is_expected.not_to be_allowed(:create_merge_request_in) } end end context 'when project is private' do let(:project) { private_project } - %w[guest].each do |role| - context "when the current_user is #{role}" do - let(:current_user) { send(role) } + context 'when the current_user is guest' do + let(:current_user) { guest } - it { is_expected.not_to be_allowed(:create_merge_request_in) } - end + it { is_expected.not_to be_allowed(:create_merge_request_in) } end context 'when the current_user is reporter or above' do -- GitLab