diff --git a/app/presenters/projects/security/configuration_presenter.rb b/app/presenters/projects/security/configuration_presenter.rb index cb368b804a12d72948006620ac239520d0564a1e..7df38dd7d6f6543a2edbe895fcf2b0be8645bd87 100644 --- a/app/presenters/projects/security/configuration_presenter.rb +++ b/app/presenters/projects/security/configuration_presenter.rb @@ -45,7 +45,7 @@ def can_enable_auto_devops? end def user_is_project_admin? - can?(current_user, :admin_project, self) + can?(current_user, :admin_security_testing, self) end def gitlab_ci_history_path diff --git a/app/validators/json_schemas/member_role_permissions.json b/app/validators/json_schemas/member_role_permissions.json index 92efa0f0f5583f4e6026ec2cfeb37dd68f0c1f74..042ab9ee5b4dc66573773835068fd9da67deb2be 100644 --- a/app/validators/json_schemas/member_role_permissions.json +++ b/app/validators/json_schemas/member_role_permissions.json @@ -28,6 +28,9 @@ "admin_runners": { "type": "boolean" }, + "admin_security_testing": { + "type": "boolean" + }, "admin_terraform_state": { "type": "boolean" }, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 2ed172aee518b52b7b727a9cd8e973078ffe8d3c..d36518747a07bd5c920cfaaf7eaee63b56e22c31 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -40942,6 +40942,7 @@ Member role permission. | `ADMIN_PROTECTED_BRANCH` | Create, read, update, and delete protected branches for a project. | | `ADMIN_PUSH_RULES` | Configure push rules for repositories at the group or project level. | | `ADMIN_RUNNERS` | Create, view, edit, and delete group or project Runners. Includes configuring Runner settings. | +| `ADMIN_SECURITY_TESTING` | Edit and manage security testing configurations and settings. | | `ADMIN_TERRAFORM_STATE` | Execute terraform commands, lock/unlock terraform state files, and remove file versions. | | `ADMIN_VULNERABILITY` | Edit the vulnerability object, including the status and linking an issue. Includes the `read_vulnerability` permission actions. | | `ADMIN_WEB_HOOK` | Manage webhooks. | @@ -40977,6 +40978,7 @@ Member role standard permission. | `ADMIN_PROTECTED_BRANCH` | Create, read, update, and delete protected branches for a project. | | `ADMIN_PUSH_RULES` | Configure push rules for repositories at the group or project level. | | `ADMIN_RUNNERS` | Create, view, edit, and delete group or project Runners. Includes configuring Runner settings. | +| `ADMIN_SECURITY_TESTING` | Edit and manage security testing configurations and settings. | | `ADMIN_TERRAFORM_STATE` | Execute terraform commands, lock/unlock terraform state files, and remove file versions. | | `ADMIN_VULNERABILITY` | Edit the vulnerability object, including the status and linking an issue. Includes the `read_vulnerability` permission actions. | | `ADMIN_WEB_HOOK` | Manage webhooks. | diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index 4c768b013bb4edbb306f9c8131aec5039ba20ad7..1d2408bda353ae0e6cad2588549799f9bea0d89a 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -24,7 +24,8 @@ DETAILS: > - [Delete project introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139696) in GitLab 16.8. > - [Manage group access tokens introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140115) in GitLab 16.8. > - [Admin terraform state introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140759) in GitLab 16.8. -> - Ability to create and remove an instance-wide custom role on GitLab Self-Managed [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141562) in GitLab 16.9. +> - Allow to create and remove an instance-wide custom role on GitLab Self-Managed [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141562) in GitLab 16.9. +> - [Admin security testing introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) in GitLab 17.9 [with a flag](../administration/feature_flags.md) named `custom_ability_admin_security_testing`. Disabled by default. Use this API to interact with member roles for your GitLab.com groups or entire self-managed instance. diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index 298ebcd267cca53bb6f7fd2b89829ebc3e1013bc..9b59fb806bf2b494f84f7185de3994695725bb4e 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -44037,6 +44037,8 @@ definitions: type: boolean admin_vulnerability: type: boolean + admin_security_testing: + type: boolean admin_web_hook: type: boolean archive_project: diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 0f82bbee4923a82c315b52b7395093f152526276..f686c2414a6da4a566a03380fba682ade9c81185 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -319,6 +319,7 @@ module GroupPolicy enable :read_jobs_statistics enable :read_runner_usage enable :admin_push_rules + enable :admin_security_testing end rule { (admin | maintainer) & group_analytics_dashboards_available & ~has_parent }.policy do @@ -574,6 +575,21 @@ module GroupPolicy enable :admin_vulnerability end + rule { custom_role_enables_admin_security_testing }.policy do + enable :admin_security_testing + end + + rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do + enable :access_security_and_compliance + enable :read_security_configuration + enable :read_group_security_dashboard + enable :read_security_resource + end + + rule { pre_receive_secret_detection_available & can?(:admin_security_testing) }.policy do + enable :enable_pre_receive_secret_detection + end + rule { custom_role_enables_admin_group_member }.policy do enable :admin_group_member enable :update_group_member diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 298ef2ca8956b0726a14096dc99c32aef5209263..5e511e76fc01999520782c88340fddd567917acb 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -456,6 +456,55 @@ module ProjectPolicy enable :create_on_demand_dast_scan end + rule { security_dashboard_enabled & can?(:maintainer_access) }.policy do + enable :admin_security_testing + end + + rule { custom_role_enables_admin_security_testing }.policy do + enable :admin_security_testing + end + + rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do + enable :access_security_and_compliance + enable :read_security_configuration + enable :read_project_security_dashboard + enable :read_security_resource + + # create scanner configuration + enable :push_code + enable :download_code + enable :read_merge_request + enable :create_merge_request_from + end + + rule { pre_receive_secret_detection_available & can?(:admin_security_testing) }.policy do + enable :read_pre_receive_secret_detection_info + enable :enable_pre_receive_secret_detection + enable :read_project_security_exclusions + end + + rule { container_scanning_for_registry_available & can?(:admin_security_testing) }.policy do + enable :enable_container_scanning_for_registry + end + + rule { coverage_fuzzing_enabled & can?(:admin_security_testing) }.policy do + enable :read_coverage_fuzzing + enable :create_coverage_fuzzing_corpus + end + + rule { security_scans_api_enabled & can?(:admin_security_testing) }.policy do + enable :access_security_scans_api + end + + rule { on_demand_scans_enabled & can?(:admin_security_testing) }.policy do + enable :read_on_demand_dast_scan + enable :create_on_demand_dast_scan + enable :edit_on_demand_dast_scan + + enable :read_project_runners # read runner tags when creating scan + enable :create_pipeline # run a scan + end + rule { security_dashboard_enabled & security_policy_bot }.policy do enable :create_vulnerability_state_transition end diff --git a/ee/config/custom_abilities/admin_security_testing.yml b/ee/config/custom_abilities/admin_security_testing.yml new file mode 100644 index 0000000000000000000000000000000000000000..21d9bde664b7092b58225568483cb332d89fca1a --- /dev/null +++ b/ee/config/custom_abilities/admin_security_testing.yml @@ -0,0 +1,13 @@ +--- +title: Manage security testing +name: admin_security_testing +description: Edit and manage security testing configurations and settings. +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/508649 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628 +feature_category: security_policy_management +milestone: "17.9" +group_ability: true +project_ability: true +requirements: [] +enabled_for_group_access_levels: [40, 50] +enabled_for_project_access_levels: [40, 50] diff --git a/ee/config/feature_flags/beta/custom_ability_admin_security_testing.yml b/ee/config/feature_flags/beta/custom_ability_admin_security_testing.yml new file mode 100644 index 0000000000000000000000000000000000000000..8bda7bdf94ad6fd13684d9edff532386565327bd --- /dev/null +++ b/ee/config/feature_flags/beta/custom_ability_admin_security_testing.yml @@ -0,0 +1,9 @@ +--- +name: custom_ability_admin_security_testing +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/508649 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/513510 +milestone: "17.9" +group: group::security platform management +type: beta +default_enabled: false diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index a72f75a73559abfb8adc8ad42509f237c6fec40d..a0785058b827bfed2e6b2abcf3ce323eb9c28090 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -3956,6 +3956,27 @@ def create_member_role(member, abilities = member_role_abilities) it_behaves_like 'custom roles abilities' end + context 'for a custom role with the `admin_security_testing` ability' do + let(:member_role_abilities) { { admin_security_testing: true } } + let(:licensed_features) do + { security_dashboard: true, + pre_receive_secret_detection: true, + group_level_compliance_dashboard: true } + end + + let(:allowed_abilities) do + [ + :access_security_and_compliance, + :read_security_configuration, + :read_group_security_dashboard, + :read_security_resource, + :enable_pre_receive_secret_detection + ] + end + + it_behaves_like 'custom roles abilities' + end + context 'when compliance framework feature is unavailable' do before do create_member_role(group_member_guest) diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 3dceb8e71dae3fafa38d9da12218b2fc94ce4624..3d34a18de580073518068361a72068c489668730 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -724,169 +724,128 @@ it_behaves_like 'correct access to security and compliance' end - end - describe 'vulnerability feedback permissions' do - before do - stub_licensed_features(security_dashboard: true) - end + describe 'vulnerability feedback permissions' do + before do + stub_licensed_features(security_dashboard: true) + end - context 'with developer' do - let(:current_user) { developer } + context 'with developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(:read_vulnerability_feedback) } - it { is_expected.to be_disallowed(:create_vulnerability_feedback) } - it { is_expected.to be_disallowed(:update_vulnerability_feedback) } - it { is_expected.to be_disallowed(:destroy_vulnerability_feedback) } - end + it { is_expected.to be_allowed(:read_vulnerability_feedback) } + it { is_expected.to be_disallowed(:create_vulnerability_feedback) } + it { is_expected.to be_disallowed(:update_vulnerability_feedback) } + it { is_expected.to be_disallowed(:destroy_vulnerability_feedback) } + end - where(permission: %i[ - read_vulnerability_feedback - create_vulnerability_feedback - update_vulnerability_feedback - destroy_vulnerability_feedback - ]) + where(permission: %i[ + read_vulnerability_feedback + create_vulnerability_feedback + update_vulnerability_feedback + destroy_vulnerability_feedback + ]) - with_them do - context 'with admin' do - let(:current_user) { admin } + with_them do + context 'with admin' do + let(:current_user) { admin } - context 'when admin mode enabled', :enable_admin_mode do - it { is_expected.to be_allowed(permission) } - end + context 'when admin mode enabled', :enable_admin_mode do + it { is_expected.to be_allowed(permission) } + end - context 'when admin mode disabled' do - it { is_expected.to be_disallowed(permission) } + context 'when admin mode disabled' do + it { is_expected.to be_disallowed(permission) } + end end - end - %w[owner maintainer].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + %w[owner maintainer].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(permission) } + it { is_expected.to be_allowed(permission) } + end end - end - %w[anonymous non_member guest planner reporter].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + %w[anonymous non_member guest planner reporter].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(permission) } + it { is_expected.to be_disallowed(permission) } + end end end end - end - - shared_context 'when security dashboard feature is not available' do - before do - stub_licensed_features(security_dashboard: false) - end - end - describe 'read_project_security_dashboard' do - context 'with developer' do - let(:current_user) { developer } - - include_context 'when security dashboard feature is not available' - - it { is_expected.to be_disallowed(:read_project_security_dashboard) } + shared_context 'when security dashboard feature is not available' do + before do + stub_licensed_features(security_dashboard: false) + end end - end - describe 'vulnerability permissions' do - describe 'dismiss_vulnerability' do + describe 'read_project_security_dashboard' do context 'with developer' do let(:current_user) { developer } include_context 'when security dashboard feature is not available' - it { is_expected.to be_disallowed(:admin_vulnerability) } - it { is_expected.to be_disallowed(:read_vulnerability) } - it { is_expected.to be_disallowed(:create_vulnerability_export) } - end - end - end - - describe 'permissions for security bot' do - let_it_be(:current_user) { create(:user, :security_bot) } - let(:project) { private_project } - - let(:permissions) do - %i[ - reporter_access - push_code - create_merge_request_from - create_merge_request_in - create_vulnerability_feedback - read_project - admin_merge_request - ] - end - - context 'when project does not have a security_setting' do - before do - project.security_setting.delete - project.reload + it { is_expected.to be_disallowed(:read_project_security_dashboard) } end + end - it { is_expected.to be_allowed(*permissions) } - - context 'with user other than security bot' do - let_it_be(:current_user) { create(:user) } + describe 'vulnerability permissions' do + describe 'dismiss_vulnerability' do + context 'with developer' do + let(:current_user) { developer } - it { is_expected.to be_disallowed(*permissions) } + include_context 'when security dashboard feature is not available' + it { is_expected.to be_disallowed(:admin_vulnerability) } + it { is_expected.to be_disallowed(:read_vulnerability) } + it { is_expected.to be_disallowed(:create_vulnerability_export) } + end end end - end - - describe 'security orchestration policies' do - before do - stub_licensed_features(security_orchestration_policies: true) - end - context 'with developer or maintainer role' do - where(role: %w[maintainer developer]) + describe 'permissions for security bot' do + let_it_be(:current_user) { create(:user, :security_bot) } - with_them do - let(:current_user) { public_send(role) } + let(:project) { private_project } - it { is_expected.to be_allowed(:read_security_orchestration_policies) } - it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) } + let(:permissions) do + %i[ + reporter_access + push_code + create_merge_request_from + create_merge_request_in + create_vulnerability_feedback + read_project + admin_merge_request + ] end - end - - context 'with owner role' do - where(role: %w[owner]) - - with_them do - let(:current_user) { public_send(role) } - it { is_expected.to be_allowed(:read_security_orchestration_policies) } - it { is_expected.to be_allowed(:update_security_orchestration_policy_project) } - it { is_expected.to be_allowed(:modify_security_policy) } + context 'when project does not have a security_setting' do + before do + project.security_setting.delete + project.reload + end - context 'when security_orchestration_policy_configuration is present' do - let_it_be(:security_policy_management_project) { create(:project) } + it { is_expected.to be_allowed(*permissions) } - before do - create(:security_orchestration_policy_configuration, project: project, security_policy_management_project: security_policy_management_project) - end + context 'with user other than security bot' do + let_it_be(:current_user) { create(:user) } - it { is_expected.to be_disallowed(:modify_security_policy) } + it { is_expected.to be_disallowed(*permissions) } end end end - context 'with auditor role' do - where(role: %w[auditor]) - + describe 'security orchestration policies' do before do - project.project_feature.update!(security_orchestration_policies: feature_status) + stub_licensed_features(security_orchestration_policies: true) end - context 'with policy feature enabled' do - let(:feature_status) { ProjectFeature::ENABLED } + context 'with developer or maintainer role' do + where(role: %w[maintainer developer]) with_them do let(:current_user) { public_send(role) } @@ -896,3732 +855,3813 @@ end end - context 'with policy feature disabled' do - let(:feature_status) { ProjectFeature::DISABLED } + context 'with owner role' do + where(role: %w[owner]) with_them do let(:current_user) { public_send(role) } - it { is_expected.to be_disallowed(:read_security_orchestration_policies) } - it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) } + it { is_expected.to be_allowed(:read_security_orchestration_policies) } + it { is_expected.to be_allowed(:update_security_orchestration_policy_project) } + it { is_expected.to be_allowed(:modify_security_policy) } + + context 'when security_orchestration_policy_configuration is present' do + let_it_be(:security_policy_management_project) { create(:project) } + + before do + create(:security_orchestration_policy_configuration, project: project, security_policy_management_project: security_policy_management_project) + end + + it { is_expected.to be_disallowed(:modify_security_policy) } + end end end - end - context 'when security_orchestration_policy_configuration is present' do - let_it_be(:security_policy_management_project) { create(:project) } - let(:current_user) { developer } + context 'with auditor role' do + where(role: %w[auditor]) - before do - create(:security_orchestration_policy_configuration, project: project, security_policy_management_project: security_policy_management_project) + before do + project.project_feature.update!(security_orchestration_policies: feature_status) + end + + context 'with policy feature enabled' do + let(:feature_status) { ProjectFeature::ENABLED } + + with_them do + let(:current_user) { public_send(role) } + + it { is_expected.to be_allowed(:read_security_orchestration_policies) } + it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) } + end + end + + context 'with policy feature disabled' do + let(:feature_status) { ProjectFeature::DISABLED } + + with_them do + let(:current_user) { public_send(role) } + + it { is_expected.to be_disallowed(:read_security_orchestration_policies) } + it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) } + end + end end - context 'when current_user is developer of security_policy_management_project' do + context 'when security_orchestration_policy_configuration is present' do + let_it_be(:security_policy_management_project) { create(:project) } + let(:current_user) { developer } + before do - security_policy_management_project.add_developer(developer) + create(:security_orchestration_policy_configuration, project: project, security_policy_management_project: security_policy_management_project) end - it { is_expected.to be_allowed(:modify_security_policy) } - end + context 'when current_user is developer of security_policy_management_project' do + before do + security_policy_management_project.add_developer(developer) + end - context 'when current_user is not developer of security_policy_management_project' do - it { is_expected.to be_disallowed(:modify_security_policy) } + it { is_expected.to be_allowed(:modify_security_policy) } + end + + context 'when current_user is not developer of security_policy_management_project' do + it { is_expected.to be_disallowed(:modify_security_policy) } + end end end - end - describe 'coverage_fuzzing' do - context 'when coverage_fuzzing feature is available' do - before do - stub_licensed_features(coverage_fuzzing: true) - end + describe 'coverage_fuzzing' do + context 'when coverage_fuzzing feature is available' do + before do + stub_licensed_features(coverage_fuzzing: true) + end - context 'with developer or higher role' do - where(role: %w[owner maintainer developer]) + context 'with developer or higher role' do + where(role: %w[owner maintainer developer]) - with_them do - let(:current_user) { public_send(role) } + with_them do + let(:current_user) { public_send(role) } - it { is_expected.to be_allowed(:read_coverage_fuzzing) } + it { is_expected.to be_allowed(:read_coverage_fuzzing) } + end end - end - context 'with admin' do - let(:current_user) { admin } + context 'with admin' do + let(:current_user) { admin } - context 'when admin mode enabled', :enable_admin_mode do - it { is_expected.to be_allowed(:read_coverage_fuzzing) } - end + context 'when admin mode enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:read_coverage_fuzzing) } + end - context 'when admin mode disabled' do - it { is_expected.to be_disallowed(:read_coverage_fuzzing) } + context 'when admin mode disabled' do + it { is_expected.to be_disallowed(:read_coverage_fuzzing) } + end end - end - context 'with less than developer role' do - where(role: %w[reporter planner guest]) + context 'with less than developer role' do + where(role: %w[reporter planner guest]) - with_them do - let(:current_user) { public_send(role) } + with_them do + let(:current_user) { public_send(role) } + + it { is_expected.to be_disallowed(:read_coverage_fuzzing) } + end + end + + context 'with non member' do + let(:current_user) { non_member } it { is_expected.to be_disallowed(:read_coverage_fuzzing) } end - end - context 'with non member' do - let(:current_user) { non_member } + context 'with anonymous' do + let(:current_user) { anonymous } - it { is_expected.to be_disallowed(:read_coverage_fuzzing) } + it { is_expected.to be_disallowed(:read_coverage_fuzzing) } + end end - context 'with anonymous' do - let(:current_user) { anonymous } + context 'when coverage fuzzing feature is not available' do + let(:current_user) { admin } + + before do + stub_licensed_features(coverage_fuzzing: true) + end it { is_expected.to be_disallowed(:read_coverage_fuzzing) } end end - context 'when coverage fuzzing feature is not available' do - let(:current_user) { admin } - + describe 'remove_project when default_project_deletion_protection is set to true' do before do - stub_licensed_features(coverage_fuzzing: true) + allow(Gitlab::CurrentSettings.current_application_settings) + .to receive(:default_project_deletion_protection) { true } end - it { is_expected.to be_disallowed(:read_coverage_fuzzing) } - end - end + context 'with admin' do + let(:current_user) { admin } - describe 'remove_project when default_project_deletion_protection is set to true' do - before do - allow(Gitlab::CurrentSettings.current_application_settings) - .to receive(:default_project_deletion_protection) { true } - end + context 'when admin mode enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:remove_project) } + end - context 'with admin' do - let(:current_user) { admin } + context 'when admin mode disabled' do + it { is_expected.to be_disallowed(:remove_project) } + end - context 'when admin mode enabled', :enable_admin_mode do - it { is_expected.to be_allowed(:remove_project) } - end + context 'who owns the project' do + let(:project) { create(:project, :public, namespace: admin.namespace) } - context 'when admin mode disabled' do - it { is_expected.to be_disallowed(:remove_project) } + it { is_expected.to be_disallowed(:remove_project) } + end end - context 'who owns the project' do - let(:project) { create(:project, :public, namespace: admin.namespace) } + context 'with owner' do + let(:current_user) { owner } it { is_expected.to be_disallowed(:remove_project) } end end - context 'with owner' do - let(:current_user) { owner } - - it { is_expected.to be_disallowed(:remove_project) } - end - end + describe 'admin_feature_flags_issue_links' do + before do + stub_licensed_features(feature_flags_related_issues: true) + end - describe 'admin_feature_flags_issue_links' do - before do - stub_licensed_features(feature_flags_related_issues: true) - end + context 'with maintainer' do + let(:current_user) { maintainer } - context 'with maintainer' do - let(:current_user) { maintainer } + it { is_expected.to be_allowed(:admin_feature_flags_issue_links) } - it { is_expected.to be_allowed(:admin_feature_flags_issue_links) } + context 'when repository is disabled' do + before do + project.project_feature.update!( + merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::DISABLED + ) + end - context 'when repository is disabled' do - before do - project.project_feature.update!( - merge_requests_access_level: ProjectFeature::DISABLED, - builds_access_level: ProjectFeature::DISABLED, - repository_access_level: ProjectFeature::DISABLED - ) + it { is_expected.to be_disallowed(:admin_feature_flags_issue_links) } end - - it { is_expected.to be_disallowed(:admin_feature_flags_issue_links) } end - end - context 'with developer' do - let(:current_user) { developer } + context 'with developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(:admin_feature_flags_issue_links) } + it { is_expected.to be_allowed(:admin_feature_flags_issue_links) } - context 'when feature is unlicensed' do - before do - stub_licensed_features(feature_flags_related_issues: false) + context 'when feature is unlicensed' do + before do + stub_licensed_features(feature_flags_related_issues: false) + end + + it { is_expected.to be_disallowed(:admin_feature_flags_issue_links) } end + end + + context 'with reporter' do + let(:current_user) { reporter } it { is_expected.to be_disallowed(:admin_feature_flags_issue_links) } end end - context 'with reporter' do - let(:current_user) { reporter } + describe 'admin_software_license_policy' do + context 'without license scanning feature available' do + before do + stub_licensed_features(license_scanning: false) + end - it { is_expected.to be_disallowed(:admin_feature_flags_issue_links) } - end - end + let(:current_user) { admin } - describe 'admin_software_license_policy' do - context 'without license scanning feature available' do - before do - stub_licensed_features(license_scanning: false) + it { is_expected.to be_disallowed(:admin_software_license_policy) } end - let(:current_user) { admin } - - it { is_expected.to be_disallowed(:admin_software_license_policy) } - end - - context 'with admin' do - let(:current_user) { admin } + context 'with admin' do + let(:current_user) { admin } - context 'when admin mode enabled', :enable_admin_mode do - it { is_expected.to be_allowed(:admin_software_license_policy) } - end + context 'when admin mode enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:admin_software_license_policy) } + end - context 'when admin mode disabled' do - it { is_expected.to be_disallowed(:admin_software_license_policy) } + context 'when admin mode disabled' do + it { is_expected.to be_disallowed(:admin_software_license_policy) } + end end - end - %w[owner maintainer].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + %w[owner maintainer].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:admin_software_license_policy) } + it { is_expected.to be_allowed(:admin_software_license_policy) } + end end - end - %w[anonymous non_member guest planner reporter developer].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + %w[anonymous non_member guest planner reporter developer].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:admin_software_license_policy) } + it { is_expected.to be_disallowed(:admin_software_license_policy) } + end end end - end - - describe 'read_software_license_policy' do - context 'without license scanning feature available' do - before do - stub_licensed_features(license_scanning: false) - end - let(:current_user) { admin } + describe 'read_software_license_policy' do + context 'without license scanning feature available' do + before do + stub_licensed_features(license_scanning: false) + end - it { is_expected.to be_disallowed(:read_software_license_policy) } - end - end + let(:current_user) { admin } - describe 'read_dependency' do - context 'when dependency scanning feature available' do - before do - stub_licensed_features(dependency_scanning: true) + it { is_expected.to be_disallowed(:read_software_license_policy) } end + end - context 'with public project' do - let(:current_user) { create(:user) } - - context 'with public access to repository' do - let(:project) { public_project } - - it { is_expected.to be_allowed(:read_dependency) } - end - - context 'with limited access to repository' do - let(:project) { create(:project, :public, :repository_private) } - - it { is_expected.not_to be_allowed(:read_dependency) } + describe 'read_dependency' do + context 'when dependency scanning feature available' do + before do + stub_licensed_features(dependency_scanning: true) end - end - context 'with private project' do - let(:project) { private_project } + context 'with public project' do + let(:current_user) { create(:user) } - context 'with admin' do - let(:current_user) { admin } + context 'with public access to repository' do + let(:project) { public_project } - context 'when admin mode enabled', :enable_admin_mode do it { is_expected.to be_allowed(:read_dependency) } end - context 'when admin mode disabled' do - it { is_expected.to be_disallowed(:read_dependency) } + context 'with limited access to repository' do + let(:project) { create(:project, :public, :repository_private) } + + it { is_expected.not_to be_allowed(:read_dependency) } end end - %w[owner maintainer developer reporter].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + context 'with private project' do + let(:project) { private_project } - it { is_expected.to be_allowed(:read_dependency) } - end - end + context 'with admin' do + let(:current_user) { admin } - %w[anonymous non_member guest planner].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + context 'when admin mode enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:read_dependency) } + end - it { is_expected.to be_disallowed(:read_dependency) } + context 'when admin mode disabled' do + it { is_expected.to be_disallowed(:read_dependency) } + end end - end - end - end - context 'when dependency list feature not available' do - let(:current_user) { admin } + %w[owner maintainer developer reporter].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.not_to be_allowed(:read_dependency) } - end - end + it { is_expected.to be_allowed(:read_dependency) } + end + end - describe 'read_licenses' do - context 'when license management feature available' do - context 'with public project' do - let(:current_user) { non_member } + %w[anonymous non_member guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - context 'with public access to repository' do - it { is_expected.to be_allowed(:read_licenses) } + it { is_expected.to be_disallowed(:read_dependency) } + end + end end end - context 'with private project' do - let(:project) { private_project } + context 'when dependency list feature not available' do + let(:current_user) { admin } - where(role: %w[owner maintainer developer reporter]) + it { is_expected.not_to be_allowed(:read_dependency) } + end + end - with_them do - let(:current_user) { public_send(role) } + describe 'read_licenses' do + context 'when license management feature available' do + context 'with public project' do + let(:current_user) { non_member } - it { is_expected.to be_allowed(:read_licenses) } + context 'with public access to repository' do + it { is_expected.to be_allowed(:read_licenses) } + end end - context 'with admin' do - let(:current_user) { admin } + context 'with private project' do + let(:project) { private_project } + + where(role: %w[owner maintainer developer reporter]) + + with_them do + let(:current_user) { public_send(role) } - context 'when admin mode enabled', :enable_admin_mode do it { is_expected.to be_allowed(:read_licenses) } end - context 'when admin mode disabled' do - it { is_expected.to be_disallowed(:read_licenses) } + context 'with admin' do + let(:current_user) { admin } + + context 'when admin mode enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:read_licenses) } + end + + context 'when admin mode disabled' do + it { is_expected.to be_disallowed(:read_licenses) } + end end - end - %w[anonymous non_member guest planner].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + %w[anonymous non_member guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:read_licenses) } + it { is_expected.to be_disallowed(:read_licenses) } + end end end end - end - - context 'when license management feature in not available' do - before do - stub_licensed_features(license_scanning: false) - end - - let(:current_user) { admin } - it { is_expected.to be_disallowed(:read_licenses) } - end - end + context 'when license management feature in not available' do + before do + stub_licensed_features(license_scanning: false) + end - describe 'publish_status_page' do - let(:feature) { :status_page } - let(:policy) { :publish_status_page } + let(:current_user) { admin } - context 'when feature is available' do - where(:role, :admin_mode, :allowed) do - :anonymous | nil | false - :guest | nil | false - :planner | nil | false - :reporter | nil | false - :developer | nil | true - :maintainer | nil | true - :owner | nil | true - :admin | false | false - :admin | true | true + it { is_expected.to be_disallowed(:read_licenses) } end + end - with_them do - let(:current_user) { public_send(role) if role } + describe 'publish_status_page' do + let(:feature) { :status_page } + let(:policy) { :publish_status_page } - before do - stub_licensed_features(feature => true) - enable_admin_mode!(current_user) if admin_mode + context 'when feature is available' do + where(:role, :admin_mode, :allowed) do + :anonymous | nil | false + :guest | nil | false + :planner | nil | false + :reporter | nil | false + :developer | nil | true + :maintainer | nil | true + :owner | nil | true + :admin | false | false + :admin | true | true end - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + with_them do + let(:current_user) { public_send(role) if role } - context 'when feature is not available' do before do - stub_licensed_features(feature => false) + stub_licensed_features(feature => true) + enable_admin_mode!(current_user) if admin_mode end - it { is_expected.to be_disallowed(policy) } + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + + context 'when feature is not available' do + before do + stub_licensed_features(feature => false) + end + + it { is_expected.to be_disallowed(policy) } + end end end end - end - - describe 'add_project_to_instance_security_dashboard' do - let(:policy) { :add_project_to_instance_security_dashboard } - context 'when user is auditor' do - let(:current_user) { create(:user, :auditor) } - - it { is_expected.to be_allowed(policy) } - end + describe 'add_project_to_instance_security_dashboard' do + let(:policy) { :add_project_to_instance_security_dashboard } - context 'when user is not auditor' do - context 'with developer access' do - let(:current_user) { developer } + context 'when user is auditor' do + let(:current_user) { create(:user, :auditor) } it { is_expected.to be_allowed(policy) } end - context 'without developer access' do - let(:current_user) { create(:user) } - - it { is_expected.to be_disallowed(policy) } - end - end - end + context 'when user is not auditor' do + context 'with developer access' do + let(:current_user) { developer } - context 'visual review bot' do - let(:current_user) { Users::Internal.visual_review_bot } + it { is_expected.to be_allowed(policy) } + end - it { expect_disallowed(:create_note) } - it { expect_disallowed(:read_note) } - it { expect_disallowed(:resolve_note) } - end + context 'without developer access' do + let(:current_user) { create(:user) } - context 'when push_rules is not enabled by the current license' do - before do - stub_licensed_features(push_rules: false) + it { is_expected.to be_disallowed(policy) } + end + end end - let(:current_user) { maintainer } - - it { is_expected.to be_disallowed(:change_push_rules) } - end + context 'visual review bot' do + let(:current_user) { Users::Internal.visual_review_bot } - context 'when push_rules is enabled by the current license' do - before do - stub_licensed_features(push_rules: true) + it { expect_disallowed(:create_note) } + it { expect_disallowed(:read_note) } + it { expect_disallowed(:resolve_note) } end - let(:current_user) { maintainer } - - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } - - it { is_expected.to be_allowed(:change_push_rules) } - end + context 'when push_rules is not enabled by the current license' do + before do + stub_licensed_features(push_rules: false) + end - context 'when the user is a maintainer' do let(:current_user) { maintainer } - it { is_expected.to be_allowed(:change_push_rules) } - end - - context 'when the user is a developer' do - let(:current_user) { developer } - it { is_expected.to be_disallowed(:change_push_rules) } end - end - context 'commit_committer_check is not enabled by the current license' do - before do - stub_licensed_features(commit_committer_check: false) - end + context 'when push_rules is enabled by the current license' do + before do + stub_licensed_features(push_rules: true) + end - let(:current_user) { maintainer } + let(:current_user) { maintainer } - it { is_expected.not_to be_allowed(:change_commit_committer_check) } - it { is_expected.not_to be_allowed(:read_commit_committer_check) } - end + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - context 'commit_committer_check is enabled by the current license' do - before do - stub_licensed_features(commit_committer_check: true) - end + it { is_expected.to be_allowed(:change_push_rules) } + end - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } + context 'when the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:change_commit_committer_check) } - it { is_expected.to be_allowed(:read_commit_committer_check) } - end + it { is_expected.to be_allowed(:change_push_rules) } + end - context 'the user is a maintainer' do - let(:current_user) { maintainer } + context 'when the user is a developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(:change_commit_committer_check) } - it { is_expected.to be_allowed(:read_commit_committer_check) } + it { is_expected.to be_disallowed(:change_push_rules) } + end end - context 'the user is a developer' do - let(:current_user) { developer } + context 'commit_committer_check is not enabled by the current license' do + before do + stub_licensed_features(commit_committer_check: false) + end + + let(:current_user) { maintainer } it { is_expected.not_to be_allowed(:change_commit_committer_check) } it { is_expected.not_to be_allowed(:read_commit_committer_check) } end - end - - context 'when commit_committer_name_check is not enabled by the current license' do - before do - stub_licensed_features(commit_committer_name_check: false) - end - let(:current_user) { maintainer } + context 'commit_committer_check is enabled by the current license' do + before do + stub_licensed_features(commit_committer_check: true) + end - it { is_expected.to be_disallowed(:read_commit_committer_name_check) } - it { is_expected.to be_disallowed(:change_commit_committer_name_check) } - end + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - context 'when commit_committer_name_check is enabled by the current license' do - before do - stub_licensed_features(commit_committer_name_check: true) - end + it { is_expected.to be_allowed(:change_commit_committer_check) } + it { is_expected.to be_allowed(:read_commit_committer_check) } + end - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } + context 'the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:read_commit_committer_name_check) } - it { is_expected.to be_allowed(:change_commit_committer_name_check) } - end + it { is_expected.to be_allowed(:change_commit_committer_check) } + it { is_expected.to be_allowed(:read_commit_committer_check) } + end - context 'when the user is a maintainer' do - let(:current_user) { maintainer } + context 'the user is a developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(:read_commit_committer_name_check) } - it { is_expected.to be_allowed(:change_commit_committer_name_check) } + it { is_expected.not_to be_allowed(:change_commit_committer_check) } + it { is_expected.not_to be_allowed(:read_commit_committer_check) } + end end - context 'the user is a developer' do - let(:current_user) { developer } + context 'when commit_committer_name_check is not enabled by the current license' do + before do + stub_licensed_features(commit_committer_name_check: false) + end + + let(:current_user) { maintainer } it { is_expected.to be_disallowed(:read_commit_committer_name_check) } it { is_expected.to be_disallowed(:change_commit_committer_name_check) } end - end - - context 'reject_unsigned_commits is not enabled by the current license' do - before do - stub_licensed_features(reject_unsigned_commits: false) - end - let(:current_user) { maintainer } + context 'when commit_committer_name_check is enabled by the current license' do + before do + stub_licensed_features(commit_committer_name_check: true) + end - it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) } - it { is_expected.not_to be_allowed(:read_reject_unsigned_commits) } - end + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - context 'reject_unsigned_commits is enabled by the current license' do - before do - stub_licensed_features(reject_unsigned_commits: true) - end + it { is_expected.to be_allowed(:read_commit_committer_name_check) } + it { is_expected.to be_allowed(:change_commit_committer_name_check) } + end - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } + context 'when the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:change_reject_unsigned_commits) } - it { is_expected.to be_allowed(:read_reject_unsigned_commits) } - end + it { is_expected.to be_allowed(:read_commit_committer_name_check) } + it { is_expected.to be_allowed(:change_commit_committer_name_check) } + end - context 'when the user is a maintainer' do - let(:current_user) { maintainer } + context 'the user is a developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(:change_reject_unsigned_commits) } - it { is_expected.to be_allowed(:read_reject_unsigned_commits) } + it { is_expected.to be_disallowed(:read_commit_committer_name_check) } + it { is_expected.to be_disallowed(:change_commit_committer_name_check) } + end end - context 'when the user is a developer' do - let(:current_user) { developer } + context 'reject_unsigned_commits is not enabled by the current license' do + before do + stub_licensed_features(reject_unsigned_commits: false) + end + + let(:current_user) { maintainer } it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) } it { is_expected.not_to be_allowed(:read_reject_unsigned_commits) } end - end - - context 'when reject_non_dco_commits is not enabled by the current license' do - before do - stub_licensed_features(reject_non_dco_commits: false) - end - let(:current_user) { maintainer } + context 'reject_unsigned_commits is enabled by the current license' do + before do + stub_licensed_features(reject_unsigned_commits: true) + end - it { is_expected.to be_disallowed(:read_reject_non_dco_commits) } - it { is_expected.to be_disallowed(:change_reject_non_dco_commits) } - end + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - context 'when reject_non_dco_commits is enabled by the current license' do - before do - stub_licensed_features(reject_non_dco_commits: true) - end + it { is_expected.to be_allowed(:change_reject_unsigned_commits) } + it { is_expected.to be_allowed(:read_reject_unsigned_commits) } + end - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } + context 'when the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:read_reject_non_dco_commits) } - it { is_expected.to be_allowed(:change_reject_non_dco_commits) } - end + it { is_expected.to be_allowed(:change_reject_unsigned_commits) } + it { is_expected.to be_allowed(:read_reject_unsigned_commits) } + end - context 'when the user is a maintainer' do - let(:current_user) { maintainer } + context 'when the user is a developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(:read_reject_non_dco_commits) } - it { is_expected.to be_allowed(:change_reject_non_dco_commits) } + it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) } + it { is_expected.not_to be_allowed(:read_reject_unsigned_commits) } + end end - context 'when the user is a developer' do - let(:current_user) { developer } + context 'when reject_non_dco_commits is not enabled by the current license' do + before do + stub_licensed_features(reject_non_dco_commits: false) + end + + let(:current_user) { maintainer } it { is_expected.to be_disallowed(:read_reject_non_dco_commits) } it { is_expected.to be_disallowed(:change_reject_non_dco_commits) } end - end - context 'when dora4 analytics is available' do - before do - stub_licensed_features(dora4_analytics: true) - end + context 'when reject_non_dco_commits is enabled by the current license' do + before do + stub_licensed_features(reject_non_dco_commits: true) + end - context 'when the user is a developer' do - let(:current_user) { developer } + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - it { is_expected.to be_allowed(:read_dora4_analytics) } - end + it { is_expected.to be_allowed(:read_reject_non_dco_commits) } + it { is_expected.to be_allowed(:change_reject_non_dco_commits) } + end - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } + context 'when the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:read_dora4_analytics) } - end - end + it { is_expected.to be_allowed(:read_reject_non_dco_commits) } + it { is_expected.to be_allowed(:change_reject_non_dco_commits) } + end - context 'when dora4 analytics is not available' do - let(:current_user) { developer } + context 'when the user is a developer' do + let(:current_user) { developer } - before do - stub_licensed_features(dora4_analytics: false) + it { is_expected.to be_disallowed(:read_reject_non_dco_commits) } + it { is_expected.to be_disallowed(:change_reject_non_dco_commits) } + end end - it { is_expected.not_to be_allowed(:read_dora4_analytics) } - end - - describe ':read_enterprise_ai_analytics' do - let(:project) { private_project_in_group } - let(:guest) { inherited_guest } - let(:planner) { inherited_planner } - let(:reporter) { inherited_reporter } + context 'when dora4 analytics is available' do + before do + stub_licensed_features(dora4_analytics: true) + end - context 'when on SAAS', :saas do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } + context 'when the user is a developer' do + let(:current_user) { developer } - it_behaves_like 'ai permission to', :read_enterprise_ai_analytics - end + it { is_expected.to be_allowed(:read_dora4_analytics) } + end - context 'when on self-managed' do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - it_behaves_like 'ai permission to', :read_enterprise_ai_analytics + it { is_expected.to be_allowed(:read_dora4_analytics) } + end end - end - describe ':read_pro_ai_analytics' do - let(:project) { private_project_in_group } - let(:guest) { inherited_guest } - let(:reporter) { inherited_reporter } - - context 'when on SAAS', :saas do - context 'with pro subscription' do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :gitlab_duo_pro, namespace: group) } + context 'when dora4 analytics is not available' do + let(:current_user) { developer } - it_behaves_like 'ai permission to', :read_pro_ai_analytics + before do + stub_licensed_features(dora4_analytics: false) end - context 'with enterprise subscription' do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } - - it_behaves_like 'ai permission to', :read_pro_ai_analytics - end + it { is_expected.not_to be_allowed(:read_dora4_analytics) } end - context 'when on self-managed' do - context 'with pro subscription' do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :gitlab_duo_pro, :self_managed) } + describe ':read_enterprise_ai_analytics' do + let(:project) { private_project_in_group } + let(:guest) { inherited_guest } + let(:planner) { inherited_planner } + let(:reporter) { inherited_reporter } - it_behaves_like 'ai permission to', :read_pro_ai_analytics + context 'when on SAAS', :saas do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } + + it_behaves_like 'ai permission to', :read_enterprise_ai_analytics end - context 'with enterprise subscription' do + context 'when on self-managed' do let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } - it_behaves_like 'ai permission to', :read_pro_ai_analytics + it_behaves_like 'ai permission to', :read_enterprise_ai_analytics end end - end - describe ':read_code_review_analytics' do - let(:project) { private_project } - - where(:role, :admin_mode, :allowed) do - :guest | nil | false - :planner | nil | true - :reporter | nil | true - :developer | nil | true - :maintainer | nil | true - :owner | nil | true - :admin | false | false - :admin | true | true - end + describe ':read_pro_ai_analytics' do + let(:project) { private_project_in_group } + let(:guest) { inherited_guest } + let(:reporter) { inherited_reporter } - with_them do - let(:current_user) { public_send(role) } + context 'when on SAAS', :saas do + context 'with pro subscription' do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :gitlab_duo_pro, namespace: group) } - before do - stub_licensed_features(code_review_analytics: true) - enable_admin_mode!(current_user) if admin_mode + it_behaves_like 'ai permission to', :read_pro_ai_analytics + end + + context 'with enterprise subscription' do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } + + it_behaves_like 'ai permission to', :read_pro_ai_analytics + end end - it { is_expected.to(allowed ? be_allowed(:read_code_review_analytics) : be_disallowed(:read_code_review_analytics)) } - end + context 'when on self-managed' do + context 'with pro subscription' do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :gitlab_duo_pro, :self_managed) } - context 'with code review analytics is not available in license' do - let(:current_user) { owner } + it_behaves_like 'ai permission to', :read_pro_ai_analytics + end - before do - stub_licensed_features(code_review_analytics: false) - end + context 'with enterprise subscription' do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } - it { is_expected.to be_disallowed(:read_code_review_analytics) } + it_behaves_like 'ai permission to', :read_pro_ai_analytics + end + end end - end - shared_examples 'merge request approval settings' do |admin_override_allowed = false| - let(:project) { private_project } - - context 'with merge request approvers rules available in license' do - where(:role, :setting, :admin_mode, :allowed) do - :guest | true | nil | false - :planner | true | nil | false - :reporter | true | nil | false - :developer | true | nil | false - :maintainer | false | nil | true - :maintainer | true | nil | false - :owner | false | nil | true - :owner | true | nil | false - :admin | false | false | false - :admin | false | true | true - :admin | true | false | false - :admin | true | true | admin_override_allowed + describe ':read_code_review_analytics' do + let(:project) { private_project } + + where(:role, :admin_mode, :allowed) do + :guest | nil | false + :planner | nil | true + :reporter | nil | true + :developer | nil | true + :maintainer | nil | true + :owner | nil | true + :admin | false | false + :admin | true | true end with_them do let(:current_user) { public_send(role) } before do - stub_licensed_features(admin_merge_request_approvers_rules: true) - stub_application_setting(app_setting => setting) + stub_licensed_features(code_review_analytics: true) enable_admin_mode!(current_user) if admin_mode end - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - end - end - - context 'with merge request approvers rules not available in license' do - where(:role, :setting, :admin_mode, :allowed) do - :guest | true | nil | false - :planner | true | nil | false - :reporter | true | nil | false - :developer | true | nil | false - :maintainer | false | nil | true - :maintainer | true | nil | true - :owner | false | nil | true - :owner | true | nil | true - :admin | false | false | false - :admin | false | true | true - :admin | true | false | false - :admin | true | true | true + it { is_expected.to(allowed ? be_allowed(:read_code_review_analytics) : be_disallowed(:read_code_review_analytics)) } end - with_them do - let(:current_user) { public_send(role) } + context 'with code review analytics is not available in license' do + let(:current_user) { owner } before do - stub_licensed_features(admin_merge_request_approvers_rules: false) - stub_application_setting(app_setting => setting) - enable_admin_mode!(current_user) if admin_mode + stub_licensed_features(code_review_analytics: false) end - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + it { is_expected.to be_disallowed(:read_code_review_analytics) } end end - end - describe ':admin_merge_request_approval_settings' do - let(:project) { private_project } - - where(:role, :licensed, :allowed) do - :guest | true | false - :planner | true | false - :reporter | true | false - :developer | true | false - :maintainer | false | false - :maintainer | true | true - :owner | false | false - :owner | true | true - :admin | true | true - :admin | false | false - end + shared_examples 'merge request approval settings' do |admin_override_allowed = false| + let(:project) { private_project } - with_them do - let(:current_user) { public_send(role) } - - before do - stub_licensed_features(merge_request_approvers: licensed) - enable_admin_mode!(current_user) if role == :admin - end - - it { is_expected.to(allowed ? be_allowed(:admin_merge_request_approval_settings) : be_disallowed(:admin_merge_request_approval_settings)) } - end - end - - describe ':modify_approvers_rules' do - it_behaves_like 'merge request approval settings', true do - let(:app_setting) { :disable_overriding_approvers_per_merge_request } - let(:policy) { :modify_approvers_rules } - end - end - - describe ':modify_merge_request_author_setting' do - it_behaves_like 'merge request approval settings' do - let(:app_setting) { :prevent_merge_requests_author_approval } - let(:policy) { :modify_merge_request_author_setting } - end - end - - describe ':modify_merge_request_committer_setting' do - it_behaves_like 'merge request approval settings' do - let(:app_setting) { :prevent_merge_requests_committers_approval } - let(:policy) { :modify_merge_request_committer_setting } - end - end - - it_behaves_like 'resource with requirement permissions' do - let(:resource) { project } - end - - describe 'Quality Management test case' do - let(:policy) { :create_test_case } - - where(:role, :admin_mode, :allowed) do - :guest | nil | false - :planner | nil | true - :reporter | nil | true - :developer | nil | true - :maintainer | nil | true - :owner | nil | true - :admin | false | false - :admin | true | true - end - - before do - stub_licensed_features(quality_management: true) - enable_admin_mode!(current_user) if admin_mode - end - - with_them do - let(:current_user) { public_send(role) } - - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - - context 'with unavailable license' do - before do - stub_licensed_features(quality_management: false) + context 'with merge request approvers rules available in license' do + where(:role, :setting, :admin_mode, :allowed) do + :guest | true | nil | false + :planner | true | nil | false + :reporter | true | nil | false + :developer | true | nil | false + :maintainer | false | nil | true + :maintainer | true | nil | false + :owner | false | nil | true + :owner | true | nil | false + :admin | false | false | false + :admin | false | true | true + :admin | true | false | false + :admin | true | true | admin_override_allowed end - it { is_expected.to(be_disallowed(policy)) } - end - end - end - - shared_examples_for 'prevents CI cancellation ability' do - context 'when feature is enabled' do - where(:restricted_role, :actual_role, :allowed) do - :developer | :planner | false - :developer | :guest | false - :developer | :reporter | false - :developer | :developer | true - :developer | :maintainer | true - :developer | :owner | true - :maintainer | :planner | false - :maintainer | :guest | false - :maintainer | :reporter | false - :maintainer | :developer | false - :maintainer | :maintainer | true - :maintainer | :owner | true - :no_one | :planner | false - :no_one | :guest | false - :no_one | :reporter | false - :no_one | :developer | false - :no_one | :maintainer | false - :no_one | :owner | false - end + with_them do + let(:current_user) { public_send(role) } - with_them do - let(:current_user) { public_send(actual_role) } + before do + stub_licensed_features(admin_merge_request_approvers_rules: true) + stub_application_setting(app_setting => setting) + enable_admin_mode!(current_user) if admin_mode + end - before do - stub_licensed_features(ci_pipeline_cancellation_restrictions: true) - project.update!(restrict_pipeline_cancellation_role: restricted_role) + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end - - it { is_expected.to(allowed ? be_allowed(ability) : be_disallowed(ability)) } end - end - end - describe 'prevents cancel_pipeline when CI cancllation restricted' do - let(:ability) { :cancel_pipeline } - - it_behaves_like 'prevents CI cancellation ability' - end - - describe 'prevents cancel_build when CI cancllation restricted' do - let(:ability) { :cancel_build } - - it_behaves_like 'prevents CI cancellation ability' - end - - describe 'project level compliance features' do - shared_examples 'project level compliance feature' do |feature, permission| - context 'when enabled' do - before do - stub_licensed_features({ feature => true }) + context 'with merge request approvers rules not available in license' do + where(:role, :setting, :admin_mode, :allowed) do + :guest | true | nil | false + :planner | true | nil | false + :reporter | true | nil | false + :developer | true | nil | false + :maintainer | false | nil | true + :maintainer | true | nil | true + :owner | false | nil | true + :owner | true | nil | true + :admin | false | false | false + :admin | false | true | true + :admin | true | false | false + :admin | true | true | true end - context 'when project is in group' do - let(:project) { public_project_in_group } - - context 'when user is eligible for access' do - where(role: %w[owner auditor]) - - with_them do - let(:current_user) { public_send(role) } - - it { is_expected.to be_allowed(permission) } - end - end - - context 'allows admin', :enable_admin_mode do - let(:current_user) { admin } + with_them do + let(:current_user) { public_send(role) } - it { is_expected.to be_allowed(permission) } + before do + stub_licensed_features(admin_merge_request_approvers_rules: false) + stub_application_setting(app_setting => setting) + enable_admin_mode!(current_user) if admin_mode end - end - context 'when project is in personal namespace' do - let(:current_user) { owner } - let(:project) { public_project } - - it { is_expected.to be_disallowed(permission) } + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end + end - context 'when disabled' do - before do - stub_licensed_features({ feature => false }) - end + describe ':admin_merge_request_approval_settings' do + let(:project) { private_project } - context 'when user is eligible for access' do - where(role: %w[owner auditor]) + where(:role, :licensed, :allowed) do + :guest | true | false + :planner | true | false + :reporter | true | false + :developer | true | false + :maintainer | false | false + :maintainer | true | true + :owner | false | false + :owner | true | true + :admin | true | true + :admin | false | false + end - with_them do - let(:current_user) { public_send(role) } + with_them do + let(:current_user) { public_send(role) } - it { is_expected.to be_disallowed(permission) } - end + before do + stub_licensed_features(merge_request_approvers: licensed) + enable_admin_mode!(current_user) if role == :admin end - context 'disallows admin', :enable_admin_mode do - let(:current_user) { admin } - - it { is_expected.to be_disallowed(permission) } - end + it { is_expected.to(allowed ? be_allowed(:admin_merge_request_approval_settings) : be_disallowed(:admin_merge_request_approval_settings)) } end end - describe 'project level compliance dashboard' do - it_behaves_like 'project level compliance feature', :project_level_compliance_dashboard, :read_compliance_dashboard - end - - describe 'project level compliance adherence report' do - it_behaves_like 'project level compliance feature', :project_level_compliance_adherence_report, :read_compliance_adherence_report - end - - describe 'project level compliance violations report' do - it_behaves_like 'project level compliance feature', :project_level_compliance_violations_report, :read_compliance_violations_report + describe ':modify_approvers_rules' do + it_behaves_like 'merge request approval settings', true do + let(:app_setting) { :disable_overriding_approvers_per_merge_request } + let(:policy) { :modify_approvers_rules } + end end - end - describe ':compliance_framework_available' do - let(:policy) { :admin_compliance_framework } - - where(:role, :feature_enabled, :admin_mode, :allowed) do - :guest | false | nil | false - :guest | true | nil | false - :planner | false | nil | false - :planner | true | nil | false - :reporter | false | nil | false - :reporter | true | nil | false - :developer | false | nil | false - :maintainer | false | nil | false - :maintainer | true | nil | false - :owner | false | nil | false - :owner | true | nil | true - :admin | false | false | false - :admin | false | true | false - :admin | true | false | false - :admin | true | true | true + describe ':modify_merge_request_author_setting' do + it_behaves_like 'merge request approval settings' do + let(:app_setting) { :prevent_merge_requests_author_approval } + let(:policy) { :modify_merge_request_author_setting } + end end - with_them do - let(:current_user) { public_send(role) } - - before do - stub_licensed_features(compliance_framework: feature_enabled) - enable_admin_mode!(current_user) if admin_mode + describe ':modify_merge_request_committer_setting' do + it_behaves_like 'merge request approval settings' do + let(:app_setting) { :prevent_merge_requests_committers_approval } + let(:policy) { :modify_merge_request_committer_setting } end - - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end - end - describe 'Incident Management on-call schedules' do - let(:current_user) { public_send(role) } - let(:admin_mode) { false } - - before do - enable_admin_mode!(current_user) if admin_mode - stub_licensed_features(oncall_schedules: true) + it_behaves_like 'resource with requirement permissions' do + let(:resource) { project } end - context ':read_incident_management_oncall_schedule' do - let(:policy) { :read_incident_management_oncall_schedule } + describe 'Quality Management test case' do + let(:policy) { :create_test_case } where(:role, :admin_mode, :allowed) do :guest | nil | false - :planner | nil | false + :planner | nil | true :reporter | nil | true :developer | nil | true :maintainer | nil | true :owner | nil | true :admin | false | false :admin | true | true - :auditor | false | true + end + + before do + stub_licensed_features(quality_management: true) + enable_admin_mode!(current_user) if admin_mode end with_them do + let(:current_user) { public_send(role) } + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } context 'with unavailable license' do before do - stub_licensed_features(oncall_schedules: false) + stub_licensed_features(quality_management: false) end it { is_expected.to(be_disallowed(policy)) } end end - - it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter end - context ':admin_incident_management_oncall_schedule' do - let(:policy) { :admin_incident_management_oncall_schedule } - - where(:role, :admin_mode, :allowed) do - :guest | nil | false - :planner | nil | false - :reporter | nil | false - :developer | nil | false - :maintainer | nil | true - :owner | nil | true - :admin | false | false - :admin | true | true - :auditor | false | false - end + shared_examples_for 'prevents CI cancellation ability' do + context 'when feature is enabled' do + where(:restricted_role, :actual_role, :allowed) do + :developer | :planner | false + :developer | :guest | false + :developer | :reporter | false + :developer | :developer | true + :developer | :maintainer | true + :developer | :owner | true + :maintainer | :planner | false + :maintainer | :guest | false + :maintainer | :reporter | false + :maintainer | :developer | false + :maintainer | :maintainer | true + :maintainer | :owner | true + :no_one | :planner | false + :no_one | :guest | false + :no_one | :reporter | false + :no_one | :developer | false + :no_one | :maintainer | false + :no_one | :owner | false + end - with_them do - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + with_them do + let(:current_user) { public_send(actual_role) } - context 'with unavailable license' do before do - stub_licensed_features(oncall_schedules: false) + stub_licensed_features(ci_pipeline_cancellation_restrictions: true) + project.update!(restrict_pipeline_cancellation_role: restricted_role) end - it { is_expected.to(be_disallowed(policy)) } + it { is_expected.to(allowed ? be_allowed(ability) : be_disallowed(ability)) } end end - - it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer end - end - describe 'Escalation Policies' do - let(:current_user) { public_send(role) } - let(:admin_mode) { false } + describe 'prevents cancel_pipeline when CI cancllation restricted' do + let(:ability) { :cancel_pipeline } - before do - enable_admin_mode!(current_user) if admin_mode - allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(true) + it_behaves_like 'prevents CI cancellation ability' end - context ':read_incident_management_escalation_policy' do - let(:policy) { :read_incident_management_escalation_policy } - - where(:role, :admin_mode, :allowed) do - :guest | nil | false - :planner | nil | false - :reporter | nil | true - :developer | nil | true - :maintainer | nil | true - :owner | nil | true - :admin | false | false - :admin | true | true - :auditor | false | true - end + describe 'prevents cancel_build when CI cancllation restricted' do + let(:ability) { :cancel_build } - with_them do - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + it_behaves_like 'prevents CI cancellation ability' + end - context 'with unavailable escalation policies' do + describe 'project level compliance features' do + shared_examples 'project level compliance feature' do |feature, permission| + context 'when enabled' do before do - allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(false) + stub_licensed_features({ feature => true }) end - it { is_expected.to(be_disallowed(policy)) } - end - end + context 'when project is in group' do + let(:project) { public_project_in_group } - it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter - end + context 'when user is eligible for access' do + where(role: %w[owner auditor]) - context ':admin_incident_management_escalation_policy' do - let(:policy) { :admin_incident_management_escalation_policy } + with_them do + let(:current_user) { public_send(role) } - where(:role, :admin_mode, :allowed) do - :guest | nil | false - :planner | nil | false - :reporter | nil | false - :developer | nil | false - :maintainer | nil | true - :owner | nil | true - :admin | false | false - :admin | true | true - :auditor | false | false - end + it { is_expected.to be_allowed(permission) } + end + end - with_them do - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + context 'allows admin', :enable_admin_mode do + let(:current_user) { admin } - context 'with unavailable escalation policies' do - before do - allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(false) + it { is_expected.to be_allowed(permission) } + end end - it { is_expected.to(be_disallowed(policy)) } + context 'when project is in personal namespace' do + let(:current_user) { owner } + let(:project) { public_project } + + it { is_expected.to be_disallowed(permission) } + end end - end - it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer - end - end + context 'when disabled' do + before do + stub_licensed_features({ feature => false }) + end - context 'when project is read only on the namespace' do - let(:project) { public_project_in_group } - let(:current_user) { maintainer } - let(:abilities) do - described_class.readonly_features.flat_map { |feature| described_class.create_update_admin(feature) } + - described_class.readonly_abilities - end + context 'when user is eligible for access' do + where(role: %w[owner auditor]) - before do - allow(project.root_namespace).to receive(:read_only?).and_return(read_only) - allow(project).to receive(:design_management_enabled?).and_return(true) - stub_licensed_features(security_dashboard: true, license_scanning: true, quality_management: true) - end + with_them do + let(:current_user) { public_send(role) } - context 'when the group is read only' do - let(:read_only) { true } + it { is_expected.to be_disallowed(permission) } + end + end - it { is_expected.to(be_disallowed(*abilities)) } - end + context 'disallows admin', :enable_admin_mode do + let(:current_user) { admin } - context 'when the group is not read only' do - let(:read_only) { false } + it { is_expected.to be_disallowed(permission) } + end + end + end - # These are abilities that are not explicitly allowed by policies because most of them are not - # real abilities. They are prevented due to the use of create_update_admin helper method. - let(:abilities_not_currently_enabled) do - %i[create_merge_request create_issue_board_list create_issue_board update_issue_board - update_issue_board_list create_label update_label create_milestone - update_milestone update_wiki update_design admin_design update_note - update_pipeline_schedule admin_pipeline_schedule create_trigger update_trigger - admin_trigger create_pages admin_release request_access create_board update_board - create_issue_link update_issue_link create_approvers admin_approvers - admin_vulnerability_feedback create_feature_flags_client - update_feature_flags_client update_iteration update_vulnerability create_vulnerability] + describe 'project level compliance dashboard' do + it_behaves_like 'project level compliance feature', :project_level_compliance_dashboard, :read_compliance_dashboard end - it { is_expected.to(be_allowed(*(abilities - abilities_not_currently_enabled))) } + describe 'project level compliance adherence report' do + it_behaves_like 'project level compliance feature', :project_level_compliance_adherence_report, :read_compliance_adherence_report + end + + describe 'project level compliance violations report' do + it_behaves_like 'project level compliance feature', :project_level_compliance_violations_report, :read_compliance_violations_report + end end - end - context 'project access tokens' do - context 'GitLab.com Core resource access tokens', :saas do - before do - stub_ee_application_setting(should_check_namespace_plan: true) + describe ':compliance_framework_available' do + let(:policy) { :admin_compliance_framework } + + where(:role, :feature_enabled, :admin_mode, :allowed) do + :guest | false | nil | false + :guest | true | nil | false + :planner | false | nil | false + :planner | true | nil | false + :reporter | false | nil | false + :reporter | true | nil | false + :developer | false | nil | false + :maintainer | false | nil | false + :maintainer | true | nil | false + :owner | false | nil | false + :owner | true | nil | true + :admin | false | false | false + :admin | false | true | false + :admin | true | false | false + :admin | true | true | true end - context 'with admin access' do - let(:current_user) { owner } + with_them do + let(:current_user) { public_send(role) } before do - project.add_owner(owner) + stub_licensed_features(compliance_framework: feature_enabled) + enable_admin_mode!(current_user) if admin_mode end - context 'when project belongs to a group' do - let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, group: group) } + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + end + end - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - it { is_expected.to be_allowed(:read_resource_access_tokens) } - it { is_expected.to be_allowed(:destroy_resource_access_tokens) } - end + describe 'Incident Management on-call schedules' do + let(:current_user) { public_send(role) } + let(:admin_mode) { false } - context 'when project belongs to personal namespace' do - it { is_expected.to be_allowed(:create_resource_access_tokens) } - it { is_expected.to be_allowed(:read_resource_access_tokens) } - it { is_expected.to be_allowed(:destroy_resource_access_tokens) } - end + before do + enable_admin_mode!(current_user) if admin_mode + stub_licensed_features(oncall_schedules: true) end - context 'with non admin access' do - let(:current_user) { developer } + context ':read_incident_management_oncall_schedule' do + let(:policy) { :read_incident_management_oncall_schedule } - before do - project.add_developer(developer) + where(:role, :admin_mode, :allowed) do + :guest | nil | false + :planner | nil | false + :reporter | nil | true + :developer | nil | true + :maintainer | nil | true + :owner | nil | true + :admin | false | false + :admin | true | true + :auditor | false | true end - context 'when project belongs to a group' do - let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, group: group) } + with_them do + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - it { is_expected.not_to be_allowed(:read_resource_access_tokens) } - it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } - end + context 'with unavailable license' do + before do + stub_licensed_features(oncall_schedules: false) + end - context 'when project belongs to personal namespace' do - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - it { is_expected.not_to be_allowed(:read_resource_access_tokens) } - it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + it { is_expected.to(be_disallowed(policy)) } + end end - end - end - context 'on GitLab.com paid', :saas do - let_it_be(:group) { create(:group_with_plan, plan: :bronze_plan) } - let_it_be(:project) { create(:project, group: group) } + it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter + end - context 'with maintainer access' do - let(:current_user) { maintainer } + context ':admin_incident_management_oncall_schedule' do + let(:policy) { :admin_incident_management_oncall_schedule } - before do - project.add_maintainer(maintainer) + where(:role, :admin_mode, :allowed) do + :guest | nil | false + :planner | nil | false + :reporter | nil | false + :developer | nil | false + :maintainer | nil | true + :owner | nil | true + :admin | false | false + :admin | true | true + :auditor | false | false end - it_behaves_like 'GitLab.com Paid plan resource access tokens' - - context 'create resource access tokens' do - it { is_expected.to be_allowed(:create_resource_access_tokens) } - - context 'with a personal namespace project' do - let(:namespace) { create(:namespace_with_plan, plan: :bronze_plan) } - let(:project) { create(:project, namespace: namespace) } - - it { is_expected.to be_allowed(:create_resource_access_tokens) } - end + with_them do + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - context 'when resource access token creation is not allowed' do + context 'with unavailable license' do before do - group.namespace_settings.update_column(:resource_access_token_creation_allowed, false) + stub_licensed_features(oncall_schedules: false) end - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - end - - context 'when parent group has resource access token creation disabled' do - let(:resource_access_token_creation_allowed) { false } - let(:ns_for_parent) { create(:namespace_settings, resource_access_token_creation_allowed: resource_access_token_creation_allowed) } - let(:parent) { create(:group_with_plan, plan: :bronze_plan, namespace_settings: ns_for_parent) } - let(:group) { create(:group, parent: parent) } - let(:project) { create(:project, group: group) } - - context 'cannot create resource access tokens' do - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - end + it { is_expected.to(be_disallowed(policy)) } end end - context 'read resource access tokens' do - it { is_expected.to be_allowed(:read_resource_access_tokens) } - end + it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer + end + end - context 'destroy resource access tokens' do - it { is_expected.to be_allowed(:destroy_resource_access_tokens) } - end + describe 'Escalation Policies' do + let(:current_user) { public_send(role) } + let(:admin_mode) { false } + + before do + enable_admin_mode!(current_user) if admin_mode + allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(true) end - context 'with developer access' do - let(:current_user) { developer } + context ':read_incident_management_escalation_policy' do + let(:policy) { :read_incident_management_escalation_policy } - before do - project.add_developer(developer) + where(:role, :admin_mode, :allowed) do + :guest | nil | false + :planner | nil | false + :reporter | nil | true + :developer | nil | true + :maintainer | nil | true + :owner | nil | true + :admin | false | false + :admin | true | true + :auditor | false | true end - context 'create resource access tokens' do - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - end + with_them do + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - context 'read resource access tokens' do - it { is_expected.not_to be_allowed(:read_resource_access_tokens) } - end + context 'with unavailable escalation policies' do + before do + allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(false) + end - context 'destroy resource access tokens' do - it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + it { is_expected.to(be_disallowed(policy)) } + end end + + it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter end - context 'with auditor access' do - let(:current_user) { auditor } + context ':admin_incident_management_escalation_policy' do + let(:policy) { :admin_incident_management_escalation_policy } - context 'read resource access tokens' do - it { is_expected.to be_allowed(:read_resource_access_tokens) } + where(:role, :admin_mode, :allowed) do + :guest | nil | false + :planner | nil | false + :reporter | nil | false + :developer | nil | false + :maintainer | nil | true + :owner | nil | true + :admin | false | false + :admin | true | true + :auditor | false | false end - context 'cannot create resource access tokens' do - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - end + with_them do + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + + context 'with unavailable escalation policies' do + before do + allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(false) + end - context 'cannot destroy resource access tokens' do - it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + it { is_expected.to(be_disallowed(policy)) } + end end + + it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer end end - end - describe 'read_analytics' do - context 'with various analytics features' do - let_it_be(:project_with_analytics_disabled) { create(:project, :analytics_disabled) } - let_it_be(:project_with_analytics_private) { create(:project, :analytics_private) } - let_it_be(:project_with_analytics_enabled) { create(:project, :analytics_enabled) } - - let(:all_read_analytics_permissions) do - %i[ - read_project_merge_request_analytics - read_code_review_analytics - read_cycle_analytics - read_issue_analytics - ] + context 'when project is read only on the namespace' do + let(:project) { public_project_in_group } + let(:current_user) { maintainer } + let(:abilities) do + described_class.readonly_features.flat_map { |feature| described_class.create_update_admin(feature) } + + described_class.readonly_abilities end before do - stub_licensed_features(issues_analytics: true, code_review_analytics: true, project_merge_request_analytics: true, cycle_analytics_for_projects: true) - - project_with_analytics_disabled.add_developer(developer) - project_with_analytics_private.add_developer(developer) - project_with_analytics_enabled.add_developer(developer) + allow(project.root_namespace).to receive(:read_only?).and_return(read_only) + allow(project).to receive(:design_management_enabled?).and_return(true) + stub_licensed_features(security_dashboard: true, license_scanning: true, quality_management: true) end - context 'when analytics is disabled for the project' do - let(:project) { project_with_analytics_disabled } + context 'when the group is read only' do + let(:read_only) { true } - %w[guest planner developer admin auditor].each do |role| - context "for #{role} user" do - let(:current_user) { send(role) } + it { is_expected.to(be_disallowed(*abilities)) } + end - it { is_expected.to be_disallowed(*all_read_analytics_permissions) } - end + context 'when the group is not read only' do + let(:read_only) { false } + + # These are abilities that are not explicitly allowed by policies because most of them are not + # real abilities. They are prevented due to the use of create_update_admin helper method. + let(:abilities_not_currently_enabled) do + %i[create_merge_request create_issue_board_list create_issue_board update_issue_board + update_issue_board_list create_label update_label create_milestone + update_milestone update_wiki update_design admin_design update_note + update_pipeline_schedule admin_pipeline_schedule create_trigger update_trigger + admin_trigger create_pages admin_release request_access create_board update_board + create_issue_link update_issue_link create_approvers admin_approvers + admin_vulnerability_feedback create_feature_flags_client + update_feature_flags_client update_iteration update_vulnerability create_vulnerability] end + + it { is_expected.to(be_allowed(*(abilities - abilities_not_currently_enabled))) } end + end - context 'when analytics is private for the project' do - let(:project) { project_with_analytics_private } + context 'project access tokens' do + context 'GitLab.com Core resource access tokens', :saas do + before do + stub_ee_application_setting(should_check_namespace_plan: true) + end - %w[guest planner].each do |role| - context "for #{role} user" do - let(:current_user) { send(role) } + context 'with admin access' do + let(:current_user) { owner } - it { is_expected.to be_disallowed(*all_read_analytics_permissions) } + before do + project.add_owner(owner) end - end - context 'for developer' do - let(:current_user) { developer } + context 'when project belongs to a group' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } + it { is_expected.not_to be_allowed(:create_resource_access_tokens) } + it { is_expected.to be_allowed(:read_resource_access_tokens) } + it { is_expected.to be_allowed(:destroy_resource_access_tokens) } + end + + context 'when project belongs to personal namespace' do + it { is_expected.to be_allowed(:create_resource_access_tokens) } + it { is_expected.to be_allowed(:read_resource_access_tokens) } + it { is_expected.to be_allowed(:destroy_resource_access_tokens) } + end end - context 'for admin', :enable_admin_mode do - let(:current_user) { admin } + context 'with non admin access' do + let(:current_user) { developer } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } - end + before do + project.add_developer(developer) + end - context 'for auditor' do - let(:current_user) { auditor } + context 'when project belongs to a group' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } + it { is_expected.not_to be_allowed(:create_resource_access_tokens) } + it { is_expected.not_to be_allowed(:read_resource_access_tokens) } + it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + end + + context 'when project belongs to personal namespace' do + it { is_expected.not_to be_allowed(:create_resource_access_tokens) } + it { is_expected.not_to be_allowed(:read_resource_access_tokens) } + it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + end end end - context 'when analytics is enabled for the project' do - let(:project) { project_with_analytics_enabled } + context 'on GitLab.com paid', :saas do + let_it_be(:group) { create(:group_with_plan, plan: :bronze_plan) } + let_it_be(:project) { create(:project, group: group) } - %w[guest planner].each do |role| - context "for #{role} user" do - let(:current_user) { send(role) } + context 'with maintainer access' do + let(:current_user) { maintainer } + + before do + project.add_maintainer(maintainer) + end + + it_behaves_like 'GitLab.com Paid plan resource access tokens' + + context 'create resource access tokens' do + it { is_expected.to be_allowed(:create_resource_access_tokens) } + + context 'with a personal namespace project' do + let(:namespace) { create(:namespace_with_plan, plan: :bronze_plan) } + let(:project) { create(:project, namespace: namespace) } + + it { is_expected.to be_allowed(:create_resource_access_tokens) } + end + + context 'when resource access token creation is not allowed' do + before do + group.namespace_settings.update_column(:resource_access_token_creation_allowed, false) + end + + it { is_expected.not_to be_allowed(:create_resource_access_tokens) } + end + + context 'when parent group has resource access token creation disabled' do + let(:resource_access_token_creation_allowed) { false } + let(:ns_for_parent) { create(:namespace_settings, resource_access_token_creation_allowed: resource_access_token_creation_allowed) } + let(:parent) { create(:group_with_plan, plan: :bronze_plan, namespace_settings: ns_for_parent) } + let(:group) { create(:group, parent: parent) } + let(:project) { create(:project, group: group) } + + context 'cannot create resource access tokens' do + it { is_expected.not_to be_allowed(:create_resource_access_tokens) } + end + end + end + + context 'read resource access tokens' do + it { is_expected.to be_allowed(:read_resource_access_tokens) } + end - it { is_expected.to be_disallowed(:read_project_merge_request_analytics) } - it { is_expected.to be_disallowed(:read_code_review_analytics) } - it { is_expected.to be_disallowed(:read_cycle_analytics) } - it { is_expected.to be_allowed(:read_issue_analytics) } + context 'destroy resource access tokens' do + it { is_expected.to be_allowed(:destroy_resource_access_tokens) } end end - context 'for developer' do + context 'with developer access' do let(:current_user) { developer } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } - end + before do + project.add_developer(developer) + end - context 'for admin', :enable_admin_mode do - let(:current_user) { admin } + context 'create resource access tokens' do + it { is_expected.not_to be_allowed(:create_resource_access_tokens) } + end + + context 'read resource access tokens' do + it { is_expected.not_to be_allowed(:read_resource_access_tokens) } + end - it { is_expected.to be_allowed(*all_read_analytics_permissions) } + context 'destroy resource access tokens' do + it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + end end - context 'for auditor' do + context 'with auditor access' do let(:current_user) { auditor } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } + context 'read resource access tokens' do + it { is_expected.to be_allowed(:read_resource_access_tokens) } + end + + context 'cannot create resource access tokens' do + it { is_expected.not_to be_allowed(:create_resource_access_tokens) } + end + + context 'cannot destroy resource access tokens' do + it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + end end end end - end - describe ':build_read_project' do - let(:policy) { :build_read_project } - - where(:role, :project_visibility, :allowed) do - :guest | 'public' | true - :planner | 'public' | true - :reporter | 'public' | true - :developer | 'public' | true - :maintainer | 'public' | true - :owner | 'public' | true - :admin | 'public' | true - :guest | 'internal' | true - :planner | 'internal' | true - :reporter | 'internal' | true - :developer | 'internal' | true - :maintainer | 'internal' | true - :owner | 'internal' | true - :admin | 'internal' | true - :guest | 'private' | false - :planner | 'private' | false - :reporter | 'private' | true - :developer | 'private' | true - :maintainer | 'private' | true - :owner | 'private' | true - :admin | 'private' | false - end + describe 'read_analytics' do + context 'with various analytics features' do + let_it_be(:project_with_analytics_disabled) { create(:project, :analytics_disabled) } + let_it_be(:project_with_analytics_private) { create(:project, :analytics_private) } + let_it_be(:project_with_analytics_enabled) { create(:project, :analytics_enabled) } - with_them do - let(:current_user) { public_send(role) } + let(:all_read_analytics_permissions) do + %i[ + read_project_merge_request_analytics + read_code_review_analytics + read_cycle_analytics + read_issue_analytics + ] + end - before do - project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(project_visibility)) - end + before do + stub_licensed_features(issues_analytics: true, code_review_analytics: true, project_merge_request_analytics: true, cycle_analytics_for_projects: true) - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - end - end + project_with_analytics_disabled.add_developer(developer) + project_with_analytics_private.add_developer(developer) + project_with_analytics_enabled.add_developer(developer) + end - describe 'pending member permissions' do - let_it_be(:current_user) { create(:user) } - let_it_be(:group) { create(:group, :public) } + context 'when analytics is disabled for the project' do + let(:project) { project_with_analytics_disabled } - context 'with a pending membership in a private project' do - let_it_be(:project) { create(:project, :private, public_builds: false) } + %w[guest planner developer admin auditor].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - where(:role) do - Gitlab::Access.sym_options.keys.map(&:to_sym) - end + it { is_expected.to be_disallowed(*all_read_analytics_permissions) } + end + end + end - with_them do - it 'a pending member has permissions to the project as if the user is not a member' do - create(:project_member, :awaiting, role, source: project, user: current_user) + context 'when analytics is private for the project' do + let(:project) { project_with_analytics_private } - expect_private_project_permissions_as_if_non_member - end - end - end + %w[guest planner].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - context 'with a group invited to a project' do - let_it_be(:project) { create(:project, :private, public_builds: false) } + it { is_expected.to be_disallowed(*all_read_analytics_permissions) } + end + end - before_all do - create(:project_group_link, project: project, group: group) - end + context 'for developer' do + let(:current_user) { developer } - where(:role) do - Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) - end + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end - with_them do - it 'a pending member in the group has permissions to the project as if the user is not a member' do - create(:group_member, :awaiting, role, source: group, user: current_user) + context 'for admin', :enable_admin_mode do + let(:current_user) { admin } - expect_private_project_permissions_as_if_non_member + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end + + context 'for auditor' do + let(:current_user) { auditor } + + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end end - end - end - context 'with a group invited to another group' do - let_it_be(:other_group) { create(:group, :public) } - let_it_be(:project) { create(:project, :private, public_builds: false, namespace: other_group) } + context 'when analytics is enabled for the project' do + let(:project) { project_with_analytics_enabled } - before_all do - create(:group_group_link, shared_with_group: group, shared_group: other_group) - end + %w[guest planner].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - where(:role) do - Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) - end + it { is_expected.to be_disallowed(:read_project_merge_request_analytics) } + it { is_expected.to be_disallowed(:read_code_review_analytics) } + it { is_expected.to be_disallowed(:read_cycle_analytics) } + it { is_expected.to be_allowed(:read_issue_analytics) } + end + end - with_them do - it "a pending member in the group has permissions to the other group's project as if the user is not a member" do - create(:group_member, :awaiting, role, source: group, user: current_user) + context 'for developer' do + let(:current_user) { developer } - expect_private_project_permissions_as_if_non_member - end - end - end + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end - context 'with a subgroup' do - let_it_be(:subgroup) { create(:group, :private, parent: group) } - let_it_be(:project) { create(:project, :private, public_builds: false, namespace: subgroup) } + context 'for admin', :enable_admin_mode do + let(:current_user) { admin } - where(:role) do - Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) - end + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end - with_them do - it 'a pending member in the group has permissions to the subgroup project as if the user is not a member' do - create(:group_member, :awaiting, role, source: group, user: current_user) + context 'for auditor' do + let(:current_user) { auditor } - expect_private_project_permissions_as_if_non_member + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end end end end - def expect_private_project_permissions_as_if_non_member - expect_disallowed(*guest_permissions) - expect_disallowed(*reporter_permissions) - expect_disallowed(*team_member_reporter_permissions) - expect_disallowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - describe ':read_approvers' do - let(:policy) { :read_approvers } + describe ':build_read_project' do + let(:policy) { :build_read_project } - where(:role, :allowed) do - :guest | false - :planner | false - :reporter | false - :developer | false - :maintainer | true - :auditor | true - :owner | true - :admin | true + where(:role, :project_visibility, :allowed) do + :guest | 'public' | true + :planner | 'public' | true + :reporter | 'public' | true + :developer | 'public' | true + :maintainer | 'public' | true + :owner | 'public' | true + :admin | 'public' | true + :guest | 'internal' | true + :planner | 'internal' | true + :reporter | 'internal' | true + :developer | 'internal' | true + :maintainer | 'internal' | true + :owner | 'internal' | true + :admin | 'internal' | true + :guest | 'private' | false + :planner | 'private' | false + :reporter | 'private' | true + :developer | 'private' | true + :maintainer | 'private' | true + :owner | 'private' | true + :admin | 'private' | false end with_them do let(:current_user) { public_send(role) } before do - enable_admin_mode!(current_user) if role == :admin + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(project_visibility)) end it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end - end - context 'importing members from another project' do - let(:current_user) { owner } + describe 'pending member permissions' do + let_it_be(:current_user) { create(:user) } + let_it_be(:group) { create(:group, :public) } - context 'for a personal project' do - it { is_expected.to be_allowed(:import_project_members_from_another_project) } - end + context 'with a pending membership in a private project' do + let_it_be(:project) { create(:project, :private, public_builds: false) } + + where(:role) do + Gitlab::Access.sym_options.keys.map(&:to_sym) + end - context 'for a project in a group' do - let(:project) { create(:project, group: create(:group)) } + with_them do + it 'a pending member has permissions to the project as if the user is not a member' do + create(:project_member, :awaiting, role, source: project, user: current_user) - context 'when the project has locked their membership' do - context 'via the parent group' do - before do - project.group.update!(membership_lock: true) + expect_private_project_permissions_as_if_non_member end - - it { is_expected.to be_disallowed(:import_project_members_from_another_project) } end + end - context 'via LDAP' do - before do - stub_application_setting(lock_memberships_to_ldap: true) - end + context 'with a group invited to a project' do + let_it_be(:project) { create(:project, :private, public_builds: false) } - it { is_expected.to be_disallowed(:import_project_members_from_another_project) } + before_all do + create(:project_group_link, project: project, group: group) end - context 'via SAML' do - before do - stub_application_setting(lock_memberships_to_saml: true) - end + where(:role) do + Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) + end + + with_them do + it 'a pending member in the group has permissions to the project as if the user is not a member' do + create(:group_member, :awaiting, role, source: group, user: current_user) - it { is_expected.to be_disallowed(:import_project_members_from_another_project) } + expect_private_project_permissions_as_if_non_member + end end end - end - end - describe 'inviting a group' do - let_it_be_with_reload(:current_user) { developer } - let_it_be_with_reload(:project) { public_project } + context 'with a group invited to another group' do + let_it_be(:other_group) { create(:group, :public) } + let_it_be(:project) { create(:project, :private, public_builds: false, namespace: other_group) } - let_it_be(:banned_group) { create(:group) } - let_it_be(:banned_subgroup) { create(:group, parent: banned_group) } + before_all do + create(:group_group_link, shared_with_group: group, shared_group: other_group) + end - before do - stub_licensed_features(unique_project_download_limit: true) - create(:namespace_ban, user: current_user, namespace: banned_group) - end + where(:role) do + Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) + end - it { is_expected.to be_allowed(:read_project) } + with_them do + it "a pending member in the group has permissions to the other group's project as if the user is not a member" do + create(:group_member, :awaiting, role, source: group, user: current_user) - context 'when the user is banned from the invited group' do - before do - create(:project_group_link, project: project, group: banned_group) + expect_private_project_permissions_as_if_non_member + end + end end - it { is_expected.to be_disallowed(:read_project) } - end + context 'with a subgroup' do + let_it_be(:subgroup) { create(:group, :private, parent: group) } + let_it_be(:project) { create(:project, :private, public_builds: false, namespace: subgroup) } - context 'when the user is banned from the invited subgroup' do - before do - create(:project_group_link, project: project, group: banned_subgroup) - end + where(:role) do + Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) + end - it { is_expected.to be_disallowed(:read_project) } - end - end + with_them do + it 'a pending member in the group has permissions to the subgroup project as if the user is not a member' do + create(:group_member, :awaiting, role, source: group, user: current_user) - describe 'user banned from namespace' do - let_it_be_with_reload(:current_user) { create(:user) } + expect_private_project_permissions_as_if_non_member + end + end + end - let_it_be(:group) { create(:group, :private) } - let_it_be(:project) { create(:project, :private, public_builds: false, group: group) } + def expect_private_project_permissions_as_if_non_member + expect_disallowed(*guest_permissions) + expect_disallowed(*reporter_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end - before do - stub_licensed_features(unique_project_download_limit: true) - project.add_developer(current_user) - end + describe ':read_approvers' do + let(:policy) { :read_approvers } - context 'when user is not banned' do - it { is_expected.to be_allowed(:read_project) } - end + where(:role, :allowed) do + :guest | false + :planner | false + :reporter | false + :developer | false + :maintainer | true + :auditor | true + :owner | true + :admin | true + end - context 'when user is banned' do - before do - create(:namespace_ban, user: current_user, namespace: group.root_ancestor) - end + with_them do + let(:current_user) { public_send(role) } - it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } + before do + enable_admin_mode!(current_user) if role == :admin + end - context 'as an owner of the project' do - before do - project.add_owner(current_user) + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end - - it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } end + end - context 'when project is inside subgroup' do - let_it_be(:subgroup) { create(:group, :private, parent: group) } - let_it_be(:project) { create(:project, :private, public_builds: false, group: subgroup) } + context 'importing members from another project' do + let(:current_user) { owner } - it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } + context 'for a personal project' do + it { is_expected.to be_allowed(:import_project_members_from_another_project) } end - context 'as an admin' do - let_it_be(:current_user) { admin } + context 'for a project in a group' do + let(:project) { create(:project, group: create(:group)) } - context 'when admin mode is enabled', :enable_admin_mode do - it { is_expected.to be_allowed(:read_project) } - end - end + context 'when the project has locked their membership' do + context 'via the parent group' do + before do + project.group.update!(membership_lock: true) + end - context 'when project is public' do - let_it_be(:group) { create(:group, :public) } - let_it_be(:project) { create(:project, :public, public_builds: false, group: group) } + it { is_expected.to be_disallowed(:import_project_members_from_another_project) } + end - it { is_expected.to be_disallowed(:read_project) } - end + context 'via LDAP' do + before do + stub_application_setting(lock_memberships_to_ldap: true) + end - context 'when the limit_unique_project_downloads_per_namespace_user feature flag is disabled' do - before do - stub_feature_flags(limit_unique_project_downloads_per_namespace_user: false) - end + it { is_expected.to be_disallowed(:import_project_members_from_another_project) } + end - it { is_expected.to be_allowed(:read_project) } - end + context 'via SAML' do + before do + stub_application_setting(lock_memberships_to_saml: true) + end - context 'when licensed feature unique_project_download_limit is not available' do - before do - stub_licensed_features(unique_project_download_limit: false) + it { is_expected.to be_disallowed(:import_project_members_from_another_project) } + end end - - it { is_expected.to be_allowed(:read_project) } end end - end - describe 'create_objective' do - let(:okr_policies) { [:create_objective, :create_key_result] } - - where(:role, :allowed) do - :guest | true - :planner | true - :reporter | true - :developer | true - :maintainer | true - :auditor | false - :owner | true - :admin | true - end + describe 'inviting a group' do + let_it_be_with_reload(:current_user) { developer } + let_it_be_with_reload(:project) { public_project } - with_them do - let(:current_user) { public_send(role) } + let_it_be(:banned_group) { create(:group) } + let_it_be(:banned_subgroup) { create(:group, parent: banned_group) } before do - enable_admin_mode!(current_user) if role == :admin - stub_licensed_features(okrs: true) + stub_licensed_features(unique_project_download_limit: true) + create(:namespace_ban, user: current_user, namespace: banned_group) end - context 'when okrs_mvc feature flag is enabled' do - it { is_expected.to(allowed ? be_allowed(*okr_policies) : be_disallowed(*okr_policies)) } - end + it { is_expected.to be_allowed(:read_project) } - context 'when okrs_mvc feature flag is disabled' do + context 'when the user is banned from the invited group' do before do - stub_feature_flags(okrs_mvc: false) + create(:project_group_link, project: project, group: banned_group) end - it { is_expected.to be_disallowed(*okr_policies) } + it { is_expected.to be_disallowed(:read_project) } end - context 'when okrs license feature is not available' do + context 'when the user is banned from the invited subgroup' do before do - stub_licensed_features(okrs: false) + create(:project_group_link, project: project, group: banned_subgroup) end - it { is_expected.to be_disallowed(*okr_policies) } + it { is_expected.to be_disallowed(:read_project) } end end - end - describe 'read_member_role' do - let_it_be_with_reload(:project) { private_project_in_group } - let_it_be_with_reload(:current_user) { create(:user) } - - let(:permission) { :read_member_role } - - where(:role, :allowed) do - :guest | true - :planner | true - :reporter | true - :developer | true - :maintainer | true - :auditor | false - :owner | true - :admin | true - end + describe 'user banned from namespace' do + let_it_be_with_reload(:current_user) { create(:user) } + + let_it_be(:group) { create(:group, :private) } + let_it_be(:project) { create(:project, :private, public_builds: false, group: group) } - with_them do before do - if role == :admin - current_user.update!(admin: true) - elsif role == :auditor - current_user.update!(auditor: true) - else - create(:project_member, role, source: project, user: current_user) - end + stub_licensed_features(unique_project_download_limit: true) + project.add_developer(current_user) + end - enable_admin_mode!(current_user) if role == :admin + context 'when user is not banned' do + it { is_expected.to be_allowed(:read_project) } end - context 'when custom_roles feature is enabled' do + context 'when user is banned' do before do - stub_licensed_features(custom_roles: true) + create(:namespace_ban, user: current_user, namespace: group.root_ancestor) end - it do - is_expected.to(allowed ? be_allowed(permission) : be_disallowed(permission)) - end - end + it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } - context 'when custom_roles feature is disabled' do - before do - stub_licensed_features(custom_roles: false) + context 'as an owner of the project' do + before do + project.add_owner(current_user) + end + + it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } end - it { is_expected.to be_disallowed(permission) } - end - end - end + context 'when project is inside subgroup' do + let_it_be(:subgroup) { create(:group, :private, parent: group) } + let_it_be(:project) { create(:project, :private, public_builds: false, group: subgroup) } - context 'hidden projects' do - let(:project) { create(:project, :repository, hidden: true) } - let(:current_user) { create(:user) } + it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } + end - before do - project.add_owner(current_user) - end + context 'as an admin' do + let_it_be(:current_user) { admin } - it { is_expected.to be_disallowed(:download_code) } - it { is_expected.to be_disallowed(:build_download_code) } - end + context 'when admin mode is enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:read_project) } + end + end - context 'custom role' do - let_it_be(:guest) { create(:user) } - let_it_be(:project) { private_project_in_group } - let_it_be(:group_member_guest) do - create( - :group_member, - user: guest, - source: project.group, - access_level: Gitlab::Access::GUEST - ) - end + context 'when project is public' do + let_it_be(:group) { create(:group, :public) } + let_it_be(:project) { create(:project, :public, public_builds: false, group: group) } - let_it_be(:project_member_guest) do - create( - :project_member, - :guest, - user: guest, - project: project, - access_level: Gitlab::Access::GUEST - ) - end + it { is_expected.to be_disallowed(:read_project) } + end - let(:member_role_abilities) { {} } - let(:allowed_abilities) { [] } - let(:disallowed_abilities) { [] } - let(:current_user) { guest } - let(:licensed_features) { {} } + context 'when the limit_unique_project_downloads_per_namespace_user feature flag is disabled' do + before do + stub_feature_flags(limit_unique_project_downloads_per_namespace_user: false) + end - subject { described_class.new(current_user, project) } + it { is_expected.to be_allowed(:read_project) } + end - def create_member_role(member, abilities = member_role_abilities) - params = abilities.merge(namespace: project.group) + context 'when licensed feature unique_project_download_limit is not available' do + before do + stub_licensed_features(unique_project_download_limit: false) + end - create(:member_role, :guest, params).tap do |role| - role.members << member + it { is_expected.to be_allowed(:read_project) } + end end end - shared_examples 'custom roles abilities' do - context 'with custom_roles license disabled' do - before do - create_member_role(group_member_guest) - - stub_licensed_features(licensed_features.merge(custom_roles: false)) - end + describe 'create_objective' do + let(:okr_policies) { [:create_objective, :create_key_result] } - it { expect_disallowed(*allowed_abilities) } + where(:role, :allowed) do + :guest | true + :planner | true + :reporter | true + :developer | true + :maintainer | true + :auditor | false + :owner | true + :admin | true end - context 'with custom_roles license enabled' do + with_them do + let(:current_user) { public_send(role) } + before do - stub_licensed_features(licensed_features.merge(custom_roles: true)) + enable_admin_mode!(current_user) if role == :admin + stub_licensed_features(okrs: true) end - context 'custom role for parent group' do - context 'when a role enables the abilities' do - before do - create_member_role(group_member_guest) - end + context 'when okrs_mvc feature flag is enabled' do + it { is_expected.to(allowed ? be_allowed(*okr_policies) : be_disallowed(*okr_policies)) } + end - it { expect_allowed(*allowed_abilities) } - it { expect_disallowed(*disallowed_abilities) } + context 'when okrs_mvc feature flag is disabled' do + before do + stub_feature_flags(okrs_mvc: false) end - context 'when a role does not enable the abilities' do - it { expect_disallowed(*allowed_abilities) } + it { is_expected.to be_disallowed(*okr_policies) } + end + + context 'when okrs license feature is not available' do + before do + stub_licensed_features(okrs: false) end + + it { is_expected.to be_disallowed(*okr_policies) } end + end + end - context 'custom role on project membership' do - context 'when a role enables the abilities' do - before do - create_member_role(project_member_guest) - end + describe 'read_member_role' do + let_it_be_with_reload(:project) { private_project_in_group } + let_it_be_with_reload(:current_user) { create(:user) } + + let(:permission) { :read_member_role } + + where(:role, :allowed) do + :guest | true + :planner | true + :reporter | true + :developer | true + :maintainer | true + :auditor | false + :owner | true + :admin | true + end + + with_them do + before do + if role == :admin + current_user.update!(admin: true) + elsif role == :auditor + current_user.update!(auditor: true) + else + create(:project_member, role, source: project, user: current_user) + end + + enable_admin_mode!(current_user) if role == :admin + end - it { expect_allowed(*allowed_abilities) } - it { expect_disallowed(*disallowed_abilities) } + context 'when custom_roles feature is enabled' do + before do + stub_licensed_features(custom_roles: true) end - context 'when a role does not enable the abilities' do - it { expect_disallowed(*allowed_abilities) } + it do + is_expected.to(allowed ? be_allowed(permission) : be_disallowed(permission)) end end - end - end - context 'for a member role with read_code true' do - let(:member_role_abilities) { { read_code: true } } - let(:allowed_abilities) { [:read_code] } + context 'when custom_roles feature is disabled' do + before do + stub_licensed_features(custom_roles: false) + end - it_behaves_like 'custom roles abilities' + it { is_expected.to be_disallowed(permission) } + end + end end - context 'for a member role with admin_runners true' do - let(:member_role_abilities) { { admin_runners: true } } - let(:allowed_abilities) do - [ - :admin_runner, - :create_runner, - :read_runner - ] + context 'hidden projects' do + let(:project) { create(:project, :repository, hidden: true) } + let(:current_user) { create(:user) } + + before do + project.add_owner(current_user) end - it_behaves_like 'custom roles abilities' + it { is_expected.to be_disallowed(:download_code) } + it { is_expected.to be_disallowed(:build_download_code) } end - context 'for a member role with read_vulnerability true' do - let(:member_role_abilities) { { read_vulnerability: true } } - let(:allowed_abilities) do - [ - :access_security_and_compliance, - :create_vulnerability_export, - :read_security_resource, - :read_vulnerability, - :read_vulnerability_feedback, - :read_vulnerability_scanner - ] + context 'custom role' do + let_it_be(:guest) { create(:user) } + let_it_be(:project) { private_project_in_group } + let_it_be(:group_member_guest) do + create( + :group_member, + user: guest, + source: project.group, + access_level: Gitlab::Access::GUEST + ) end - it_behaves_like 'custom roles abilities' - - it 'does not enable to admin_vulnerability' do - expect(subject).to be_disallowed(:admin_vulnerability) + let_it_be(:project_member_guest) do + create( + :project_member, + :guest, + user: guest, + project: project, + access_level: Gitlab::Access::GUEST + ) end - end - context 'for a member role with admin_terraform_state true' do - let(:member_role_abilities) { { admin_terraform_state: true } } - let(:allowed_abilities) { [:read_terraform_state, :admin_terraform_state] } + let(:member_role_abilities) { {} } + let(:allowed_abilities) { [] } + let(:disallowed_abilities) { [] } + let(:current_user) { guest } + let(:licensed_features) { {} } - it_behaves_like 'custom roles abilities' - end + subject { described_class.new(current_user, project) } - context 'for a member role with admin_vulnerability true' do - let(:member_role_abilities) { { read_vulnerability: true, admin_vulnerability: true } } - let(:allowed_abilities) do - [ - :admin_vulnerability, - :create_vulnerability_feedback, - :destroy_vulnerability_feedback, - :read_vulnerability, - :read_vulnerability_feedback, - :update_vulnerability_feedback, - :create_vulnerability_state_transition - ] + def create_member_role(member, abilities = member_role_abilities) + params = abilities.merge(namespace: project.group) + + create(:member_role, :guest, params).tap do |role| + role.members << member + end end - it_behaves_like 'custom roles abilities' - end + shared_examples 'custom roles abilities' do + context 'with custom_roles license disabled' do + before do + create_member_role(group_member_guest) - context 'for a member role with read_dependency true' do - let(:member_role_abilities) { { read_dependency: true } } - let(:allowed_abilities) { [:access_security_and_compliance, :read_dependency] } - let(:licensed_features) { { dependency_scanning: true } } + stub_licensed_features(licensed_features.merge(custom_roles: false)) + end - it_behaves_like 'custom roles abilities' - end + it { expect_disallowed(*allowed_abilities) } + end + + context 'with custom_roles license enabled' do + before do + stub_licensed_features(licensed_features.merge(custom_roles: true)) + end - context 'for a member role with admin_merge_request true' do - let(:member_role_abilities) { { admin_merge_request: true } } - let(:allowed_abilities) { [:admin_merge_request] } + context 'custom role for parent group' do + context 'when a role enables the abilities' do + before do + create_member_role(group_member_guest) + end - it_behaves_like 'custom roles abilities' + it { expect_allowed(*allowed_abilities) } + it { expect_disallowed(*disallowed_abilities) } + end - context 'when the merge requests access level is set as private' do - before do - project.project_feature.update_column(:merge_requests_access_level, ProjectFeature::PRIVATE) + context 'when a role does not enable the abilities' do + it { expect_disallowed(*allowed_abilities) } + end + end + + context 'custom role on project membership' do + context 'when a role enables the abilities' do + before do + create_member_role(project_member_guest) + end + + it { expect_allowed(*allowed_abilities) } + it { expect_disallowed(*disallowed_abilities) } + end + + context 'when a role does not enable the abilities' do + it { expect_disallowed(*allowed_abilities) } + end + end end + end + + context 'for a member role with read_code true' do + let(:member_role_abilities) { { read_code: true } } + let(:allowed_abilities) { [:read_code] } it_behaves_like 'custom roles abilities' end - context 'when the merge requests access level is set as disabled' do - before do - project.project_feature.update_column(:merge_requests_access_level, ProjectFeature::DISABLED) + context 'for a member role with admin_runners true' do + let(:member_role_abilities) { { admin_runners: true } } + let(:allowed_abilities) do + [ + :admin_runner, + :create_runner, + :read_runner + ] end - it { is_expected.to be_disallowed(:read_merge_request, :admin_merge_request, :download_code) } + it_behaves_like 'custom roles abilities' end - end - context 'for a member role with manage_project_access_tokens true' do - let(:member_role_abilities) { { manage_project_access_tokens: true } } - let(:allowed_abilities) { [:manage_resource_access_tokens] } + context 'for a member role with read_vulnerability true' do + let(:member_role_abilities) { { read_vulnerability: true } } + let(:allowed_abilities) do + [ + :access_security_and_compliance, + :create_vulnerability_export, + :read_security_resource, + :read_vulnerability, + :read_vulnerability_feedback, + :read_vulnerability_scanner + ] + end - it_behaves_like 'custom roles abilities' - end + it_behaves_like 'custom roles abilities' - context 'for a member role with archive_project true' do - let(:member_role_abilities) { { archive_project: true } } - let(:allowed_abilities) { [:archive_project, :view_edit_page] } + it 'does not enable to admin_vulnerability' do + expect(subject).to be_disallowed(:admin_vulnerability) + end + end - it_behaves_like 'custom roles abilities' - end + context 'for a member role with admin_terraform_state true' do + let(:member_role_abilities) { { admin_terraform_state: true } } + let(:allowed_abilities) { [:read_terraform_state, :admin_terraform_state] } - context 'for a member role with `remove_project` true' do - let(:member_role_abilities) { { remove_project: true } } - let(:allowed_abilities) { [:remove_project, :view_edit_page] } + it_behaves_like 'custom roles abilities' + end - it_behaves_like 'custom roles abilities' - end + context 'for a member role with admin_vulnerability true' do + let(:member_role_abilities) { { read_vulnerability: true, admin_vulnerability: true } } + let(:allowed_abilities) do + [ + :admin_vulnerability, + :create_vulnerability_feedback, + :destroy_vulnerability_feedback, + :read_vulnerability, + :read_vulnerability_feedback, + :update_vulnerability_feedback, + :create_vulnerability_state_transition + ] + end - context 'for a member role with `manage_security_policy_link` true' do - let(:member_role_abilities) { { manage_security_policy_link: true } } - let(:licensed_features) { { security_orchestration_policies: true } } - let(:allowed_abilities) do - [:read_security_orchestration_policies, :update_security_orchestration_policy_project, - :access_security_and_compliance] + it_behaves_like 'custom roles abilities' end - let(:disallowed_abilities) do - [:modify_security_policy] + context 'for a member role with read_dependency true' do + let(:member_role_abilities) { { read_dependency: true } } + let(:allowed_abilities) { [:access_security_and_compliance, :read_dependency] } + let(:licensed_features) { { dependency_scanning: true } } + + it_behaves_like 'custom roles abilities' end - it_behaves_like 'custom roles abilities' - end + context 'for a member role with admin_merge_request true' do + let(:member_role_abilities) { { admin_merge_request: true } } + let(:allowed_abilities) { [:admin_merge_request] } - context 'when a user is assigned to custom roles in both group and project' do - before do - stub_licensed_features(custom_roles: true, dependency_scanning: true) + it_behaves_like 'custom roles abilities' + + context 'when the merge requests access level is set as private' do + before do + project.project_feature.update_column(:merge_requests_access_level, ProjectFeature::PRIVATE) + end - create_member_role(group_member_guest, { read_dependency: true }) - create_member_role(project_member_guest, { read_code: true }) + it_behaves_like 'custom roles abilities' + end + + context 'when the merge requests access level is set as disabled' do + before do + project.project_feature.update_column(:merge_requests_access_level, ProjectFeature::DISABLED) + end + + it { is_expected.to be_disallowed(:read_merge_request, :admin_merge_request, :download_code) } + end end - it { is_expected.to be_allowed(:read_dependency) } - it { is_expected.to be_allowed(:read_code) } - end + context 'for a member role with manage_project_access_tokens true' do + let(:member_role_abilities) { { manage_project_access_tokens: true } } + let(:allowed_abilities) { [:manage_resource_access_tokens] } - context 'for a custom role with the `admin_cicd_variables` ability' do - let(:member_role_abilities) { { admin_cicd_variables: true } } - let(:allowed_abilities) { [:admin_cicd_variables] } + it_behaves_like 'custom roles abilities' + end - it_behaves_like 'custom roles abilities' - end + context 'for a member role with archive_project true' do + let(:member_role_abilities) { { archive_project: true } } + let(:allowed_abilities) { [:archive_project, :view_edit_page] } - context 'for a custom role with the `admin_push_rules` ability' do - let(:member_role_abilities) { { admin_push_rules: true } } - let(:allowed_abilities) { [:admin_push_rules] } + it_behaves_like 'custom roles abilities' + end - it_behaves_like 'custom roles abilities' + context 'for a member role with `remove_project` true' do + let(:member_role_abilities) { { remove_project: true } } + let(:allowed_abilities) { [:remove_project, :view_edit_page] } - context 'when push rules feature is enabled' do - before do - stub_licensed_features( - custom_roles: true, - push_rules: true, - commit_committer_check: true, - commit_committer_name_check: true, - reject_unsigned_commits: true, - reject_non_dco_commits: true - ) + it_behaves_like 'custom roles abilities' + end - create_member_role(group_member_guest) + context 'for a member role with `manage_security_policy_link` true' do + let(:member_role_abilities) { { manage_security_policy_link: true } } + let(:licensed_features) { { security_orchestration_policies: true } } + let(:allowed_abilities) do + [:read_security_orchestration_policies, :update_security_orchestration_policy_project, + :access_security_and_compliance] end - it do - expect_allowed( - :change_push_rules, - :read_commit_committer_check, - :change_commit_committer_check, - :change_commit_committer_name_check, - :read_reject_unsigned_commits, - :change_reject_unsigned_commits, - :change_reject_non_dco_commits - ) + let(:disallowed_abilities) do + [:modify_security_policy] end - end - end - context 'for a custom role with the `admin_compliance_framework` ability' do - let(:licensed_features) do - { - compliance_framework: true, - project_level_compliance_dashboard: true, - project_level_compliance_adherence_report: true, - project_level_compliance_violations_report: true - } + it_behaves_like 'custom roles abilities' end - let(:member_role_abilities) { { read_compliance_dashboard: true, admin_compliance_framework: true } } + context 'when a user is assigned to custom roles in both group and project' do + before do + stub_licensed_features(custom_roles: true, dependency_scanning: true) - let(:allowed_abilities) do - [ - :admin_compliance_framework, - :read_compliance_dashboard, - :read_compliance_adherence_report, - :read_compliance_violations_report - ] + create_member_role(group_member_guest, { read_dependency: true }) + create_member_role(project_member_guest, { read_code: true }) + end + + it { is_expected.to be_allowed(:read_dependency) } + it { is_expected.to be_allowed(:read_code) } end - it_behaves_like 'custom roles abilities' - end + context 'for a custom role with the `admin_cicd_variables` ability' do + let(:member_role_abilities) { { admin_cicd_variables: true } } + let(:allowed_abilities) { [:admin_cicd_variables] } - context 'for a custom role with the `read_compliance_dashboard` ability' do - let(:licensed_features) do - { - project_level_compliance_dashboard: true, - project_level_compliance_adherence_report: true, - project_level_compliance_violations_report: true - } + it_behaves_like 'custom roles abilities' end - let(:member_role_abilities) { { read_compliance_dashboard: true } } + context 'for a custom role with the `admin_push_rules` ability' do + let(:member_role_abilities) { { admin_push_rules: true } } + let(:allowed_abilities) { [:admin_push_rules] } - let(:allowed_abilities) do - [ - :read_compliance_dashboard, - :read_compliance_adherence_report, - :read_compliance_violations_report - ] - end + it_behaves_like 'custom roles abilities' - it_behaves_like 'custom roles abilities' - end + context 'when push rules feature is enabled' do + before do + stub_licensed_features( + custom_roles: true, + push_rules: true, + commit_committer_check: true, + commit_committer_name_check: true, + reject_unsigned_commits: true, + reject_non_dco_commits: true + ) + + create_member_role(group_member_guest) + end - context 'for a member role with `admin_web_hook` true' do - let(:member_role_abilities) { { admin_web_hook: true } } - let(:allowed_abilities) { [:admin_web_hook, :read_web_hook] } + it do + expect_allowed( + :change_push_rules, + :read_commit_committer_check, + :change_commit_committer_check, + :change_commit_committer_name_check, + :read_reject_unsigned_commits, + :change_reject_unsigned_commits, + :change_reject_non_dco_commits + ) + end + end + end - it_behaves_like 'custom roles abilities' - end + context 'for a custom role with the `admin_compliance_framework` ability' do + let(:licensed_features) do + { + compliance_framework: true, + project_level_compliance_dashboard: true, + project_level_compliance_adherence_report: true, + project_level_compliance_violations_report: true + } + end - context 'for a member role with `manage_deploy_tokens` true' do - let(:member_role_abilities) { { manage_deploy_tokens: true } } - let(:allowed_abilities) { [:manage_deploy_tokens, :read_deploy_token, :create_deploy_token, :destroy_deploy_token] } + let(:member_role_abilities) { { read_compliance_dashboard: true, admin_compliance_framework: true } } - it_behaves_like 'custom roles abilities' - end + let(:allowed_abilities) do + [ + :admin_compliance_framework, + :read_compliance_dashboard, + :read_compliance_adherence_report, + :read_compliance_violations_report + ] + end - context 'for a custom role with the `manage_merge_request_settings` ability' do - let(:member_role_abilities) { { read_code: true, manage_merge_request_settings: true } } - let(:allowed_abilities) do - [ - :manage_merge_request_settings, - :edit_approval_rule, - :modify_approvers_rules, - :modify_merge_request_author_setting, - :modify_merge_request_committer_setting - ] + it_behaves_like 'custom roles abilities' end - it_behaves_like 'custom roles abilities' + context 'for a custom role with the `read_compliance_dashboard` ability' do + let(:licensed_features) do + { + project_level_compliance_dashboard: true, + project_level_compliance_adherence_report: true, + project_level_compliance_violations_report: true + } + end + + let(:member_role_abilities) { { read_compliance_dashboard: true } } - context 'when `target_branch_rules` feature is available' do - let(:licensed_features) { { target_branch_rules: true } } - let(:allowed_abilities) { [:admin_target_branch_rule] } + let(:allowed_abilities) do + [ + :read_compliance_dashboard, + :read_compliance_adherence_report, + :read_compliance_violations_report + ] + end it_behaves_like 'custom roles abilities' end - context 'when `merge_request_approvers` feature is available' do - let(:licensed_features) { { merge_request_approvers: true } } - let(:allowed_abilities) { [:admin_merge_request_approval_settings] } + context 'for a member role with `admin_web_hook` true' do + let(:member_role_abilities) { { admin_web_hook: true } } + let(:allowed_abilities) { [:admin_web_hook, :read_web_hook] } it_behaves_like 'custom roles abilities' end - end - context 'for a custom role with the `admin_integrations` ability' do - let(:member_role_abilities) { { admin_integrations: true } } - let(:allowed_abilities) { [:admin_integrations] } + context 'for a member role with `manage_deploy_tokens` true' do + let(:member_role_abilities) { { manage_deploy_tokens: true } } + let(:allowed_abilities) { [:manage_deploy_tokens, :read_deploy_token, :create_deploy_token, :destroy_deploy_token] } - it_behaves_like 'custom roles abilities' - end + it_behaves_like 'custom roles abilities' + end + + context 'for a custom role with the `manage_merge_request_settings` ability' do + let(:member_role_abilities) { { read_code: true, manage_merge_request_settings: true } } + let(:allowed_abilities) do + [ + :manage_merge_request_settings, + :edit_approval_rule, + :modify_approvers_rules, + :modify_merge_request_author_setting, + :modify_merge_request_committer_setting + ] + end + + it_behaves_like 'custom roles abilities' - context 'for a custom role with the `read_runners` ability' do - let(:member_role_abilities) { { read_runners: true } } - let(:allowed_abilities) { [:read_project_runners, :read_runner] } + context 'when `target_branch_rules` feature is available' do + let(:licensed_features) { { target_branch_rules: true } } + let(:allowed_abilities) { [:admin_target_branch_rule] } - it_behaves_like 'custom roles abilities' - end + it_behaves_like 'custom roles abilities' + end + + context 'when `merge_request_approvers` feature is available' do + let(:licensed_features) { { merge_request_approvers: true } } + let(:allowed_abilities) { [:admin_merge_request_approval_settings] } - context 'for a member role with `admin_protected_branch` true' do - let(:member_role_abilities) { { admin_protected_branch: true } } - let(:allowed_abilities) do - [:admin_protected_branch, :read_protected_branch, :create_protected_branch, - :update_protected_branch, :destroy_protected_branch] + it_behaves_like 'custom roles abilities' + end end - it_behaves_like 'custom roles abilities' - end + context 'for a custom role with the `admin_integrations` ability' do + let(:member_role_abilities) { { admin_integrations: true } } + let(:allowed_abilities) { [:admin_integrations] } - context 'for a member role with `manage_protected_tags` true' do - let(:member_role_abilities) { { manage_protected_tags: true } } - let(:allowed_abilities) { [:manage_protected_tags] } + it_behaves_like 'custom roles abilities' + end - it_behaves_like 'custom roles abilities' - end - end + context 'for a custom role with the `read_runners` ability' do + let(:member_role_abilities) { { read_runners: true } } + let(:allowed_abilities) { [:read_project_runners, :read_runner] } - describe 'permissions for suggested reviewers bot', :saas do - let(:permissions) { [:admin_project_member, :create_resource_access_tokens] } - let(:namespace) { build_stubbed(:namespace) } - let(:project) { build_stubbed(:project, namespace: namespace) } + it_behaves_like 'custom roles abilities' + end - context 'when user is suggested_reviewers_bot' do - let(:current_user) { Users::Internal.suggested_reviewers_bot } + context 'for a member role with `admin_protected_branch` true' do + let(:member_role_abilities) { { admin_protected_branch: true } } + let(:allowed_abilities) do + [:admin_protected_branch, :read_protected_branch, :create_protected_branch, + :update_protected_branch, :destroy_protected_branch] + end - where(:suggested_reviewers_available, :token_creation_allowed, :allowed) do - false | false | false - false | true | false - true | false | false - true | true | true + it_behaves_like 'custom roles abilities' end - with_them do + context 'for a custom role with the `admin_security_testing` ability' do + let(:member_role_abilities) { { admin_security_testing: true } } + let(:licensed_features) do + { security_dashboard: true, + security_scans_api: true, + security_on_demand_scans: true, + coverage_fuzzing: true, + pre_receive_secret_detection: true, + container_scanning_for_registry: true, + project_level_compliance_dashboard: true } + end + + let(:allowed_abilities) do + [ + :access_security_and_compliance, + :read_security_configuration, + :access_security_scans_api, + :read_on_demand_dast_scan, + :create_on_demand_dast_scan, + :edit_on_demand_dast_scan, + :enable_pre_receive_secret_detection, + :read_project_security_dashboard, + :read_project_security_exclusions, + :read_coverage_fuzzing, + :create_coverage_fuzzing_corpus, + :enable_container_scanning_for_registry, + :push_code, + :create_merge_request_from, + :create_pipeline, + :build_download_code, + :read_merge_request, + :download_code, + :read_project_runners, + :read_pre_receive_secret_detection_info + ] + end + before do - allow(project).to receive(:can_suggest_reviewers?).and_return(suggested_reviewers_available) + # Ensure merge requests are enabled for the project + project.project_feature.update!(merge_requests_access_level: ProjectFeature::ENABLED) + allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:sast).and_return( + instance_double(CloudConnector::BaseAvailableServiceData, free_access?: true)) + end - allow(::Gitlab::CurrentSettings) - .to receive(:personal_access_tokens_disabled?) - .and_return(!token_creation_allowed) + it_behaves_like 'custom roles abilities' + end + end + + describe 'permissions for suggested reviewers bot', :saas do + let(:permissions) { [:admin_project_member, :create_resource_access_tokens] } + let(:namespace) { build_stubbed(:namespace) } + let(:project) { build_stubbed(:project, namespace: namespace) } + + context 'when user is suggested_reviewers_bot' do + let(:current_user) { Users::Internal.suggested_reviewers_bot } + + where(:suggested_reviewers_available, :token_creation_allowed, :allowed) do + false | false | false + false | true | false + true | false | false + true | true | true end - it 'always allows permissions except when feature disabled' do - if allowed - expect_allowed(*permissions) - else - expect_disallowed(*permissions) + with_them do + before do + allow(project).to receive(:can_suggest_reviewers?).and_return(suggested_reviewers_available) + + allow(::Gitlab::CurrentSettings) + .to receive(:personal_access_tokens_disabled?) + .and_return(!token_creation_allowed) + end + + it 'always allows permissions except when feature disabled' do + if allowed + expect_allowed(*permissions) + else + expect_disallowed(*permissions) + end end end end - end - context 'when user is not suggested_reviewers_bot' do - let(:current_user) { developer } + context 'when user is not suggested_reviewers_bot' do + let(:current_user) { developer } - before do - allow(project).to receive(:can_suggest_reviewers?).and_return(true) + before do + allow(project).to receive(:can_suggest_reviewers?).and_return(true) - allow(::Gitlab::CurrentSettings) - .to receive(:personal_access_tokens_disabled?) - .and_return(false) - end + allow(::Gitlab::CurrentSettings) + .to receive(:personal_access_tokens_disabled?) + .and_return(false) + end - it 'does not allow permissions' do - expect_disallowed(*permissions) + it 'does not allow permissions' do + expect_disallowed(*permissions) + end end end - end - describe 'read_project_runners' do - context 'with auditor' do - let(:current_user) { auditor } + describe 'read_project_runners' do + context 'with auditor' do + let(:current_user) { auditor } - it { is_expected.to be_allowed(:read_project_runners) } + it { is_expected.to be_allowed(:read_project_runners) } + end end - end - describe 'read_runner_usage' do - where(:licensed, :current_user, :project, :enable_admin_mode, :clickhouse_configured, :expected) do - true | ref(:admin) | ref(:public_project_in_group) | true | true | true - false | ref(:maintainer) | ref(:public_project_in_group) | false | true | false - true | ref(:maintainer) | ref(:public_project_in_group) | false | false | false - true | ref(:maintainer) | ref(:public_project_in_group) | false | true | true - true | ref(:auditor) | ref(:public_project_in_group) | false | true | false - true | ref(:developer) | ref(:public_project_in_group) | false | true | false - true | ref(:admin) | ref(:public_project) | true | true | false - true | ref(:maintainer) | ref(:public_project) | false | true | false - end + describe 'read_runner_usage' do + where(:licensed, :current_user, :project, :enable_admin_mode, :clickhouse_configured, :expected) do + true | ref(:admin) | ref(:public_project_in_group) | true | true | true + false | ref(:maintainer) | ref(:public_project_in_group) | false | true | false + true | ref(:maintainer) | ref(:public_project_in_group) | false | false | false + true | ref(:maintainer) | ref(:public_project_in_group) | false | true | true + true | ref(:auditor) | ref(:public_project_in_group) | false | true | false + true | ref(:developer) | ref(:public_project_in_group) | false | true | false + true | ref(:admin) | ref(:public_project) | true | true | false + true | ref(:maintainer) | ref(:public_project) | false | true | false + end - with_them do - before do - stub_licensed_features(runner_performance_insights_for_namespace: licensed) + with_them do + before do + stub_licensed_features(runner_performance_insights_for_namespace: licensed) - enable_admin_mode!(admin) if enable_admin_mode + enable_admin_mode!(admin) if enable_admin_mode - allow(::Gitlab::ClickHouse).to receive(:configured?).and_return(clickhouse_configured) - end + allow(::Gitlab::ClickHouse).to receive(:configured?).and_return(clickhouse_configured) + end - it 'matches expectation' do - if expected - is_expected.to be_allowed(:read_runner_usage) - else - is_expected.to be_disallowed(:read_runner_usage) + it 'matches expectation' do + if expected + is_expected.to be_allowed(:read_runner_usage) + else + is_expected.to be_disallowed(:read_runner_usage) + end end end end - end - describe 'workspace creation' do - context 'with no user' do - let(:current_user) { nil } + describe 'workspace creation' do + context 'with no user' do + let(:current_user) { nil } - it { is_expected.to be_disallowed(:create_workspace) } - end + it { is_expected.to be_disallowed(:create_workspace) } + end - context 'with an authorized user' do - let(:current_user) { developer } + context 'with an authorized user' do + let(:current_user) { developer } - it { is_expected.to be_allowed(:create_workspace) } + it { is_expected.to be_allowed(:create_workspace) } + end end - end - - describe 'create_pipeline policy' do - context 'as a guest member' do - let(:current_user) { guest } - - it { is_expected.not_to be_allowed(:create_pipeline) } - context 'and user is a security_policy_bot' do - let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } - let(:current_user) { security_policy_bot } + describe 'create_pipeline policy' do + context 'as a guest member' do + let(:current_user) { guest } it { is_expected.not_to be_allowed(:create_pipeline) } - shared_examples 'allows to create pipeline' do - let(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } + context 'and user is a security_policy_bot' do + let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } + let(:current_user) { security_policy_bot } - before do - project.add_guest(security_policy_bot) - end + it { is_expected.not_to be_allowed(:create_pipeline) } - it { is_expected.to be_allowed(:create_pipeline) } - end + shared_examples 'allows to create pipeline' do + let(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } - context 'and user is a member of the project' do - context 'and the project is private' do - let(:project) { private_project } + before do + project.add_guest(security_policy_bot) + end - it_behaves_like 'allows to create pipeline' + it { is_expected.to be_allowed(:create_pipeline) } end - context 'and the project is internal' do - let(:project) { internal_project } + context 'and user is a member of the project' do + context 'and the project is private' do + let(:project) { private_project } - it_behaves_like 'allows to create pipeline' - end + it_behaves_like 'allows to create pipeline' + end - context 'and the project is public' do - let(:project) { public_project } + context 'and the project is internal' do + let(:project) { internal_project } - it_behaves_like 'allows to create pipeline' - end + it_behaves_like 'allows to create pipeline' + end - context 'and the project is public in group' do - let(:project) { public_project_in_group } + context 'and the project is public' do + let(:project) { public_project } - it_behaves_like 'allows to create pipeline' + it_behaves_like 'allows to create pipeline' + end + + context 'and the project is public in group' do + let(:project) { public_project_in_group } + + it_behaves_like 'allows to create pipeline' + end end end end end - end - describe 'build_download_code policy' do - let(:project) { private_project } + describe 'build_download_code policy' do + let(:project) { private_project } - context 'as a guest member' do - let(:current_user) { guest } + context 'as a guest member' do + let(:current_user) { guest } - it { is_expected.not_to be_allowed(:build_download_code) } + it { is_expected.not_to be_allowed(:build_download_code) } - context 'and user is a security_policy_bot' do - let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } - let(:current_user) { security_policy_bot } + context 'and user is a security_policy_bot' do + let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } + let(:current_user) { security_policy_bot } - it { is_expected.not_to be_allowed(:build_download_code) } + it { is_expected.not_to be_allowed(:build_download_code) } - context 'and user is a member of the project' do - before do - [private_project, internal_project, public_project, public_project_in_group].each do |project| - project.add_guest(security_policy_bot) + context 'and user is a member of the project' do + before do + [private_project, internal_project, public_project, public_project_in_group].each do |project| + project.add_guest(security_policy_bot) + end end - end - it { is_expected.to be_allowed(:build_download_code) } + it { is_expected.to be_allowed(:build_download_code) } + end end end end - end - describe 'create_bot_pipeline policy' do - context 'as a guest member' do - let(:current_user) { guest } + describe 'create_bot_pipeline policy' do + context 'as a guest member' do + let(:current_user) { guest } - it { is_expected.not_to be_allowed(:create_bot_pipeline) } + it { is_expected.not_to be_allowed(:create_bot_pipeline) } - context 'and user is a security_policy_bot' do - let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } - let(:current_user) { security_policy_bot } + context 'and user is a security_policy_bot' do + let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } + let(:current_user) { security_policy_bot } - it { is_expected.not_to be_allowed(:create_bot_pipeline) } + it { is_expected.not_to be_allowed(:create_bot_pipeline) } - context 'and user is a member of the project' do - shared_examples 'allows to create_bot_pipeline' do - let(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } + context 'and user is a member of the project' do + shared_examples 'allows to create_bot_pipeline' do + let(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } - before do - project.add_guest(security_policy_bot) - end + before do + project.add_guest(security_policy_bot) + end - it { is_expected.to be_allowed(:create_bot_pipeline) } - end + it { is_expected.to be_allowed(:create_bot_pipeline) } + end - context 'and the project is private' do - let(:project) { private_project } + context 'and the project is private' do + let(:project) { private_project } - it_behaves_like 'allows to create_bot_pipeline' - end + it_behaves_like 'allows to create_bot_pipeline' + end - context 'and the project is internal' do - let(:project) { internal_project } + context 'and the project is internal' do + let(:project) { internal_project } - it_behaves_like 'allows to create_bot_pipeline' - end + it_behaves_like 'allows to create_bot_pipeline' + end - context 'and the project is public' do - let(:project) { public_project } + context 'and the project is public' do + let(:project) { public_project } - it_behaves_like 'allows to create_bot_pipeline' - end + it_behaves_like 'allows to create_bot_pipeline' + end - context 'and the project is public in group' do - let(:project) { public_project_in_group } + context 'and the project is public in group' do + let(:project) { public_project_in_group } - it_behaves_like 'allows to create_bot_pipeline' + it_behaves_like 'allows to create_bot_pipeline' + end end end end end - end - describe 'security_policy_bot policy' do - let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } - let(:current_user) { security_policy_bot } + describe 'security_policy_bot policy' do + let_it_be(:security_policy_bot) { create(:user, user_type: :security_policy_bot) } + let(:current_user) { security_policy_bot } - context 'when user is authenticated via CI_JOB_TOKEN', :request_store do - let(:project) { public_project } - let(:job) { build_stubbed(:ci_build, project: scope_project, user: current_user) } - let(:scope_project) { project } + context 'when user is authenticated via CI_JOB_TOKEN', :request_store do + let(:project) { public_project } + let(:job) { build_stubbed(:ci_build, project: scope_project, user: current_user) } + let(:scope_project) { project } - let_it_be(:other_private_project) { create(:project, :private) } + let_it_be(:other_private_project) { create(:project, :private) } - before do - project.add_guest(security_policy_bot) - current_user.set_ci_job_token_scope!(job) - project.update!( - ci_outbound_job_token_scope_enabled: token_scope_enabled, - ci_inbound_job_token_scope_enabled: token_scope_enabled - ) - scope_project.update!( - ci_outbound_job_token_scope_enabled: token_scope_enabled, - ci_inbound_job_token_scope_enabled: token_scope_enabled - ) - end + before do + project.add_guest(security_policy_bot) + current_user.set_ci_job_token_scope!(job) + project.update!( + ci_outbound_job_token_scope_enabled: token_scope_enabled, + ci_inbound_job_token_scope_enabled: token_scope_enabled + ) + scope_project.update!( + ci_outbound_job_token_scope_enabled: token_scope_enabled, + ci_inbound_job_token_scope_enabled: token_scope_enabled + ) + end + + context 'when token scope is disabled' do + let(:token_scope_enabled) { false } + + context 'when pipeline is executed in project where bot is invited' do + it { is_expected.to be_allowed(:create_pipeline) } + it { is_expected.to be_allowed(:create_bot_pipeline) } + it { is_expected.to be_allowed(:build_download_code) } + end - context 'when token scope is disabled' do - let(:token_scope_enabled) { false } + context 'when pipeline is executed in project where bot is not invited' do + let(:scope_project) { other_private_project } - context 'when pipeline is executed in project where bot is invited' do - it { is_expected.to be_allowed(:create_pipeline) } - it { is_expected.to be_allowed(:create_bot_pipeline) } - it { is_expected.to be_allowed(:build_download_code) } + it { is_expected.to be_allowed(:create_pipeline) } + it { is_expected.to be_allowed(:create_bot_pipeline) } + it { is_expected.to be_allowed(:build_download_code) } + end end - context 'when pipeline is executed in project where bot is not invited' do - let(:scope_project) { other_private_project } + context 'when token scope is enabled' do + let(:token_scope_enabled) { true } + + context 'when pipeline is executed in project where bot is invited' do + it { is_expected.to be_allowed(:create_pipeline) } + it { is_expected.to be_allowed(:create_bot_pipeline) } + it { is_expected.to be_allowed(:build_download_code) } + end - it { is_expected.to be_allowed(:create_pipeline) } - it { is_expected.to be_allowed(:create_bot_pipeline) } - it { is_expected.to be_allowed(:build_download_code) } + context 'when pipeline is executed in project where bot is not invited' do + let(:scope_project) { other_private_project } + + it { is_expected.to be_disallowed(:create_pipeline) } + it { is_expected.to be_disallowed(:create_bot_pipeline) } + it { is_expected.to be_disallowed(:build_download_code) } + end end end - context 'when token scope is enabled' do - let(:token_scope_enabled) { true } + context 'when security policy bot is on the project' do + before do + project.add_guest(security_policy_bot) + end - context 'when pipeline is executed in project where bot is invited' do - it { is_expected.to be_allowed(:create_pipeline) } - it { is_expected.to be_allowed(:create_bot_pipeline) } - it { is_expected.to be_allowed(:build_download_code) } + context 'when security_dashboard is not enabled' do + it { is_expected.to be_disallowed(:create_vulnerability_state_transition) } end - context 'when pipeline is executed in project where bot is not invited' do - let(:scope_project) { other_private_project } + context 'when security_dashboard is enabled' do + before do + stub_licensed_features(security_dashboard: true) + end - it { is_expected.to be_disallowed(:create_pipeline) } - it { is_expected.to be_disallowed(:create_bot_pipeline) } - it { is_expected.to be_disallowed(:build_download_code) } + it { is_expected.to be_allowed(:create_vulnerability_state_transition) } end end end - context 'when security policy bot is on the project' do - before do - project.add_guest(security_policy_bot) - end + describe 'download_code_spp_repository policy' do + let(:current_user) { guest } - context 'when security_dashboard is not enabled' do - it { is_expected.to be_disallowed(:create_vulnerability_state_transition) } - end + it { is_expected.not_to be_allowed(:download_code_spp_repository) } - context 'when security_dashboard is enabled' do + context 'when project is a security policy project' do before do - stub_licensed_features(security_dashboard: true) + create(:security_orchestration_policy_configuration, security_policy_management_project: project) end - it { is_expected.to be_allowed(:create_vulnerability_state_transition) } - end - end - end + it { is_expected.not_to be_allowed(:download_code_spp_repository) } - describe 'download_code_spp_repository policy' do - let(:current_user) { guest } + context 'and project allows spp_repository_pipeline_access' do + before do + project.project_setting.update!(spp_repository_pipeline_access: true) + end - it { is_expected.not_to be_allowed(:download_code_spp_repository) } + context 'and the project is private' do + let(:project) { private_project } - context 'when project is a security policy project' do - before do - create(:security_orchestration_policy_configuration, security_policy_management_project: project) - end + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - it { is_expected.not_to be_allowed(:download_code_spp_repository) } + context 'and the project is internal' do + let(:project) { internal_project } - context 'and project allows spp_repository_pipeline_access' do - before do - project.project_setting.update!(spp_repository_pipeline_access: true) - end + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'and the project is private' do - let(:project) { private_project } + context 'and the project is public' do + let(:project) { public_project } - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'and the project is internal' do - let(:project) { internal_project } + context 'and the project is public in group' do + let(:project) { public_project_in_group } - it { is_expected.to be_allowed(:download_code_spp_repository) } + it { is_expected.to be_allowed(:download_code_spp_repository) } + end end - context 'and the project is public' do - let(:project) { public_project } + context 'and namespace allows spp_repository_pipeline_access' do + before do + project.group.namespace_settings.update!(spp_repository_pipeline_access: true) + end + + context 'and the project is private in group' do + let(:project) { private_project_in_group } - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'and the project is public in group' do - let(:project) { public_project_in_group } + context 'and the project is internal in group' do + let_it_be_with_refind(:internal_project_in_group) { create(:project, :internal, namespace: group) } + let(:project) { internal_project_in_group } - it { is_expected.to be_allowed(:download_code_spp_repository) } - end - end + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'and namespace allows spp_repository_pipeline_access' do - before do - project.group.namespace_settings.update!(spp_repository_pipeline_access: true) + context 'and the project is public in group' do + let(:project) { public_project_in_group } + + it { is_expected.to be_allowed(:download_code_spp_repository) } + end end - context 'and the project is private in group' do - let(:project) { private_project_in_group } + context 'and application setting allows spp_repository_pipeline_access' do + before do + stub_application_setting(spp_repository_pipeline_access: true) + end - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + context 'and the project is private' do + let(:project) { private_project } - context 'and the project is internal in group' do - let_it_be_with_refind(:internal_project_in_group) { create(:project, :internal, namespace: group) } - let(:project) { internal_project_in_group } + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + context 'and the project is internal' do + let(:project) { internal_project } + + it { is_expected.to be_allowed(:download_code_spp_repository) } + end + + context 'and the project is public' do + let(:project) { public_project } + + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'and the project is public in group' do - let(:project) { public_project_in_group } + context 'and the project is public in group' do + let(:project) { public_project_in_group } - it { is_expected.to be_allowed(:download_code_spp_repository) } + it { is_expected.to be_allowed(:download_code_spp_repository) } + end end end - context 'and application setting allows spp_repository_pipeline_access' do + context 'when user is authenticated via CI_JOB_TOKEN', :request_store do + let(:job) { build_stubbed(:ci_build, project: scope_project, user: current_user) } + let(:scope_project) { project } + + let_it_be(:other_private_project) { create(:project, :private) } + before do - stub_application_setting(spp_repository_pipeline_access: true) + current_user.set_ci_job_token_scope!(job) + create(:security_orchestration_policy_configuration, security_policy_management_project: project) + project.project_setting.update!(spp_repository_pipeline_access: true) + project.update!( + ci_outbound_job_token_scope_enabled: token_scope_enabled, + ci_inbound_job_token_scope_enabled: token_scope_enabled + ) + scope_project.update!( + ci_outbound_job_token_scope_enabled: token_scope_enabled, + ci_inbound_job_token_scope_enabled: token_scope_enabled + ) end - context 'and the project is private' do - let(:project) { private_project } + context 'when token scope is disabled' do + let(:token_scope_enabled) { false } - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + context 'when accessing from the same project' do + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'and the project is internal' do - let(:project) { internal_project } + context 'when accessing from other project' do + let(:scope_project) { other_private_project } - it { is_expected.to be_allowed(:download_code_spp_repository) } + it { is_expected.to be_allowed(:download_code_spp_repository) } + end end - context 'and the project is public' do - let(:project) { public_project } + context 'when token scope is enabled' do + let(:token_scope_enabled) { true } - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + context 'when accessing from the same project' do + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'and the project is public in group' do - let(:project) { public_project_in_group } + context 'when accessing from other project' do + let(:scope_project) { other_private_project } - it { is_expected.to be_allowed(:download_code_spp_repository) } + it { is_expected.to be_disallowed(:download_code_spp_repository) } + end end end end - context 'when user is authenticated via CI_JOB_TOKEN', :request_store do - let(:job) { build_stubbed(:ci_build, project: scope_project, user: current_user) } - let(:scope_project) { project } - - let_it_be(:other_private_project) { create(:project, :private) } + describe 'generate_description' do + let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } + let(:current_user) { guest } + let(:project) { private_project } before do - current_user.set_ci_job_token_scope!(job) - create(:security_orchestration_policy_configuration, security_policy_management_project: project) - project.project_setting.update!(spp_repository_pipeline_access: true) - project.update!( - ci_outbound_job_token_scope_enabled: token_scope_enabled, - ci_inbound_job_token_scope_enabled: token_scope_enabled - ) - scope_project.update!( - ci_outbound_job_token_scope_enabled: token_scope_enabled, - ci_inbound_job_token_scope_enabled: token_scope_enabled - ) + allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) end - context 'when token scope is disabled' do - let(:token_scope_enabled) { false } + context "when feature is authorized" do + before do + allow(authorizer).to receive(:allowed?).and_return(true) + end - context 'when accessing from the same project' do - it { is_expected.to be_allowed(:download_code_spp_repository) } + context 'when user can create issue' do + it { is_expected.to be_allowed(:generate_description) } end - context 'when accessing from other project' do - let(:scope_project) { other_private_project } + context 'when user cannot create issue' do + let(:current_user) { create(:user) } - it { is_expected.to be_allowed(:download_code_spp_repository) } + it { is_expected.to be_disallowed(:generate_description) } end end - context 'when token scope is enabled' do - let(:token_scope_enabled) { true } - - context 'when accessing from the same project' do - it { is_expected.to be_allowed(:download_code_spp_repository) } + context "when feature is not authorized" do + before do + allow(authorizer).to receive(:allowed?).and_return(false) end - context 'when accessing from other project' do - let(:scope_project) { other_private_project } - - it { is_expected.to be_disallowed(:download_code_spp_repository) } - end + it { is_expected.to be_disallowed(:generate_description) } end end - end - describe 'generate_description' do - let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } - let(:current_user) { guest } - let(:project) { private_project } + describe 'summarize_new_merge_request policy' do + let_it_be(:namespace) { group } + let_it_be(:project) { private_project } + let_it_be(:current_user) { maintainer } - before do - allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) - end + let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } - context "when feature is authorized" do before do - allow(authorizer).to receive(:allowed?).and_return(true) - end - - context 'when user can create issue' do - it { is_expected.to be_allowed(:generate_description) } - end - - context 'when user cannot create issue' do - let(:current_user) { create(:user) } - - it { is_expected.to be_disallowed(:generate_description) } + allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) + allow(project).to receive(:namespace).and_return(namespace) end - end - context "when feature is not authorized" do - before do - allow(authorizer).to receive(:allowed?).and_return(false) - end + context "when feature is authorized" do + before do + allow(authorizer).to receive(:allowed?).and_return(true) + end - it { is_expected.to be_disallowed(:generate_description) } - end - end + it { is_expected.to be_allowed(:summarize_new_merge_request) } - describe 'summarize_new_merge_request policy' do - let_it_be(:namespace) { group } - let_it_be(:project) { private_project } - let_it_be(:current_user) { maintainer } + context 'when add_ai_summary_for_new_mr feature flag is disabled' do + before do + stub_feature_flags(add_ai_summary_for_new_mr: false) + end - let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } + it { is_expected.to be_disallowed(:summarize_new_merge_request) } + end - before do - allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) - allow(project).to receive(:namespace).and_return(namespace) - end + context 'when user cannot create_merge_request_in' do + let(:current_user) { guest } - context "when feature is authorized" do - before do - allow(authorizer).to receive(:allowed?).and_return(true) + it { is_expected.to be_disallowed(:summarize_new_merge_request) } + end end - it { is_expected.to be_allowed(:summarize_new_merge_request) } - - context 'when add_ai_summary_for_new_mr feature flag is disabled' do + context "when feature is not authorized" do before do - stub_feature_flags(add_ai_summary_for_new_mr: false) + allow(authorizer).to receive(:allowed?).and_return(false) end it { is_expected.to be_disallowed(:summarize_new_merge_request) } end + end - context 'when user cannot create_merge_request_in' do - let(:current_user) { guest } + describe 'admin_target_branch_rule policy' do + let(:current_user) { owner } - it { is_expected.to be_disallowed(:summarize_new_merge_request) } - end - end + describe 'when the project does not have the correct license' do + before do + stub_licensed_features(target_branch_rules: false) + end - context "when feature is not authorized" do - before do - allow(authorizer).to receive(:allowed?).and_return(false) + it { is_expected.to be_disallowed(:admin_target_branch_rule) } end - it { is_expected.to be_disallowed(:summarize_new_merge_request) } - end - end - - describe 'admin_target_branch_rule policy' do - let(:current_user) { owner } + describe 'when the user does not have permissions' do + let(:current_user) { auditor } - describe 'when the project does not have the correct license' do - before do - stub_licensed_features(target_branch_rules: false) + it { is_expected.to be_disallowed(:admin_target_branch_rule) } end - it { is_expected.to be_disallowed(:admin_target_branch_rule) } + describe 'when the user has permission' do + before do + stub_licensed_features(target_branch_rules: true) + end + + it { is_expected.to be_allowed(:admin_target_branch_rule) } + end end - describe 'when the user does not have permissions' do - let(:current_user) { auditor } + describe 'read_target_branch_rule policy' do + let(:current_user) { owner } - it { is_expected.to be_disallowed(:admin_target_branch_rule) } - end + describe 'when the user has permission' do + before do + stub_licensed_features(target_branch_rules: true) + end - describe 'when the user has permission' do - before do - stub_licensed_features(target_branch_rules: true) + it { is_expected.to be_allowed(:read_target_branch_rule) } end - - it { is_expected.to be_allowed(:admin_target_branch_rule) } end - end - describe 'read_target_branch_rule policy' do - let(:current_user) { owner } + describe 'read_observability policy' do + let(:current_user) { reporter } - describe 'when the user has permission' do before do - stub_licensed_features(target_branch_rules: true) + stub_licensed_features(observability: true) end - it { is_expected.to be_allowed(:read_target_branch_rule) } - end - end + describe 'when observability_features is disabled' do + before do + stub_feature_flags(observability_features: false) + end - describe 'read_observability policy' do - let(:current_user) { reporter } + it { is_expected.to be_disallowed(:read_observability) } + end - before do - stub_licensed_features(observability: true) - end + describe 'when observability feature flag is enabled for root namespace' do + before do + stub_feature_flags(observability_features: project.root_namespace) + end - describe 'when observability_features is disabled' do - before do - stub_feature_flags(observability_features: false) + it { is_expected.to be_allowed(:read_observability) } end - it { is_expected.to be_disallowed(:read_observability) } - end + describe 'when the project does not have the correct license' do + before do + stub_feature_flags(observability_features: true) + stub_licensed_features(observability: false) + end - describe 'when observability feature flag is enabled for root namespace' do - before do - stub_feature_flags(observability_features: project.root_namespace) + it { is_expected.to be_disallowed(:read_observability) } end - it { is_expected.to be_allowed(:read_observability) } - end + describe 'when the user does not have permission' do + let(:current_user) { guest } - describe 'when the project does not have the correct license' do - before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: false) + before do + stub_feature_flags(observability_features: true) + stub_licensed_features(observability: true) + end + + it { is_expected.to be_disallowed(:read_observability) } end - it { is_expected.to be_disallowed(:read_observability) } + describe 'when the user has permission' do + before do + stub_feature_flags(observability_features: true) + stub_licensed_features(observability: true) + end + + it { is_expected.to be_allowed(:read_observability) } + end end - describe 'when the user does not have permission' do - let(:current_user) { guest } + describe 'write_observability policy' do + let(:current_user) { developer } before do - stub_feature_flags(observability_features: true) stub_licensed_features(observability: true) end - it { is_expected.to be_disallowed(:read_observability) } - end + describe 'when observability and observability_tracing feature flags are disabled' do + before do + stub_feature_flags(observability_features: false) + stub_feature_flags(observability_tracing: false) + end - describe 'when the user has permission' do - before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: true) + it { is_expected.to be_disallowed(:write_observability) } end - it { is_expected.to be_allowed(:read_observability) } - end - end + describe 'when observability feature flag is enabled for root namespace' do + before do + stub_feature_flags(observability_features: project.root_namespace) + end - describe 'write_observability policy' do - let(:current_user) { developer } + it { is_expected.to be_allowed(:write_observability) } + end - before do - stub_licensed_features(observability: true) - end + describe 'when observability_tracing feature flag is enabled for root namespace' do + before do + stub_feature_flags(observability_tracing: project.root_namespace) + end - describe 'when observability and observability_tracing feature flags are disabled' do - before do - stub_feature_flags(observability_features: false) - stub_feature_flags(observability_tracing: false) + it { is_expected.to be_allowed(:write_observability) } end - it { is_expected.to be_disallowed(:write_observability) } - end + describe 'when the project does not have the correct license' do + before do + stub_feature_flags(observability_features: true) + stub_licensed_features(observability: false) + end - describe 'when observability feature flag is enabled for root namespace' do - before do - stub_feature_flags(observability_features: project.root_namespace) + it { is_expected.to be_disallowed(:write_observability) } end - it { is_expected.to be_allowed(:write_observability) } - end + describe 'when the user does not have permission' do + let(:current_user) { reporter } - describe 'when observability_tracing feature flag is enabled for root namespace' do - before do - stub_feature_flags(observability_tracing: project.root_namespace) + before do + stub_feature_flags(observability_features: true) + stub_licensed_features(observability: true) + end + + it { is_expected.to be_disallowed(:write_observability) } end - it { is_expected.to be_allowed(:write_observability) } + describe 'when the user has permission' do + before do + stub_feature_flags(observability_features: true) + stub_licensed_features(observability: true) + end + + it { is_expected.to be_allowed(:write_observability) } + end end - describe 'when the project does not have the correct license' do + describe "#admin_vulnerability" do before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: false) + stub_licensed_features(security_dashboard: true) end - it { is_expected.to be_disallowed(:write_observability) } - end - - describe 'when the user does not have permission' do - let(:current_user) { reporter } - - before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: true) + let(:expected_permissions) do + [ + :admin_vulnerability, + :read_vulnerability, + :create_vulnerability_feedback, + :destroy_vulnerability_feedback, + :update_vulnerability_feedback, + :create_vulnerability_state_transition + ] end - it { is_expected.to be_disallowed(:write_observability) } - end + context "with guest" do + let(:current_user) { guest } - describe 'when the user has permission' do - before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: true) + it { is_expected.to be_disallowed(:admin_vulnerability) } end - it { is_expected.to be_allowed(:write_observability) } - end - end - - describe "#admin_vulnerability" do - before do - stub_licensed_features(security_dashboard: true) - end + context "with planner" do + let(:current_user) { planner } - let(:expected_permissions) do - [ - :admin_vulnerability, - :read_vulnerability, - :create_vulnerability_feedback, - :destroy_vulnerability_feedback, - :update_vulnerability_feedback, - :create_vulnerability_state_transition - ] - end + it { is_expected.to be_disallowed(:admin_vulnerability) } + end - context "with guest" do - let(:current_user) { guest } + context "with reporter" do + let(:current_user) { reporter } - it { is_expected.to be_disallowed(:admin_vulnerability) } - end + it { is_expected.to be_disallowed(:admin_vulnerability) } + end - context "with planner" do - let(:current_user) { planner } + context "with developer" do + let(:current_user) { developer } - it { is_expected.to be_disallowed(:admin_vulnerability) } - end + it { is_expected.to be_disallowed(:admin_vulnerability) } + end - context "with reporter" do - let(:current_user) { reporter } + context "with maintainer" do + let(:current_user) { maintainer } - it { is_expected.to be_disallowed(:admin_vulnerability) } - end + it { is_expected.to be_allowed(*expected_permissions) } + end - context "with developer" do - let(:current_user) { developer } + context "with owner" do + let(:current_user) { owner } - it { is_expected.to be_disallowed(:admin_vulnerability) } + it { is_expected.to be_allowed(*expected_permissions) } + end end - context "with maintainer" do - let(:current_user) { maintainer } + describe 'generate_cube_query policy' do + let(:current_user) { owner } + let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } - it { is_expected.to be_allowed(*expected_permissions) } - end + where(:ai_global_switch, :flag_enabled, :licensed, :allowed) do + true | true | true | true + true | true | false | false + true | false | true | false + true | false | false | false + false | true | true | false + false | true | false | false + false | false | true | false + false | false | false | false + end - context "with owner" do - let(:current_user) { owner } + with_them do + before do + stub_feature_flags(ai_global_switch: ai_global_switch) + stub_feature_flags(generate_cube_query: flag_enabled) + allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) + allow(authorizer).to receive(:allowed?).and_return(licensed) + end - it { is_expected.to be_allowed(*expected_permissions) } + it 'permits the correct abilities' do + if allowed + is_expected.to be_allowed(:generate_cube_query) + else + is_expected.to be_disallowed(:generate_cube_query) + end + end + end end - end - describe 'generate_cube_query policy' do - let(:current_user) { owner } - let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } - - where(:ai_global_switch, :flag_enabled, :licensed, :allowed) do - true | true | true | true - true | true | false | false - true | false | true | false - true | false | false | false - false | true | true | false - false | true | false | false - false | false | true | false - false | false | false | false - end + describe 'read_ai_agents' do + where(:feature_flag_enabled, :licensed_feature, :current_user, :allowed) do + true | true | ref(:owner) | true + true | true | ref(:reporter) | true + true | true | ref(:planner) | true + true | true | ref(:guest) | true + true | true | ref(:non_member) | false + true | false | ref(:owner) | false + true | false | ref(:reporter) | false + true | false | ref(:planner) | false + true | false | ref(:guest) | false + true | false | ref(:non_member) | false + false | true | ref(:owner) | false + false | true | ref(:reporter) | false + false | true | ref(:planner) | false + false | true | ref(:guest) | false + false | true | ref(:non_member) | false + false | false | ref(:owner) | false + false | false | ref(:reporter) | false + false | false | ref(:planner) | false + false | false | ref(:guest) | false + false | false | ref(:non_member) | false + end + with_them do + before do + stub_feature_flags(agent_registry: feature_flag_enabled) + stub_licensed_features(ai_agents: licensed_feature) + end - with_them do - before do - stub_feature_flags(ai_global_switch: ai_global_switch) - stub_feature_flags(generate_cube_query: flag_enabled) - allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) - allow(authorizer).to receive(:allowed?).and_return(licensed) + if params[:allowed] + it { expect_allowed(:read_ai_agents) } + else + it { expect_disallowed(:read_ai_agents) } + end + end + end + + describe 'write_ai_agents' do + where(:feature_flag_enabled, :licensed_feature, :current_user, :allowed) do + true | true | ref(:owner) | true + true | true | ref(:reporter) | true + true | true | ref(:planner) | false + true | true | ref(:guest) | false + true | true | ref(:non_member) | false + true | false | ref(:owner) | false + true | false | ref(:reporter) | false + true | false | ref(:planner) | false + true | false | ref(:guest) | false + true | false | ref(:non_member) | false + false | true | ref(:owner) | false + false | true | ref(:reporter) | false + false | true | ref(:planner) | false + false | true | ref(:guest) | false + false | true | ref(:non_member) | false + false | false | ref(:owner) | false + false | false | ref(:reporter) | false + false | false | ref(:planner) | false + false | false | ref(:guest) | false + false | false | ref(:non_member) | false end + with_them do + before do + stub_feature_flags(agent_registry: feature_flag_enabled) + stub_licensed_features(ai_agents: licensed_feature) + end - it 'permits the correct abilities' do - if allowed - is_expected.to be_allowed(:generate_cube_query) + if params[:allowed] + it { expect_allowed(:write_ai_agents) } else - is_expected.to be_disallowed(:generate_cube_query) + it { expect_disallowed(:write_ai_agents) } end end end - end - - describe 'read_ai_agents' do - where(:feature_flag_enabled, :licensed_feature, :current_user, :allowed) do - true | true | ref(:owner) | true - true | true | ref(:reporter) | true - true | true | ref(:planner) | true - true | true | ref(:guest) | true - true | true | ref(:non_member) | false - true | false | ref(:owner) | false - true | false | ref(:reporter) | false - true | false | ref(:planner) | false - true | false | ref(:guest) | false - true | false | ref(:non_member) | false - false | true | ref(:owner) | false - false | true | ref(:reporter) | false - false | true | ref(:planner) | false - false | true | ref(:guest) | false - false | true | ref(:non_member) | false - false | false | ref(:owner) | false - false | false | ref(:reporter) | false - false | false | ref(:planner) | false - false | false | ref(:guest) | false - false | false | ref(:non_member) | false - end - with_them do - before do - stub_feature_flags(agent_registry: feature_flag_enabled) - stub_licensed_features(ai_agents: licensed_feature) - end - if params[:allowed] - it { expect_allowed(:read_ai_agents) } - else - it { expect_disallowed(:read_ai_agents) } - end - end - end + describe 'access_duo_chat' do + let_it_be(:current_user) { create(:user) } + let(:project) { create(:project, :public, group: group) } - describe 'write_ai_agents' do - where(:feature_flag_enabled, :licensed_feature, :current_user, :allowed) do - true | true | ref(:owner) | true - true | true | ref(:reporter) | true - true | true | ref(:planner) | false - true | true | ref(:guest) | false - true | true | ref(:non_member) | false - true | false | ref(:owner) | false - true | false | ref(:reporter) | false - true | false | ref(:planner) | false - true | false | ref(:guest) | false - true | false | ref(:non_member) | false - false | true | ref(:owner) | false - false | true | ref(:reporter) | false - false | true | ref(:planner) | false - false | true | ref(:guest) | false - false | true | ref(:non_member) | false - false | false | ref(:owner) | false - false | false | ref(:reporter) | false - false | false | ref(:planner) | false - false | false | ref(:guest) | false - false | false | ref(:non_member) | false - end - with_them do - before do - stub_feature_flags(agent_registry: feature_flag_enabled) - stub_licensed_features(ai_agents: licensed_feature) - end + subject { described_class.new(current_user, project) } - if params[:allowed] - it { expect_allowed(:write_ai_agents) } - else - it { expect_disallowed(:write_ai_agents) } - end - end - end + context 'when on SaaS instance', :saas do + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } - describe 'access_duo_chat' do - let_it_be(:current_user) { create(:user) } - let(:project) { create(:project, :public, group: group) } + context 'when container is a group with AI enabled' do + include_context 'with duo features enabled and ai chat available for group on SaaS' - subject { described_class.new(current_user, project) } + context 'when user is a member of the group' do + before do + group.add_guest(current_user) + end - context 'when on SaaS instance', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } + it { is_expected.to be_allowed(:access_duo_chat) } - context 'when container is a group with AI enabled' do - include_context 'with duo features enabled and ai chat available for group on SaaS' + context 'when the group does not have an Premium SaaS license' do + let_it_be(:group) { create(:group) } - context 'when user is a member of the group' do - before do - group.add_guest(current_user) + it { is_expected.to be_disallowed(:access_duo_chat) } + end end - it { is_expected.to be_allowed(:access_duo_chat) } + context 'when user is not a member of the parent group' do + context 'when the user has AI enabled via another group' do + before do + allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) + end - context 'when the group does not have an Premium SaaS license' do - let_it_be(:group) { create(:group) } + context 'user can view project' do + it 'is allowed' do + is_expected.to be_allowed(:access_duo_chat) + end + end - it { is_expected.to be_disallowed(:access_duo_chat) } + context 'user cannot view project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'is not allowed' do + is_expected.to be_disallowed(:access_duo_chat) + end + end + end end - end - context 'when user is not a member of the parent group' do - context 'when the user has AI enabled via another group' do + context 'when user is a member of the project' do before do - allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) + project.add_guest(current_user) end - context 'user can view project' do + context 'when the user has AI enabled through parent group' do it 'is allowed' do + allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) + is_expected.to be_allowed(:access_duo_chat) end end + end + end + end - context 'user cannot view project' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end + context 'for self-managed', :with_cloud_connector do + let_it_be_with_reload(:group) { create(:group) } + let(:policy) { :access_duo_chat } - it 'is not allowed' do - is_expected.to be_disallowed(:access_duo_chat) - end - end - end + before do + project.add_guest(current_user) end - context 'when user is a member of the project' do - before do - project.add_guest(current_user) + context 'when not on .org or .com' do + where(:enabled_for_user, :duo_features_enabled, :duo_chat_matcher) do + true | false | be_disallowed(policy) + true | true | be_allowed(policy) + false | false | be_disallowed(policy) + false | true | be_disallowed(policy) end - context 'when the user has AI enabled through parent group' do - it 'is allowed' do - allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) - - is_expected.to be_allowed(:access_duo_chat) + with_them do + before do + allow(::Gitlab).to receive(:org_or_com?).and_return(false) + stub_ee_application_setting(duo_features_enabled: duo_features_enabled, lock_duo_features_enabled: true) + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :access_duo_chat).and_return(enabled_for_user) end + + it { is_expected.to duo_chat_matcher } end end end end - context 'for self-managed', :with_cloud_connector do - let_it_be_with_reload(:group) { create(:group) } - let(:policy) { :access_duo_chat } + context 'access_duo_features' do + let(:project) { private_project } - before do - project.add_guest(current_user) + where(:current_user, :duo_features_enabled, :cs_matcher) do + ref(:guest) | true | be_allowed(:access_duo_features) + ref(:guest) | false | be_disallowed(:access_duo_features) + nil | true | be_disallowed(:access_duo_features) + nil | false | be_disallowed(:access_duo_features) end - context 'when not on .org or .com' do - where(:enabled_for_user, :duo_features_enabled, :duo_chat_matcher) do - true | false | be_disallowed(policy) - true | true | be_allowed(policy) - false | false | be_disallowed(policy) - false | true | be_disallowed(policy) + with_them do + before do + project.update!(duo_features_enabled: duo_features_enabled) end - with_them do - before do - allow(::Gitlab).to receive(:org_or_com?).and_return(false) - stub_ee_application_setting(duo_features_enabled: duo_features_enabled, lock_duo_features_enabled: true) - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(current_user, :access_duo_chat).and_return(enabled_for_user) - end - - it { is_expected.to duo_chat_matcher } + it do + is_expected.to cs_matcher end end end - end - - context 'access_duo_features' do - let(:project) { private_project } - where(:current_user, :duo_features_enabled, :cs_matcher) do - ref(:guest) | true | be_allowed(:access_duo_features) - ref(:guest) | false | be_disallowed(:access_duo_features) - nil | true | be_disallowed(:access_duo_features) - nil | false | be_disallowed(:access_duo_features) - end + describe 'access to project for duo workflow' do + let_it_be_with_reload(:project) { public_project } - with_them do - before do - project.update!(duo_features_enabled: duo_features_enabled) + where(:current_user, :token_info, :duo_features_enabled, :cs_matcher) do + ref(:guest) | nil | true | be_allowed(:read_project) + ref(:guest) | { token_scopes: [:ai_workflows] } | true | be_allowed(:read_project) + ref(:guest) | { token_scopes: [:ai_workflows] } | false | be_disallowed(:read_project, :admin_project) + ref(:guest) | { token_scopes: [:other_scope] } | true | be_allowed(:read_project) + ref(:maintainer) | { token_scopes: [:ai_workflows] } | false | be_disallowed(:read_project, :admin_project) end - it do - is_expected.to cs_matcher - end - end - end - - describe 'access to project for duo workflow' do - let_it_be_with_reload(:project) { public_project } - - where(:current_user, :token_info, :duo_features_enabled, :cs_matcher) do - ref(:guest) | nil | true | be_allowed(:read_project) - ref(:guest) | { token_scopes: [:ai_workflows] } | true | be_allowed(:read_project) - ref(:guest) | { token_scopes: [:ai_workflows] } | false | be_disallowed(:read_project, :admin_project) - ref(:guest) | { token_scopes: [:other_scope] } | true | be_allowed(:read_project) - ref(:maintainer) | { token_scopes: [:ai_workflows] } | false | be_disallowed(:read_project, :admin_project) - end + with_them do + before do + project.update!(duo_features_enabled: duo_features_enabled) + ::Current.token_info = token_info + end - with_them do - before do - project.update!(duo_features_enabled: duo_features_enabled) - ::Current.token_info = token_info + it { is_expected.to cs_matcher } end - - it { is_expected.to cs_matcher } end - end - describe 'on_demand_scans_enabled policy' do - let(:current_user) { owner } - let(:permissions) { [:read_on_demand_dast_scan, :create_on_demand_dast_scan, :edit_on_demand_dast_scan] } + describe 'on_demand_scans_enabled policy' do + let(:current_user) { owner } + let(:permissions) { [:read_on_demand_dast_scan, :create_on_demand_dast_scan, :edit_on_demand_dast_scan] } - where(:feature_available, :allowed) do - false | false - true | true - end + where(:feature_available, :allowed) do + false | false + true | true + end - with_them do - context "when feature is #{params[:feature_available] ? 'available' : 'unavailable'}" do - before do - stub_licensed_features(security_on_demand_scans: feature_available) - end + with_them do + context "when feature is #{params[:feature_available] ? 'available' : 'unavailable'}" do + before do + stub_licensed_features(security_on_demand_scans: feature_available) + end - it "on demand scan permissions are #{params[:allowed] ? 'allowed' : 'disallowed'}" do - if allowed - expect_allowed(*permissions) - else - expect_disallowed(*permissions) + it "on demand scan permissions are #{params[:allowed] ? 'allowed' : 'disallowed'}" do + if allowed + expect_allowed(*permissions) + else + expect_disallowed(*permissions) + end end end end end - end - describe 'read_runner_cloud_provisioning_info policy' do - let(:current_user) { maintainer } + describe 'read_runner_cloud_provisioning_info policy' do + let(:current_user) { maintainer } - it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_info) } + it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_info) } - context 'when SaaS-only feature is available' do - before do - stub_saas_features(google_cloud_support: true) - end + context 'when SaaS-only feature is available' do + before do + stub_saas_features(google_cloud_support: true) + end - context 'the user is a maintainer' do - let(:current_user) { maintainer } + context 'the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:read_runner_cloud_provisioning_info) } - end + it { is_expected.to be_allowed(:read_runner_cloud_provisioning_info) } + end - context 'the user is a guest' do - let(:current_user) { guest } + context 'the user is a guest' do + let(:current_user) { guest } - it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_info) } + it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_info) } + end end end - end - describe 'read_runner_gke_provisioning_info policy' do - let(:current_user) { maintainer } + describe 'read_runner_gke_provisioning_info policy' do + let(:current_user) { maintainer } - it { is_expected.to be_disallowed(:read_runner_gke_provisioning_info) } + it { is_expected.to be_disallowed(:read_runner_gke_provisioning_info) } - context 'when SaaS-only feature is available' do - before do - stub_saas_features(google_cloud_support: true) - end + context 'when SaaS-only feature is available' do + before do + stub_saas_features(google_cloud_support: true) + end - context 'the user is a maintainer' do - let(:current_user) { maintainer } + context 'the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:read_runner_gke_provisioning_info) } - end + it { is_expected.to be_allowed(:read_runner_gke_provisioning_info) } + end - context 'the user is a guest' do - let(:current_user) { guest } + context 'the user is a guest' do + let(:current_user) { guest } - it { is_expected.to be_disallowed(:read_runner_gke_provisioning_info) } + it { is_expected.to be_disallowed(:read_runner_gke_provisioning_info) } + end end end - end - describe 'provision_cloud_runner policy' do - let(:current_user) { maintainer } + describe 'provision_cloud_runner policy' do + let(:current_user) { maintainer } - it { is_expected.to be_disallowed(:provision_cloud_runner) } + it { is_expected.to be_disallowed(:provision_cloud_runner) } - context 'when SaaS-only feature is available' do - before do - stub_saas_features(google_cloud_support: true) - end + context 'when SaaS-only feature is available' do + before do + stub_saas_features(google_cloud_support: true) + end - context 'the user is a maintainer' do - let(:current_user) { maintainer } + context 'the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:provision_cloud_runner) } - end + it { is_expected.to be_allowed(:provision_cloud_runner) } + end - context 'the user is a guest' do - let(:current_user) { guest } + context 'the user is a guest' do + let(:current_user) { guest } - it { is_expected.to be_disallowed(:provision_cloud_runner) } + it { is_expected.to be_disallowed(:provision_cloud_runner) } + end end end - end - describe 'provision_gke_runner policy' do - let(:current_user) { maintainer } + describe 'provision_gke_runner policy' do + let(:current_user) { maintainer } - it { is_expected.to be_disallowed(:provision_gke_runner) } + it { is_expected.to be_disallowed(:provision_gke_runner) } - context 'when SaaS-only feature is available' do - before do - stub_saas_features(google_cloud_support: true) - end + context 'when SaaS-only feature is available' do + before do + stub_saas_features(google_cloud_support: true) + end - context 'the user is a maintainer' do - let(:current_user) { maintainer } + context 'the user is a maintainer' do + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:provision_gke_runner) } - end + it { is_expected.to be_allowed(:provision_gke_runner) } + end - context 'the user is a guest' do - let(:current_user) { guest } + context 'the user is a guest' do + let(:current_user) { guest } - it { is_expected.to be_disallowed(:provision_gke_runner) } + it { is_expected.to be_disallowed(:provision_gke_runner) } + end end end - end - - describe 'read_google_cloud_artifact_registry' do - where(:saas_feature_enabled, :current_user, :match_expected_result) do - true | ref(:owner) | be_allowed(:read_google_cloud_artifact_registry) - true | ref(:reporter) | be_allowed(:read_google_cloud_artifact_registry) - true | ref(:planner) | be_disallowed(:read_google_cloud_artifact_registry) - true | ref(:guest) | be_disallowed(:read_google_cloud_artifact_registry) - true | ref(:non_member) | be_disallowed(:read_google_cloud_artifact_registry) - false | ref(:owner) | be_disallowed(:read_google_cloud_artifact_registry) - false | ref(:reporter) | be_disallowed(:read_google_cloud_artifact_registry) - false | ref(:planner) | be_disallowed(:read_google_cloud_artifact_registry) - false | ref(:guest) | be_disallowed(:read_google_cloud_artifact_registry) - false | ref(:non_member) | be_disallowed(:read_google_cloud_artifact_registry) - end - with_them do - before do - stub_saas_features(google_cloud_support: saas_feature_enabled) + describe 'read_google_cloud_artifact_registry' do + where(:saas_feature_enabled, :current_user, :match_expected_result) do + true | ref(:owner) | be_allowed(:read_google_cloud_artifact_registry) + true | ref(:reporter) | be_allowed(:read_google_cloud_artifact_registry) + true | ref(:planner) | be_disallowed(:read_google_cloud_artifact_registry) + true | ref(:guest) | be_disallowed(:read_google_cloud_artifact_registry) + true | ref(:non_member) | be_disallowed(:read_google_cloud_artifact_registry) + false | ref(:owner) | be_disallowed(:read_google_cloud_artifact_registry) + false | ref(:reporter) | be_disallowed(:read_google_cloud_artifact_registry) + false | ref(:planner) | be_disallowed(:read_google_cloud_artifact_registry) + false | ref(:guest) | be_disallowed(:read_google_cloud_artifact_registry) + false | ref(:non_member) | be_disallowed(:read_google_cloud_artifact_registry) end - it { is_expected.to match_expected_result } - end - end + with_them do + before do + stub_saas_features(google_cloud_support: saas_feature_enabled) + end - describe 'admin_google_cloud_artifact_registry' do - where(:saas_feature_enabled, :current_user, :match_expected_result) do - true | ref(:owner) | be_allowed(:admin_google_cloud_artifact_registry) - true | ref(:maintainer) | be_allowed(:admin_google_cloud_artifact_registry) - true | ref(:developer) | be_disallowed(:admin_google_cloud_artifact_registry) - true | ref(:non_member) | be_disallowed(:admin_google_cloud_artifact_registry) - false | ref(:owner) | be_disallowed(:admin_google_cloud_artifact_registry) - false | ref(:maintainer) | be_disallowed(:admin_google_cloud_artifact_registry) - false | ref(:developer) | be_disallowed(:admin_google_cloud_artifact_registry) - false | ref(:non_member) | be_disallowed(:admin_google_cloud_artifact_registry) + it { is_expected.to match_expected_result } + end end - with_them do - before do - stub_saas_features(google_cloud_support: saas_feature_enabled) + describe 'admin_google_cloud_artifact_registry' do + where(:saas_feature_enabled, :current_user, :match_expected_result) do + true | ref(:owner) | be_allowed(:admin_google_cloud_artifact_registry) + true | ref(:maintainer) | be_allowed(:admin_google_cloud_artifact_registry) + true | ref(:developer) | be_disallowed(:admin_google_cloud_artifact_registry) + true | ref(:non_member) | be_disallowed(:admin_google_cloud_artifact_registry) + false | ref(:owner) | be_disallowed(:admin_google_cloud_artifact_registry) + false | ref(:maintainer) | be_disallowed(:admin_google_cloud_artifact_registry) + false | ref(:developer) | be_disallowed(:admin_google_cloud_artifact_registry) + false | ref(:non_member) | be_disallowed(:admin_google_cloud_artifact_registry) end - it { is_expected.to match_expected_result } - end - end - - context 'saved replies permissions' do - let(:current_user) { owner } + with_them do + before do + stub_saas_features(google_cloud_support: saas_feature_enabled) + end - context 'when no license is present' do - before do - stub_licensed_features(project_saved_replies: false) + it { is_expected.to match_expected_result } end - - it { is_expected.to be_disallowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) } end - context 'with correct license' do - before do - stub_licensed_features(project_saved_replies: true) + context 'saved replies permissions' do + let(:current_user) { owner } + + context 'when no license is present' do + before do + stub_licensed_features(project_saved_replies: false) + end + + it { is_expected.to be_disallowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) } end - it { is_expected.to be_allowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) } + context 'with correct license' do + before do + stub_licensed_features(project_saved_replies: true) + end - context 'when the user is a guest' do - let(:current_user) { guest } + it { is_expected.to be_allowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) } - it { is_expected.to be_allowed(:read_saved_replies) } + context 'when the user is a guest' do + let(:current_user) { guest } - it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } - end + it { is_expected.to be_allowed(:read_saved_replies) } - context 'when the user is a reporter' do - let(:current_user) { reporter } + it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } + end - it { is_expected.to be_allowed(:read_saved_replies) } + context 'when the user is a reporter' do + let(:current_user) { reporter } - it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } - end + it { is_expected.to be_allowed(:read_saved_replies) } - context 'when the user is a developer' do - let(:current_user) { developer } + it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } + end - it { is_expected.to be_allowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) } - end + context 'when the user is a developer' do + let(:current_user) { developer } - context 'when the user is a guest member of the project' do - let(:current_user) { guest } + it { is_expected.to be_allowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) } + end - it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } - end - end - end + context 'when the user is a guest member of the project' do + let(:current_user) { guest } - describe 'enable_pre_receive_secret_detection' do - where(:current_user, :licensed, :match_expected_result) do - ref(:owner) | true | be_allowed(:enable_pre_receive_secret_detection) - ref(:maintainer) | true | be_allowed(:enable_pre_receive_secret_detection) - ref(:developer) | true | be_disallowed(:enable_pre_receive_secret_detection) - ref(:owner) | false | be_disallowed(:enable_pre_receive_secret_detection) - ref(:maintainer) | false | be_disallowed(:enable_pre_receive_secret_detection) - ref(:developer) | false | be_disallowed(:enable_pre_receive_secret_detection) + it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } + end + end end - with_them do - before do - stub_licensed_features(pre_receive_secret_detection: licensed) + describe 'enable_pre_receive_secret_detection' do + where(:current_user, :licensed, :match_expected_result) do + ref(:owner) | true | be_allowed(:enable_pre_receive_secret_detection) + ref(:maintainer) | true | be_allowed(:enable_pre_receive_secret_detection) + ref(:developer) | true | be_disallowed(:enable_pre_receive_secret_detection) + ref(:owner) | false | be_disallowed(:enable_pre_receive_secret_detection) + ref(:maintainer) | false | be_disallowed(:enable_pre_receive_secret_detection) + ref(:developer) | false | be_disallowed(:enable_pre_receive_secret_detection) end - it { is_expected.to match_expected_result } - end - - describe 'when the project does not have the correct license' do - let(:current_user) { owner } + with_them do + before do + stub_licensed_features(pre_receive_secret_detection: licensed) + end - it { is_expected.to be_disallowed(:enable_pre_receive_secret_detection) } - end - end + it { is_expected.to match_expected_result } + end - describe 'duo_workflow' do - let(:project) { public_project_in_group } - - where(:duo_workflow_feature_flag, :stage_check_available, :duo_features_enabled, :current_user, :match_expected_result) do - true | true | true | ref(:owner) | be_allowed(:duo_workflow) - true | true | true | ref(:maintainer) | be_allowed(:duo_workflow) - true | true | true | ref(:developer) | be_allowed(:duo_workflow) - true | true | true | ref(:planner) | be_disallowed(:duo_workflow) - true | true | true | ref(:guest) | be_disallowed(:duo_workflow) - true | true | true | ref(:non_member) | be_disallowed(:duo_workflow) - true | false | true | ref(:owner) | be_disallowed(:duo_workflow) - true | false | true | ref(:maintainer) | be_disallowed(:duo_workflow) - true | false | true | ref(:developer) | be_disallowed(:duo_workflow) - true | false | true | ref(:planner) | be_disallowed(:duo_workflow) - true | false | true | ref(:guest) | be_disallowed(:duo_workflow) - true | false | true | ref(:non_member) | be_disallowed(:duo_workflow) - false | true | true | ref(:owner) | be_disallowed(:duo_workflow) - false | true | true | ref(:maintainer) | be_disallowed(:duo_workflow) - false | true | true | ref(:developer) | be_disallowed(:duo_workflow) - false | true | true | ref(:planner) | be_disallowed(:duo_workflow) - false | true | true | ref(:guest) | be_disallowed(:duo_workflow) - false | true | true | ref(:non_member) | be_disallowed(:duo_workflow) - false | false | true | ref(:owner) | be_disallowed(:duo_workflow) - false | false | true | ref(:maintainer) | be_disallowed(:duo_workflow) - false | false | true | ref(:developer) | be_disallowed(:duo_workflow) - false | false | true | ref(:planner) | be_disallowed(:duo_workflow) - false | false | true | ref(:guest) | be_disallowed(:duo_workflow) - false | false | true | ref(:non_member) | be_disallowed(:duo_workflow) - false | false | false | ref(:owner) | be_disallowed(:duo_workflow) - false | false | false | ref(:maintainer) | be_disallowed(:duo_workflow) - false | false | false | ref(:developer) | be_disallowed(:duo_workflow) - false | false | false | ref(:planner) | be_disallowed(:duo_workflow) - false | false | false | ref(:guest) | be_disallowed(:duo_workflow) - false | false | false | ref(:non_member) | be_disallowed(:duo_workflow) - end + describe 'when the project does not have the correct license' do + let(:current_user) { owner } - with_them do - before do - stub_feature_flags(duo_workflow: duo_workflow_feature_flag) - allow(::Gitlab::Llm::StageCheck).to receive(:available?).with(project, :duo_workflow).and_return(stage_check_available) - stub_ee_application_setting(duo_features_enabled: duo_features_enabled) + it { is_expected.to be_disallowed(:enable_pre_receive_secret_detection) } end - - it { is_expected.to match_expected_result } end - end - describe 'enable_container_scanning_for_registry' do - where(:current_user, :match_expected_result) do - ref(:owner) | be_allowed(:enable_container_scanning_for_registry) - ref(:maintainer) | be_allowed(:enable_container_scanning_for_registry) - ref(:developer) | be_disallowed(:enable_container_scanning_for_registry) - ref(:non_member) | be_disallowed(:enable_container_scanning_for_registry) - end + describe 'duo_workflow' do + let(:project) { public_project_in_group } - with_them do - before do - stub_licensed_features(container_scanning_for_registry: true) + where(:duo_workflow_feature_flag, :stage_check_available, :duo_features_enabled, :current_user, :match_expected_result) do + true | true | true | ref(:owner) | be_allowed(:duo_workflow) + true | true | true | ref(:maintainer) | be_allowed(:duo_workflow) + true | true | true | ref(:developer) | be_allowed(:duo_workflow) + true | true | true | ref(:planner) | be_disallowed(:duo_workflow) + true | true | true | ref(:guest) | be_disallowed(:duo_workflow) + true | true | true | ref(:non_member) | be_disallowed(:duo_workflow) + true | false | true | ref(:owner) | be_disallowed(:duo_workflow) + true | false | true | ref(:maintainer) | be_disallowed(:duo_workflow) + true | false | true | ref(:developer) | be_disallowed(:duo_workflow) + true | false | true | ref(:planner) | be_disallowed(:duo_workflow) + true | false | true | ref(:guest) | be_disallowed(:duo_workflow) + true | false | true | ref(:non_member) | be_disallowed(:duo_workflow) + false | true | true | ref(:owner) | be_disallowed(:duo_workflow) + false | true | true | ref(:maintainer) | be_disallowed(:duo_workflow) + false | true | true | ref(:developer) | be_disallowed(:duo_workflow) + false | true | true | ref(:planner) | be_disallowed(:duo_workflow) + false | true | true | ref(:guest) | be_disallowed(:duo_workflow) + false | true | true | ref(:non_member) | be_disallowed(:duo_workflow) + false | false | true | ref(:owner) | be_disallowed(:duo_workflow) + false | false | true | ref(:maintainer) | be_disallowed(:duo_workflow) + false | false | true | ref(:developer) | be_disallowed(:duo_workflow) + false | false | true | ref(:planner) | be_disallowed(:duo_workflow) + false | false | true | ref(:guest) | be_disallowed(:duo_workflow) + false | false | true | ref(:non_member) | be_disallowed(:duo_workflow) + false | false | false | ref(:owner) | be_disallowed(:duo_workflow) + false | false | false | ref(:maintainer) | be_disallowed(:duo_workflow) + false | false | false | ref(:developer) | be_disallowed(:duo_workflow) + false | false | false | ref(:planner) | be_disallowed(:duo_workflow) + false | false | false | ref(:guest) | be_disallowed(:duo_workflow) + false | false | false | ref(:non_member) | be_disallowed(:duo_workflow) end - it { is_expected.to match_expected_result } + with_them do + before do + stub_feature_flags(duo_workflow: duo_workflow_feature_flag) + allow(::Gitlab::Llm::StageCheck).to receive(:available?).with(project, :duo_workflow).and_return(stage_check_available) + stub_ee_application_setting(duo_features_enabled: duo_features_enabled) + end + + it { is_expected.to match_expected_result } + end end - context 'when license feature is not available' do + describe 'enable_container_scanning_for_registry' do where(:current_user, :match_expected_result) do - ref(:owner) | be_disallowed(:enable_container_scanning_for_registry) - ref(:maintainer) | be_disallowed(:enable_container_scanning_for_registry) + ref(:owner) | be_allowed(:enable_container_scanning_for_registry) + ref(:maintainer) | be_allowed(:enable_container_scanning_for_registry) ref(:developer) | be_disallowed(:enable_container_scanning_for_registry) ref(:non_member) | be_disallowed(:enable_container_scanning_for_registry) end with_them do before do - stub_licensed_features(container_scanning_for_registry: false) + stub_licensed_features(container_scanning_for_registry: true) end it { is_expected.to match_expected_result } end - end - end - describe 'read_pre_receive_secret_detection_info' do - where(:current_user, :match_expected_result) do - ref(:owner) | be_allowed(:read_pre_receive_secret_detection_info) - ref(:maintainer) | be_allowed(:read_pre_receive_secret_detection_info) - ref(:developer) | be_allowed(:read_pre_receive_secret_detection_info) - ref(:planner) | be_disallowed(:read_pre_receive_secret_detection_info) - ref(:guest) | be_disallowed(:read_pre_receive_secret_detection_info) - ref(:non_member) | be_disallowed(:read_pre_receive_secret_detection_info) + context 'when license feature is not available' do + where(:current_user, :match_expected_result) do + ref(:owner) | be_disallowed(:enable_container_scanning_for_registry) + ref(:maintainer) | be_disallowed(:enable_container_scanning_for_registry) + ref(:developer) | be_disallowed(:enable_container_scanning_for_registry) + ref(:non_member) | be_disallowed(:enable_container_scanning_for_registry) + end + + with_them do + before do + stub_licensed_features(container_scanning_for_registry: false) + end + + it { is_expected.to match_expected_result } + end + end end - with_them do - before do - stub_licensed_features(pre_receive_secret_detection: true) + describe 'read_pre_receive_secret_detection_info' do + where(:current_user, :match_expected_result) do + ref(:owner) | be_allowed(:read_pre_receive_secret_detection_info) + ref(:maintainer) | be_allowed(:read_pre_receive_secret_detection_info) + ref(:developer) | be_allowed(:read_pre_receive_secret_detection_info) + ref(:planner) | be_disallowed(:read_pre_receive_secret_detection_info) + ref(:guest) | be_disallowed(:read_pre_receive_secret_detection_info) + ref(:non_member) | be_disallowed(:read_pre_receive_secret_detection_info) end - it { is_expected.to match_expected_result } - end - end + with_them do + before do + stub_licensed_features(pre_receive_secret_detection: true) + end - describe 'admin_project_secrets_manager' do - where(:current_user, :match_expected_result) do - ref(:owner) | be_allowed(:admin_project_secrets_manager) - ref(:maintainer) | be_disallowed(:admin_project_secrets_manager) - ref(:developer) | be_disallowed(:admin_project_secrets_manager) - ref(:non_member) | be_disallowed(:admin_project_secrets_manager) + it { is_expected.to match_expected_result } + end end - with_them do - it { is_expected.to match_expected_result } - end - end + describe 'admin_project_secrets_manager' do + where(:current_user, :match_expected_result) do + ref(:owner) | be_allowed(:admin_project_secrets_manager) + ref(:maintainer) | be_disallowed(:admin_project_secrets_manager) + ref(:developer) | be_disallowed(:admin_project_secrets_manager) + ref(:non_member) | be_disallowed(:admin_project_secrets_manager) + end - describe 'manage_project_security_exclusions' do - let(:policy) { :manage_project_security_exclusions } - - where(:role, :allowed) do - :guest | false - :planner | false - :reporter | false - :developer | false - :maintainer | true - :auditor | false - :owner | true - :admin | true + with_them do + it { is_expected.to match_expected_result } + end end - with_them do - let(:current_user) { public_send(role) } + describe 'manage_project_security_exclusions' do + let(:policy) { :manage_project_security_exclusions } - before do - enable_admin_mode!(current_user) if role == :admin + where(:role, :allowed) do + :guest | false + :planner | false + :reporter | false + :developer | false + :maintainer | true + :auditor | false + :owner | true + :admin | true end - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - end - end + with_them do + let(:current_user) { public_send(role) } + + before do + enable_admin_mode!(current_user) if role == :admin + end - describe 'read_project_security_exclusions' do - let(:policy) { :read_project_security_exclusions } - - where(:role, :allowed) do - :guest | false - :planner | false - :reporter | false - :developer | true - :maintainer | true - :auditor | true - :owner | true - :admin | true + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + end end - with_them do - let(:current_user) { public_send(role) } + describe 'read_project_security_exclusions' do + let(:policy) { :read_project_security_exclusions } - before do - enable_admin_mode!(current_user) if role == :admin + where(:role, :allowed) do + :guest | false + :planner | false + :reporter | false + :developer | true + :maintainer | true + :auditor | true + :owner | true + :admin | true end - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - end - end + with_them do + let(:current_user) { public_send(role) } - describe 'manage_security_settings' do - let(:policy) { :manage_security_settings } - - where(:role, :allowed) do - :guest | false - :reporter | false - :developer | false - :maintainer | true - :auditor | false - :owner | true - :admin | true + before do + enable_admin_mode!(current_user) if role == :admin + end + + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + end end - with_them do - let(:current_user) { public_send(role) } + describe 'manage_security_settings' do + let(:policy) { :manage_security_settings } - before do - enable_admin_mode!(current_user) if role == :admin + where(:role, :allowed) do + :guest | false + :reporter | false + :developer | false + :maintainer | true + :auditor | false + :owner | true + :admin | true end - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - end - end - - describe 'read_security_settings' do - let(:policy) { :read_security_settings } - - where(:role, :allowed) do - :guest | false - :reporter | false - :developer | true - :maintainer | true - :auditor | true - :owner | true - :admin | true - end + with_them do + let(:current_user) { public_send(role) } - with_them do - let(:current_user) { public_send(role) } + before do + enable_admin_mode!(current_user) if role == :admin + end - before do - enable_admin_mode!(current_user) if role == :admin + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end - - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end - end - describe 'access_security_scans_api' do - context 'when feature is disabled' do - let(:current_user) { owner } + describe 'read_security_settings' do + let(:policy) { :read_security_settings } - before do - stub_licensed_features(security_scans_api: false) + where(:role, :allowed) do + :guest | false + :reporter | false + :developer | true + :maintainer | true + :auditor | true + :owner | true + :admin | true end - it 'is not allowed' do - is_expected.to be_disallowed(:access_security_scans_api) + with_them do + let(:current_user) { public_send(role) } + + before do + enable_admin_mode!(current_user) if role == :admin + end + + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end - context 'when feature is enabled' do - where(:free_access, :current_user, :allowed) do - true | ref(:owner) | true - true | ref(:maintainer) | true - true | ref(:developer) | true - true | ref(:guest) | false - true | ref(:planner) | false - true | ref(:reporter) | false - true | ref(:non_member) | false - false | ref(:owner) | false - false | ref(:maintainer) | false - false | ref(:developer) | false - false | ref(:guest) | false - false | ref(:planner) | false - false | ref(:reporter) | false - false | ref(:non_member) | false - end + describe 'access_security_scans_api' do + context 'when feature is disabled' do + let(:current_user) { owner } - with_them do before do - stub_licensed_features(security_scans_api: true) - allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:sast).and_return( - instance_double(CloudConnector::BaseAvailableServiceData, free_access?: free_access)) + stub_licensed_features(security_scans_api: false) end - it { is_expected.to(allowed ? be_allowed(:access_security_scans_api) : be_disallowed(:access_security_scans_api)) } + it 'is not allowed' do + is_expected.to be_disallowed(:access_security_scans_api) + end end - end - end - describe 'access_ai_review_mr' do - let(:current_user) { owner } + context 'when feature is enabled' do + where(:free_access, :current_user, :allowed) do + true | ref(:owner) | true + true | ref(:maintainer) | true + true | ref(:developer) | true + true | ref(:guest) | false + true | ref(:planner) | false + true | ref(:reporter) | false + true | ref(:non_member) | false + false | ref(:owner) | false + false | ref(:maintainer) | false + false | ref(:developer) | false + false | ref(:guest) | false + false | ref(:planner) | false + false | ref(:reporter) | false + false | ref(:non_member) | false + end - where(:duo_features_enabled, :allowed_to_use, :enabled_for_user) do - true | false | be_disallowed(:access_ai_review_mr) - false | true | be_disallowed(:access_ai_review_mr) - true | true | be_allowed(:access_ai_review_mr) + with_them do + before do + stub_licensed_features(security_scans_api: true) + allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:sast).and_return( + instance_double(CloudConnector::BaseAvailableServiceData, free_access?: free_access)) + end + + it { is_expected.to(allowed ? be_allowed(:access_security_scans_api) : be_disallowed(:access_security_scans_api)) } + end + end end - with_them do - before do - allow(project).to receive(:duo_features_enabled).and_return(duo_features_enabled) + describe 'access_ai_review_mr' do + let(:current_user) { owner } - allow(current_user).to receive(:allowed_to_use?) - .with(:review_merge_request, licensed_feature: :ai_review_mr).and_return(allowed_to_use) + where(:duo_features_enabled, :allowed_to_use, :enabled_for_user) do + true | false | be_disallowed(:access_ai_review_mr) + false | true | be_disallowed(:access_ai_review_mr) + true | true | be_allowed(:access_ai_review_mr) end - it { is_expected.to enabled_for_user } + with_them do + before do + allow(project).to receive(:duo_features_enabled).and_return(duo_features_enabled) + + allow(current_user).to receive(:allowed_to_use?) + .with(:review_merge_request, licensed_feature: :ai_review_mr).and_return(allowed_to_use) + end + + it { is_expected.to enabled_for_user } + end end end end diff --git a/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9172925f6f8e1da477570912b5c17970d9c6ec88 --- /dev/null +++ b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User with admin_security_testing custom role', feature_category: :security_policy_management do + let_it_be(:project) { create(:project, :in_group) } + let_it_be(:group) { project.group } + let_it_be(:user) { create(:user) } + + let_it_be(:role) { create(:member_role, :guest, :admin_security_testing, namespace: group) } + let_it_be(:membership) { create(:group_member, :guest, member_role: role, user: user, source: group) } + + before do + stub_licensed_features( + custom_roles: true, + security_dashboard: true, + security_on_demand_scans: true, + security_scans_api: true, + pre_receive_secret_detection: true, + container_scanning_for_registry: true, + coverage_fuzzing: true) + stub_feature_flags(custom_ability_admin_security_testing: true) + sign_in(user) + end + + describe "Controllers endpoints" do + describe Projects::Security::ApiFuzzingConfigurationController do + it 'can access the show endpoint' do + get project_security_configuration_api_fuzzing_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Projects::Security::DastConfigurationController do + it 'can access the show endpoint' do + get project_security_configuration_dast_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Projects::Security::SecretDetectionConfigurationController do + it 'can access the show endpoint' do + get project_security_configuration_secret_detection_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Projects::Security::SastConfigurationController do + it 'can access the show endpoint' do + get project_security_configuration_sast_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Projects::Security::CorpusManagementController do + it 'can access the show endpoint' do + get project_security_configuration_corpus_management_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Projects::Security::DastProfilesController do + it 'can access the show endpoint' do + get project_security_configuration_profile_library_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Projects::Security::DastScannerProfilesController do + it 'can access the show endpoint' do + get new_project_security_configuration_profile_library_dast_scanner_profile_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Projects::Security::DastSiteProfilesController do + it 'can access the show endpoint' do + get new_project_security_configuration_profile_library_dast_site_profile_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + describe 'GraphQL mutations' do + include GraphqlHelpers + + let(:full_path) { project.full_path } + + let(:fields) do + <<~FIELDS + errors + FIELDS + end + + let(:mutation_name) { nil } + let(:mutation) { graphql_mutation(mutation_name, input, fields) } + + subject(:execute_mutation) { post_graphql_mutation(mutation, current_user: user) } + + describe Mutations::Security::CiConfiguration::ConfigureContainerScanning do + let(:mutation_name) { :configureContainerScanning } + let(:input) { { project_path: full_path } } + + it 'has access via a custom role' do + execute_mutation + expect_graphql_errors_to_be_empty + end + end + + describe Mutations::Security::CiConfiguration::ConfigureDependencyScanning do + let(:mutation_name) { :configureDependencyScanning } + let(:input) { { project_path: full_path } } + + it 'has access via a custom role' do + execute_mutation + expect_graphql_errors_to_be_empty + end + end + + describe Mutations::Security::CiConfiguration::SetContainerScanningForRegistry do + let(:mutation_name) { :setContainerScanningForRegistry } + let(:input) { { namespace_path: full_path, enable: true } } + + it 'has access via a custom role' do + execute_mutation + expect_graphql_errors_to_be_empty + end + end + + describe Mutations::Security::CiConfiguration::SetGroupSecretPushProtection do + let(:mutation_name) { :setGroupSecretPushProtection } + let(:input) { { namespace_path: group.full_path, secret_push_protection_enabled: true } } + + it 'has access via a custom role' do + execute_mutation + expect_graphql_errors_to_be_empty + end + end + + describe Mutations::Security::CiConfiguration::SetPreReceiveSecretDetection do + let(:mutation_name) { :setPreReceiveSecretDetection } + let(:input) { { namespace_path: full_path, enable: true } } + + it 'has access via a custom role' do + execute_mutation + expect_graphql_errors_to_be_empty + end + end + end +end