From 5ead97ef24376f2dca4ddf5b48df19c7e2fb9bc0 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Sun, 29 Dec 2024 17:34:23 +0200 Subject: [PATCH 01/26] Add the new permission for admin security testing --- app/policies/project_policy.rb | 20 +++++++++++++++++++ .../json_schemas/member_role_permissions.json | 3 +++ doc/api/openapi/openapi_v2.yaml | 2 ++ .../security/security_testing.rb | 14 +++++++++++++ ee/app/policies/ee/group_policy.rb | 20 +++++++++++++++++++ .../admin_security_testing.yml | 12 +++++++++++ 6 files changed, 71 insertions(+) create mode 100644 ee/app/graphql/types/permission_types/security/security_testing.rb create mode 100644 ee/config/custom_abilities/admin_security_testing.yml diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index fa1c6afc1c4b02..da2d16e67a59a0 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -1028,6 +1028,26 @@ class ProjectPolicy < BasePolicy enable :access_security_and_compliance 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 + enable :access_security_and_compliance + end + + rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do + enable :read_security_configuration + enable :modify_security_policy + enable :admin_vulnerability + end + + rule { security_orchestration_policies_enabled & can?(:admin_security_testing) }.policy do + enable :read_security_orchestration_policies + enable :update_security_orchestration_policy_project + end + rule { ~admin & ~organization_owner & ~project_runner_registration_allowed }.policy do prevent :register_project_runners prevent :create_runner diff --git a/app/validators/json_schemas/member_role_permissions.json b/app/validators/json_schemas/member_role_permissions.json index 92efa0f0f5583f..042ab9ee5b4dc6 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/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index 298ebcd267cca5..9b59fb806bf2b4 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/graphql/types/permission_types/security/security_testing.rb b/ee/app/graphql/types/permission_types/security/security_testing.rb new file mode 100644 index 00000000000000..d08cc1fb8c140c --- /dev/null +++ b/ee/app/graphql/types/permission_types/security/security_testing.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + module PermissionTypes + module Security + class SecurityTesting < BasePermissionType + graphql_name 'SecurityTestingPermissions' + description 'Check permissions for the current user on security testing features' + + abilities :admin_security_testing + end + end + end +end diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 0f82bbee4923a8..b18f37b8868af8 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -574,6 +574,26 @@ module GroupPolicy enable :admin_vulnerability 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 + enable :access_security_and_compliance + end + + rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do + enable :read_security_configuration + enable :modify_security_policy + enable :admin_vulnerability + end + + rule { security_orchestration_policies_enabled & can?(:admin_security_testing) }.policy do + enable :read_security_orchestration_policies + enable :update_security_orchestration_policy_project + end + rule { custom_role_enables_admin_group_member }.policy do enable :admin_group_member enable :update_group_member 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 00000000000000..0c11aecd0efa25 --- /dev/null +++ b/ee/config/custom_abilities/admin_security_testing.yml @@ -0,0 +1,12 @@ +--- +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: YOUR_GITLAB_MR_URL +feature_category: security_policy_management +milestone: "17.8" +group_ability: true +project_ability: true +requirements: [] +available_from_access_level: 40 -- GitLab From 9c69a803f8fdd325fa9112295b4835ea6ac0eb69 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Sun, 29 Dec 2024 17:39:23 +0200 Subject: [PATCH 02/26] Add admin security testing permission to roles documentation --- doc/api/graphql/reference/index.md | 2 ++ doc/user/custom_roles/abilities.md | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 2ed172aee518b5..d36518747a07bd 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/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index 58fd6de1cd29b2..2b69d073636e07 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,7 +80,8 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Link to a security policy project | Allows linking security policy projects. | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | +| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`](YOUR_GITLAB_MR_URL) | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | +| [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab From dc6b6eb9f61c7156271d1a965ae324031c2f63c1 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Sun, 29 Dec 2024 17:43:22 +0200 Subject: [PATCH 03/26] Remove introduced_by_mr --- ee/config/custom_abilities/admin_security_testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/config/custom_abilities/admin_security_testing.yml b/ee/config/custom_abilities/admin_security_testing.yml index 0c11aecd0efa25..751bcb9ea38bd9 100644 --- a/ee/config/custom_abilities/admin_security_testing.yml +++ b/ee/config/custom_abilities/admin_security_testing.yml @@ -3,7 +3,7 @@ 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: YOUR_GITLAB_MR_URL +introduced_by_mr: feature_category: security_policy_management milestone: "17.8" group_ability: true -- GitLab From 2f8745be626de21a9fb5e134f8929e56c13d73e7 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Sun, 29 Dec 2024 17:46:01 +0200 Subject: [PATCH 04/26] Remove mr link from docs --- doc/user/custom_roles/abilities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index 2b69d073636e07..1d56f40060609e 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,7 +80,7 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`](YOUR_GITLAB_MR_URL) | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | +| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`]() | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab From d42e78f7beea5cf9b2132afda048f91d913b352c Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Sun, 29 Dec 2024 17:48:58 +0200 Subject: [PATCH 05/26] Set placeholder for the mr url --- doc/user/custom_roles/abilities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index 1d56f40060609e..250fb85c0e83f0 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,7 +80,7 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`]() | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | +| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162208) | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab From 037ba1ac1b47a9d9a105760f2ed374b470c382df Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Sun, 29 Dec 2024 17:52:44 +0200 Subject: [PATCH 06/26] Fix the mr link --- doc/user/custom_roles/abilities.md | 2 +- ee/config/custom_abilities/admin_security_testing.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index 250fb85c0e83f0..b3121793c5bd4e 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,7 +80,7 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162208) | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | +| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management diff --git a/ee/config/custom_abilities/admin_security_testing.yml b/ee/config/custom_abilities/admin_security_testing.yml index 751bcb9ea38bd9..acdd1adbd3e6f8 100644 --- a/ee/config/custom_abilities/admin_security_testing.yml +++ b/ee/config/custom_abilities/admin_security_testing.yml @@ -3,7 +3,7 @@ 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: +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628 feature_category: security_policy_management milestone: "17.8" group_ability: true -- GitLab From a9ec9e6c0f734ce1f370221ca488f15547a9e583 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Thu, 9 Jan 2025 14:33:49 +0200 Subject: [PATCH 07/26] Fix the new permission under project and group policy and add unit tests --- .../security/configuration_presenter.rb | 2 +- ee/app/policies/ee/group_policy.rb | 17 +---- ee/app/policies/ee/project_policy.rb | 31 +++++++- ee/spec/policies/group_policy_spec.rb | 7 ++ ee/spec/policies/project_policy_spec.rb | 74 +++++++++++++++++++ 5 files changed, 113 insertions(+), 18 deletions(-) diff --git a/app/presenters/projects/security/configuration_presenter.rb b/app/presenters/projects/security/configuration_presenter.rb index cb368b804a12d7..2a31b60ceafd83 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_project, self) || can?(current_user, :admin_security_testing, self) end def gitlab_ci_history_path diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index b18f37b8868af8..bff2a25a33a2c0 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -560,6 +560,7 @@ module GroupPolicy rule { security_dashboard_enabled & can?(:maintainer_access) }.policy do enable :admin_vulnerability + enable :admin_security_testing end rule { custom_role_enables_read_dependency }.policy do @@ -574,24 +575,8 @@ module GroupPolicy enable :admin_vulnerability 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 - enable :access_security_and_compliance - end - - rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do - enable :read_security_configuration - enable :modify_security_policy - enable :admin_vulnerability - end - - rule { security_orchestration_policies_enabled & can?(:admin_security_testing) }.policy do - enable :read_security_orchestration_policies - enable :update_security_orchestration_policy_project end rule { custom_role_enables_admin_group_member }.policy do diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 298ef2ca8956b0..18aa9a60ff36ea 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -456,6 +456,35 @@ 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 :access_security_scans_api + enable :read_on_demand_dast_scan + enable :create_on_demand_dast_scan + enable :edit_on_demand_dast_scan + enable :enable_pre_receive_secret_detection + enable :read_project_security_dashboard + enable :read_project_security_exclusions + enable :read_coverage_fuzzing + enable :create_coverage_fuzzing_corpus + enable :enable_container_scanning_for_registry + enable :push_code + enable :create_merge_request_from + enable :create_pipeline + enable :build_download_code + enable :read_merge_request + enable :download_code + enable :read_pre_receive_secret_detection_info + end + rule { security_dashboard_enabled & security_policy_bot }.policy do enable :create_vulnerability_state_transition end @@ -1010,7 +1039,7 @@ module ProjectPolicy condition(:container_scanning_for_registry_available) do @subject.licensed_feature_available?(:container_scanning_for_registry) end - rule { container_scanning_for_registry_available & can?(:maintainer_access) }.policy do + rule { (container_scanning_for_registry_available & can?(:maintainer_access)) }.policy do enable :enable_container_scanning_for_registry end diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index a72f75a73559ab..f7c9eef1950519 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -3778,6 +3778,13 @@ def create_member_role(member, abilities = member_role_abilities) end end + context 'for a member role with admin_security_testing true' do + let(:member_role_abilities) { { admin_security_testing: true } } + let(:allowed_abilities) { [:admin_security_testing] } + + it_behaves_like 'custom roles abilities' + end + context 'for a member role with read_vulnerability true' do let(:member_role_abilities) { { read_vulnerability: true } } let(:allowed_abilities) { [:read_group_security_dashboard] } diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 3dceb8e71dae3f..6216a30654960a 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -724,6 +724,80 @@ it_behaves_like 'correct access to security and compliance' end + + context 'when the user as admin_security_testing permission' do + let(:current_user) { guest } + let_it_be(:project) { create(:project, :in_group) } + + let(:security_permissions) do + %i[ + access_security_and_compliance + 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_pre_receive_secret_detection_info + ] + end + + def check_base_permissions(policy) + security_permissions.select do |permission| + policy.allowed?(permission) + end + end + + # Define rules for different user roles and expected permissions + where(:role, :admin_security_testing_enabled) do + :guest | false + :guest | true + :developer | false + :developer | true + :maintainer | false + :maintainer | true + end + + with_them do + let(:test_user) { create(:user) } + + before do + stub_licensed_features(custom_roles: true, + security_on_demand_scans: true, + security_scans_api: true, + pre_receive_secret_detection: true, + container_scanning_for_registry: true, + security_dashboard: true) + allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:sast).and_return( + instance_double(CloudConnector::BaseAvailableServiceData, free_access?: true)) + project_member = create(:project_member, role, user: test_user, source: project) + create(:member_role, role, admin_security_testing: admin_security_testing_enabled, members: [project_member], namespace: project.group) + end + + it 'has expected permissions' do + policy = described_class.new(test_user, project) + base_permissions = check_base_permissions(policy) + + security_permissions.each do |permission| + if admin_security_testing_enabled || base_permissions.include?(permission) + expect(policy).to be_allowed(permission) + else + expect(policy).to be_disallowed(permission) + end + end + end + end + end end describe 'vulnerability feedback permissions' do -- GitLab From c51ec5a9cb1314fb37c172755bf28c2a7e4bf86b Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Thu, 9 Jan 2025 15:18:51 +0200 Subject: [PATCH 08/26] Remove custom role from the app project policy and fix the test --- app/policies/project_policy.rb | 20 -------------------- ee/app/policies/ee/project_policy.rb | 1 + ee/spec/policies/project_policy_spec.rb | 1 + 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index da2d16e67a59a0..fa1c6afc1c4b02 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -1028,26 +1028,6 @@ class ProjectPolicy < BasePolicy enable :access_security_and_compliance 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 - enable :access_security_and_compliance - end - - rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do - enable :read_security_configuration - enable :modify_security_policy - enable :admin_vulnerability - end - - rule { security_orchestration_policies_enabled & can?(:admin_security_testing) }.policy do - enable :read_security_orchestration_policies - enable :update_security_orchestration_policy_project - end - rule { ~admin & ~organization_owner & ~project_runner_registration_allowed }.policy do prevent :register_project_runners prevent :create_runner diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 18aa9a60ff36ea..519e849bb4700a 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -466,6 +466,7 @@ module ProjectPolicy rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do enable :access_security_and_compliance + enable :read_security_configuration enable :access_security_scans_api enable :read_on_demand_dast_scan enable :create_on_demand_dast_scan diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 6216a30654960a..d544eb6ada2fcc 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -732,6 +732,7 @@ let(:security_permissions) do %i[ access_security_and_compliance + read_security_configuration access_security_scans_api read_on_demand_dast_scan create_on_demand_dast_scan -- GitLab From 9df2638520f90b0cabcdf3fc55d7777f0b311c0c Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Thu, 9 Jan 2025 15:38:48 +0200 Subject: [PATCH 09/26] Add the new permission to docs EE: true Changelog: added --- doc/api/member_roles.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index 4c768b013bb4ed..057343fdd0ce6f 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -25,6 +25,7 @@ DETAILS: > - [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. +> - [Admin security testing introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) in GitLab 17.8. Use this API to interact with member roles for your GitLab.com groups or entire self-managed instance. -- GitLab From 21f81bbff135873b3241817a84470f2e63721ab8 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Mon, 13 Jan 2025 20:39:03 +0200 Subject: [PATCH 10/26] Add sub permissions to the group policy and add request_spec file for the new permission --- ee/app/policies/ee/group_policy.rb | 23 +++ ee/app/policies/ee/project_policy.rb | 1 + .../admin_security_testing/request_spec.rb | 180 ++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index bff2a25a33a2c0..460da03c756f61 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -579,6 +579,29 @@ module GroupPolicy enable :admin_security_testing end + rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do + enable :access_security_and_compliance + enable :read_compliance_dashboard + enable :read_security_configuration + enable :access_security_scans_api + enable :read_on_demand_dast_scan + enable :create_on_demand_dast_scan + enable :edit_on_demand_dast_scan + enable :enable_pre_receive_secret_detection + enable :read_group_security_dashboard + enable :read_project_security_exclusions + enable :read_coverage_fuzzing + enable :create_coverage_fuzzing_corpus + enable :enable_container_scanning_for_registry + enable :push_code + enable :create_merge_request_from + enable :create_pipeline + enable :build_download_code + enable :read_merge_request + enable :download_code + enable :read_pre_receive_secret_detection_info + 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 519e849bb4700a..36af749b9b20bb 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -466,6 +466,7 @@ module ProjectPolicy rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do enable :access_security_and_compliance + enable :read_compliance_dashboard enable :read_security_configuration enable :access_security_scans_api enable :read_on_demand_dast_scan 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 00000000000000..aa3745c7c91105 --- /dev/null +++ b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb @@ -0,0 +1,180 @@ +# 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(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be_with_reload(:role) { create(:member_role, :guest, admin_security_testing: true, namespace: group) } + let_it_be(:member) { create(:group_member, :guest, user: user, source: group, member_role: role) } + let_it_be(:project_member) { create(:project_member, :guest, user: user, source: project) } + let_it_be(:member_role) do + create(:member_role, :guest, admin_security_testing: true, members: [project_member], namespace: project.group) + end + + 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, + project_level_compliance_dashboard: true, + group_level_compliance_dashboard: true) + + sign_in(user) + end + + describe "Controllers endpoints" do + describe Projects::Security::ComplianceDashboardsController do + it 'can access the show endpoint' do + get namespace_project_security_compliance_dashboard_path(project_id: project.id, + namespace_id: project.namespace.id) + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Groups::Security::ComplianceDashboardsController do + it 'can access the show endpoint' do + group.add_member(user, :guest) + get group_security_compliance_dashboard_path(group) + expect(response).to have_gitlab_http_status(:ok) + end + end + + 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_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_dast_profiles_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 project_security_dast_scanner_profiles_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 project_security_dast_site_profiles_path(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + describe 'GraphQL mutations' do + include GraphqlHelpers + + let_it_be_with_refind(:policy_project) { create(:project, group: group) } + let(:full_path) { project.full_path } + let(:base_params) do + { + project_path: full_path + + } + end + + let(:additional_params) { {} } + let(:input) { base_params.merge(additional_params) } + + let(:fields) do + <<~FIELDS + errors + FIELDS + end + + let(:mutation_name) { nil } + let(:mutation) { graphql_mutation(mutation_name, input, fields) } + + let_it_be_with_refind(:policy_project_id) { GitlabSchema.id_from_object(policy_project).to_s } + + subject(:execute_mutation) { post_graphql_mutation(mutation, current_user: user) } + + describe Mutations::Security::CiConfiguration::ConfigureContainerScanning do + let(:mutation_name) { :configureContainerScanning } + + it 'has access via a custom role' do + execute_mutation + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Mutations::Security::CiConfiguration::ConfigureDependencyScanning do + let(:mutation_name) { :configureDependencyScanning } + + it 'has access via a custom role' do + execute_mutation + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Mutations::Security::CiConfiguration::SetContainerScanningForRegistry do + let(:mutation_name) { :setContainerScanningForRegistry } + let(:additional_params) { { namespace_path: group.full_path, enable: true } } + + it 'has access via a custom role' do + execute_mutation + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Mutations::Security::CiConfiguration::SetGroupSecretPushProtection do + let(:mutation_name) { :setGroupSecretPushProtection } + let(:additional_params) { { namespace_path: group.full_path, secret_push_protection_enabled: true } } + + it 'has access via a custom role' do + execute_mutation + expect(response).to have_gitlab_http_status(:ok) + end + end + + describe Mutations::Security::CiConfiguration::SetPreReceiveSecretDetection do + let(:mutation_name) { :setPreReceiveSecretDetection } + let(:additional_params) { { namespace_path: full_path, enable: true } } + + it 'has access via a custom role' do + execute_mutation + expect(response).to have_gitlab_http_status(:ok) + end + end + end +end -- GitLab From cade850cdf16f0d9819a4c127fd83e7e91dce257 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 14 Jan 2025 16:38:01 +0200 Subject: [PATCH 11/26] Fix the request spec tests --- .../admin_security_testing/request_spec.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 index aa3745c7c91105..c90c16b5d8ae34 100644 --- a/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb +++ b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb @@ -30,15 +30,13 @@ describe "Controllers endpoints" do describe Projects::Security::ComplianceDashboardsController do it 'can access the show endpoint' do - get namespace_project_security_compliance_dashboard_path(project_id: project.id, - namespace_id: project.namespace.id) + get namespace_project_security_compliance_dashboard_path(group, project) expect(response).to have_gitlab_http_status(:ok) end end describe Groups::Security::ComplianceDashboardsController do it 'can access the show endpoint' do - group.add_member(user, :guest) get group_security_compliance_dashboard_path(group) expect(response).to have_gitlab_http_status(:ok) end @@ -74,28 +72,28 @@ describe Projects::Security::CorpusManagementController do it 'can access the show endpoint' do - get project_security_corpus_management_path(project) + 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_dast_profiles_path(project) + 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 project_security_dast_scanner_profiles_path(project) + 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 project_security_dast_site_profiles_path(project) + get new_project_security_configuration_profile_library_dast_site_profile_path(project) expect(response).to have_gitlab_http_status(:ok) end end -- GitLab From 931373d17fa2a41dbfa9d535cf37fb04f84c8c8f Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Thu, 16 Jan 2025 16:18:03 +0200 Subject: [PATCH 12/26] Add feature flag for the new permission and fix the mr comments --- .../security/configuration_presenter.rb | 2 +- ee/app/policies/ee/group_policy.rb | 7 +- ee/app/policies/ee/project_policy.rb | 10 +- .../custom_ability_admin_security_testing.yml | 9 + ee/spec/policies/group_policy_spec.rb | 40 +- ee/spec/policies/project_policy_spec.rb | 5543 ++++++++--------- .../admin_security_testing/request_spec.rb | 2 +- 7 files changed, 2805 insertions(+), 2808 deletions(-) create mode 100644 ee/config/feature_flags/beta/custom_ability_admin_security_testing.yml diff --git a/app/presenters/projects/security/configuration_presenter.rb b/app/presenters/projects/security/configuration_presenter.rb index 2a31b60ceafd83..7df38dd7d6f654 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) + can?(current_user, :admin_security_testing, self) end def gitlab_ci_history_path diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 460da03c756f61..e967281c168abf 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -575,11 +575,15 @@ module GroupPolicy enable :admin_vulnerability end + condition(:custom_ability_admin_security_testing) do + custom_roles_allowed? && custom_role_enables_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 + rule { security_dashboard_enabled & can?(:admin_security_testing) & custom_ability_admin_security_testing }.policy do enable :access_security_and_compliance enable :read_compliance_dashboard enable :read_security_configuration @@ -599,6 +603,7 @@ module GroupPolicy enable :build_download_code enable :read_merge_request enable :download_code + enable :read_group_runners enable :read_pre_receive_secret_detection_info end diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 36af749b9b20bb..c3a778798bc8fc 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -460,11 +460,15 @@ module ProjectPolicy enable :admin_security_testing end + condition(:custom_ability_admin_security_testing) do + custom_roles_allowed? && custom_role_enables_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 + rule { security_dashboard_enabled & can?(:admin_security_testing) & custom_ability_admin_security_testing }.policy do enable :access_security_and_compliance enable :read_compliance_dashboard enable :read_security_configuration @@ -484,6 +488,8 @@ module ProjectPolicy enable :build_download_code enable :read_merge_request enable :download_code + enable :read_project_runners + enable :read_runner enable :read_pre_receive_secret_detection_info end @@ -1041,7 +1047,7 @@ module ProjectPolicy condition(:container_scanning_for_registry_available) do @subject.licensed_feature_available?(:container_scanning_for_registry) end - rule { (container_scanning_for_registry_available & can?(:maintainer_access)) }.policy do + rule { container_scanning_for_registry_available & can?(:maintainer_access) }.policy do enable :enable_container_scanning_for_registry end 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 00000000000000..66113dabc82109 --- /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.7' +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 f7c9eef1950519..d26115313359a1 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -3778,13 +3778,6 @@ def create_member_role(member, abilities = member_role_abilities) end end - context 'for a member role with admin_security_testing true' do - let(:member_role_abilities) { { admin_security_testing: true } } - let(:allowed_abilities) { [:admin_security_testing] } - - it_behaves_like 'custom roles abilities' - end - context 'for a member role with read_vulnerability true' do let(:member_role_abilities) { { read_vulnerability: true } } let(:allowed_abilities) { [:read_group_security_dashboard] } @@ -3963,6 +3956,39 @@ 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) { { security_dashboard: true } } + + let(:allowed_abilities) do + [ + :access_security_and_compliance, + :read_compliance_dashboard, + :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_group_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_group_runners, + :read_pre_receive_secret_detection_info + ] + 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 d544eb6ada2fcc..2e3dda2443bc48 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -725,243 +725,127 @@ it_behaves_like 'correct access to security and compliance' end - context 'when the user as admin_security_testing permission' do - let(:current_user) { guest } - let_it_be(:project) { create(:project, :in_group) } - - let(:security_permissions) do - %i[ - 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_pre_receive_secret_detection_info - ] + describe 'vulnerability feedback permissions' do + before do + stub_licensed_features(security_dashboard: true) end - def check_base_permissions(policy) - security_permissions.select do |permission| - policy.allowed?(permission) - end - end + context 'with developer' do + let(:current_user) { developer } - # Define rules for different user roles and expected permissions - where(:role, :admin_security_testing_enabled) do - :guest | false - :guest | true - :developer | false - :developer | true - :maintainer | false - :maintainer | true + 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 + ]) + with_them do - let(:test_user) { create(:user) } + context 'with admin' do + let(:current_user) { admin } - before do - stub_licensed_features(custom_roles: true, - security_on_demand_scans: true, - security_scans_api: true, - pre_receive_secret_detection: true, - container_scanning_for_registry: true, - security_dashboard: true) - allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:sast).and_return( - instance_double(CloudConnector::BaseAvailableServiceData, free_access?: true)) - project_member = create(:project_member, role, user: test_user, source: project) - create(:member_role, role, admin_security_testing: admin_security_testing_enabled, members: [project_member], namespace: project.group) - end - - it 'has expected permissions' do - policy = described_class.new(test_user, project) - base_permissions = check_base_permissions(policy) - - security_permissions.each do |permission| - if admin_security_testing_enabled || base_permissions.include?(permission) - expect(policy).to be_allowed(permission) - else - expect(policy).to be_disallowed(permission) - end + context 'when admin mode enabled', :enable_admin_mode do + it { is_expected.to be_allowed(permission) } end - end - end - end - end - describe 'vulnerability feedback permissions' do - before do - stub_licensed_features(security_dashboard: true) - end - - 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 - - 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 } - - 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) } @@ -971,3732 +855,3799 @@ def check_base_permissions(policy) 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 + 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 } + let(:current_user) { admin } - it { is_expected.to be_disallowed(:read_software_license_policy) } + it { is_expected.to be_disallowed(:read_software_license_policy) } + end end - end - describe 'read_dependency' do - context 'when dependency scanning feature available' do - before do - stub_licensed_features(dependency_scanning: true) - end + describe 'read_dependency' do + context 'when dependency scanning feature available' do + before do + stub_licensed_features(dependency_scanning: true) + end - context 'with public project' do - let(:current_user) { create(:user) } + context 'with public project' do + let(:current_user) { create(:user) } - context 'with public access to repository' do - let(:project) { public_project } + context 'with public access to repository' do + let(:project) { public_project } - it { is_expected.to be_allowed(:read_dependency) } - end + it { is_expected.to be_allowed(:read_dependency) } + end - context 'with limited access to repository' do - let(:project) { create(:project, :public, :repository_private) } + context 'with limited access to repository' do + let(:project) { create(:project, :public, :repository_private) } - it { is_expected.not_to be_allowed(:read_dependency) } + it { is_expected.not_to be_allowed(:read_dependency) } + end end - end - context 'with private project' do - let(:project) { private_project } + context 'with private project' do + let(:project) { private_project } - 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_dependency) } - end + 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 'when admin mode disabled' do + it { is_expected.to be_disallowed(:read_dependency) } + end end - end - %w[owner maintainer developer reporter].each do |role| - context "with #{role}" do - let(:current_user) { send(role) } + %w[owner maintainer developer reporter].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:read_dependency) } + it { is_expected.to be_allowed(:read_dependency) } + 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_dependency) } + 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 } + context 'when dependency list feature not available' do + let(:current_user) { admin } - it { is_expected.not_to be_allowed(:read_dependency) } + it { is_expected.not_to be_allowed(:read_dependency) } + end end - end - describe 'read_licenses' do - context 'when license management feature available' do - context 'with public project' do - let(:current_user) { non_member } + describe 'read_licenses' do + context 'when license management feature available' do + context 'with public project' do + let(:current_user) { non_member } - context 'with public access to repository' do - 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 - end - - 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 'with private project' do + let(:project) { private_project } - it { is_expected.to be_allowed(:read_licenses) } - end + where(role: %w[owner maintainer developer reporter]) - context 'with admin' do - let(:current_user) { admin } + 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) } + describe 'add_project_to_instance_security_dashboard' do + let(:policy) { :add_project_to_instance_security_dashboard } - it { is_expected.to be_allowed(policy) } - end - - 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 - - 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 'commit_committer_check is enabled by the current license' do - before do - stub_licensed_features(commit_committer_check: true) - end - - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } - - it { is_expected.to be_allowed(:change_commit_committer_check) } - it { is_expected.to be_allowed(:read_commit_committer_check) } - end - - context '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 - context 'the user is a developer' do - let(:current_user) { developer } - - 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 } - - 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 commit_committer_name_check is enabled by the current license' do - before do - stub_licensed_features(commit_committer_name_check: true) - end - - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } - - 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 push_rules is enabled by the current license' do + before do + stub_licensed_features(push_rules: true) + end - context 'when 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 - - context '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_disallowed(:read_commit_committer_name_check) } - it { is_expected.to be_disallowed(:change_commit_committer_name_check) } - end - end + it { is_expected.to be_allowed(:change_push_rules) } + end - context 'reject_unsigned_commits is not enabled by the current license' do - before do - stub_licensed_features(reject_unsigned_commits: false) - end + context 'when the user is a maintainer' do + let(:current_user) { maintainer } - let(:current_user) { maintainer } + it { is_expected.to be_allowed(:change_push_rules) } + 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 a developer' do + let(:current_user) { developer } - context 'reject_unsigned_commits is enabled by the current license' do - before do - stub_licensed_features(reject_unsigned_commits: true) + it { is_expected.to be_disallowed(:change_push_rules) } + end end - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } - - it { is_expected.to be_allowed(:change_reject_unsigned_commits) } - it { is_expected.to be_allowed(:read_reject_unsigned_commits) } - 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 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 - - context 'when the user is a developer' do - let(:current_user) { developer } - - 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) + it { is_expected.not_to be_allowed(:change_commit_committer_check) } + it { is_expected.not_to be_allowed(:read_commit_committer_check) } 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 - - 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 'commit_committer_check is enabled by the current license' do + before do + stub_licensed_features(commit_committer_check: true) + end - context 'when the user is an admin', :enable_admin_mode do - let(:current_user) { admin } + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - 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_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 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_commit_committer_check) } + it { is_expected.to be_allowed(:read_commit_committer_check) } + end - context 'when the user is a developer' do - let(:current_user) { developer } + context 'the user is a developer' do + let(:current_user) { developer } - it { is_expected.to be_disallowed(:read_reject_non_dco_commits) } - it { is_expected.to be_disallowed(:change_reject_non_dco_commits) } + it { is_expected.not_to be_allowed(:change_commit_committer_check) } + it { is_expected.not_to be_allowed(:read_commit_committer_check) } + end end - end - context 'when dora4 analytics is available' do - before do - stub_licensed_features(dora4_analytics: true) - 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 - context 'when the user is a developer' do - let(:current_user) { developer } + let(:current_user) { maintainer } - it { is_expected.to be_allowed(:read_dora4_analytics) } + 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 } - - it { is_expected.to be_allowed(:read_dora4_analytics) } - end - end + context 'when commit_committer_name_check is enabled by the current license' do + before do + stub_licensed_features(commit_committer_name_check: true) + end - context 'when dora4 analytics is not available' do - let(:current_user) { developer } + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - before do - stub_licensed_features(dora4_analytics: false) - end + 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.not_to be_allowed(:read_dora4_analytics) } - end + context 'when the user is a maintainer' do + let(:current_user) { maintainer } - 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 { is_expected.to be_allowed(:read_commit_committer_name_check) } + it { is_expected.to be_allowed(:change_commit_committer_name_check) } + end - context 'when on SAAS', :saas do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } + context 'the user is a developer' do + let(:current_user) { developer } - it_behaves_like 'ai permission to', :read_enterprise_ai_analytics + 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 on self-managed' do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } - - it_behaves_like 'ai permission to', :read_enterprise_ai_analytics - end - end + context 'reject_unsigned_commits is not enabled by the current license' do + before do + stub_licensed_features(reject_unsigned_commits: false) + end - describe ':read_pro_ai_analytics' do - let(:project) { private_project_in_group } - let(:guest) { inherited_guest } - let(:reporter) { inherited_reporter } + let(:current_user) { maintainer } - 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) } + it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) } + it { is_expected.not_to be_allowed(:read_reject_unsigned_commits) } + end - it_behaves_like 'ai permission to', :read_pro_ai_analytics + context 'reject_unsigned_commits is enabled by the current license' do + before do + stub_licensed_features(reject_unsigned_commits: true) end - context 'with enterprise subscription' do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - it_behaves_like 'ai permission to', :read_pro_ai_analytics + it { is_expected.to be_allowed(:change_reject_unsigned_commits) } + it { is_expected.to be_allowed(:read_reject_unsigned_commits) } end - 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 'when the user is a maintainer' do + let(:current_user) { maintainer } - it_behaves_like 'ai permission to', :read_pro_ai_analytics + it { is_expected.to be_allowed(:change_reject_unsigned_commits) } + it { is_expected.to be_allowed(:read_reject_unsigned_commits) } end - context 'with enterprise subscription' do - let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } + context 'when the user is a developer' do + let(:current_user) { developer } - it_behaves_like 'ai permission to', :read_pro_ai_analytics + it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) } + it { is_expected.not_to be_allowed(:read_reject_unsigned_commits) } 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 - - with_them do - let(:current_user) { public_send(role) } + context 'when reject_non_dco_commits is not enabled by the current license' do before do - stub_licensed_features(code_review_analytics: true) - enable_admin_mode!(current_user) if admin_mode + stub_licensed_features(reject_non_dco_commits: false) end - it { is_expected.to(allowed ? be_allowed(:read_code_review_analytics) : be_disallowed(:read_code_review_analytics)) } - end + let(:current_user) { maintainer } - context 'with code review analytics is not available in license' do - let(:current_user) { owner } + 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 reject_non_dco_commits is enabled by the current license' do before do - stub_licensed_features(code_review_analytics: false) + stub_licensed_features(reject_non_dco_commits: true) end - it { is_expected.to be_disallowed(:read_code_review_analytics) } - end - end + context 'when the user is an admin', :enable_admin_mode do + let(:current_user) { admin } - 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 + it { is_expected.to be_allowed(:read_reject_non_dco_commits) } + it { is_expected.to be_allowed(:change_reject_non_dco_commits) } end - with_them do - let(:current_user) { public_send(role) } + context 'when the user is a maintainer' do + let(:current_user) { maintainer } - 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 + 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(allowed ? be_allowed(policy) : be_disallowed(policy)) } + context 'when the user is a developer' do + let(:current_user) { developer } + + 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 '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 + context 'when dora4 analytics is available' do + before do + stub_licensed_features(dora4_analytics: true) end - with_them do - let(:current_user) { public_send(role) } - - 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 + context 'when the user is a developer' do + let(:current_user) { developer } - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + it { is_expected.to be_allowed(:read_dora4_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 + 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 end - with_them do - let(:current_user) { public_send(role) } + context 'when dora4 analytics is not available' do + let(:current_user) { developer } before do - stub_licensed_features(merge_request_approvers: licensed) - enable_admin_mode!(current_user) if role == :admin + stub_licensed_features(dora4_analytics: false) 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 } + it { is_expected.not_to be_allowed(:read_dora4_analytics) } 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 ':read_enterprise_ai_analytics' do + let(:project) { private_project_in_group } + let(:guest) { inherited_guest } + let(:planner) { inherited_planner } + let(:reporter) { inherited_reporter } - 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 + context 'when on SAAS', :saas do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } - it_behaves_like 'resource with requirement permissions' do - let(:resource) { project } - end + it_behaves_like 'ai permission to', :read_enterprise_ai_analytics + 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 + context 'when on self-managed' do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } - before do - stub_licensed_features(quality_management: true) - enable_admin_mode!(current_user) if admin_mode + it_behaves_like 'ai permission to', :read_enterprise_ai_analytics + end end - with_them do - let(:current_user) { public_send(role) } + describe ':read_pro_ai_analytics' do + let(:project) { private_project_in_group } + let(:guest) { inherited_guest } + let(:reporter) { inherited_reporter } - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + 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 'with unavailable license' do - before do - stub_licensed_features(quality_management: false) + it_behaves_like 'ai permission to', :read_pro_ai_analytics end - it { is_expected.to(be_disallowed(policy)) } - end - end - end + context 'with enterprise subscription' do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } - 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 + it_behaves_like 'ai permission to', :read_pro_ai_analytics + end end - with_them do - let(:current_user) { public_send(actual_role) } + 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) } - before do - stub_licensed_features(ci_pipeline_cancellation_restrictions: true) - project.update!(restrict_pipeline_cancellation_role: restricted_role) + it_behaves_like 'ai permission to', :read_pro_ai_analytics end - it { is_expected.to(allowed ? be_allowed(ability) : be_disallowed(ability)) } + context 'with enterprise subscription' do + let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } + + it_behaves_like 'ai permission to', :read_pro_ai_analytics + end 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 ':read_code_review_analytics' do + let(:project) { private_project } - describe 'prevents cancel_build when CI cancllation restricted' do - let(:ability) { :cancel_build } + 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 - it_behaves_like 'prevents CI cancellation ability' - end + with_them do + let(:current_user) { public_send(role) } - 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 }) + stub_licensed_features(code_review_analytics: true) + enable_admin_mode!(current_user) if admin_mode end - context 'when project is in group' do - let(:project) { public_project_in_group } + it { is_expected.to(allowed ? be_allowed(:read_code_review_analytics) : be_disallowed(:read_code_review_analytics)) } + end - context 'when user is eligible for access' do - where(role: %w[owner auditor]) + context 'with code review analytics is not available in license' do + let(:current_user) { owner } - with_them do - let(:current_user) { public_send(role) } + before do + stub_licensed_features(code_review_analytics: false) + end - it { is_expected.to be_allowed(permission) } - end - end + it { is_expected.to be_disallowed(:read_code_review_analytics) } + end + end - context 'allows admin', :enable_admin_mode do - let(:current_user) { admin } + shared_examples 'merge request approval settings' do |admin_override_allowed = false| + let(:project) { private_project } - it { is_expected.to be_allowed(permission) } - end + 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 - context 'when project is in personal namespace' do - let(:current_user) { owner } - let(:project) { public_project } + with_them do + let(:current_user) { public_send(role) } - it { is_expected.to be_disallowed(permission) } + 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 + + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end - context 'when disabled' do - before do - stub_licensed_features({ feature => false }) + 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 user is eligible for access' do - where(role: %w[owner auditor]) - - 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) } + 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 'disallows admin', :enable_admin_mode do - let(:current_user) { admin } - it { is_expected.to be_disallowed(permission) } + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end end - describe 'project level compliance dashboard' do - it_behaves_like 'project level compliance feature', :project_level_compliance_dashboard, :read_compliance_dashboard - end + describe ':admin_merge_request_approval_settings' do + let(:project) { private_project } - describe 'project level compliance adherence report' do - it_behaves_like 'project level compliance feature', :project_level_compliance_adherence_report, :read_compliance_adherence_report - end + 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 - 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 + with_them do + let(:current_user) { public_send(role) } - 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 + before do + stub_licensed_features(merge_request_approvers: licensed) + enable_admin_mode!(current_user) if role == :admin + end - with_them do - let(:current_user) { public_send(role) } + it { is_expected.to(allowed ? be_allowed(:admin_merge_request_approval_settings) : be_disallowed(:admin_merge_request_approval_settings)) } + end + end - before do - stub_licensed_features(compliance_framework: feature_enabled) - enable_admin_mode!(current_user) if admin_mode + 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 - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + 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 - end - describe 'Incident Management on-call schedules' do - let(:current_user) { public_send(role) } - let(:admin_mode) { false } + 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 - 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 } + 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(actual_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 + before do + stub_licensed_features(ci_pipeline_cancellation_restrictions: true) + project.update!(restrict_pipeline_cancellation_role: restricted_role) + end + + it { is_expected.to(allowed ? be_allowed(ability) : be_disallowed(ability)) } + end end + end - with_them do - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + describe 'prevents cancel_pipeline when CI cancllation restricted' do + let(:ability) { :cancel_pipeline } - context 'with unavailable license' do + 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(oncall_schedules: false) + stub_licensed_features({ feature => true }) end - it { is_expected.to(be_disallowed(policy)) } + 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 } + + it { is_expected.to be_allowed(permission) } + 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) } + end + end + + context 'when disabled' do + before do + stub_licensed_features({ feature => false }) + end + + 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_disallowed(permission) } + end + end + + context 'disallows admin', :enable_admin_mode do + let(:current_user) { admin } + + it { is_expected.to be_disallowed(permission) } + end end end - it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer - end - end + describe 'project level compliance dashboard' do + it_behaves_like 'project level compliance feature', :project_level_compliance_dashboard, :read_compliance_dashboard + end - describe 'Escalation Policies' do - let(:current_user) { public_send(role) } - let(:admin_mode) { false } + describe 'project level compliance adherence report' do + it_behaves_like 'project level compliance feature', :project_level_compliance_adherence_report, :read_compliance_adherence_report + end - before do - enable_admin_mode!(current_user) if admin_mode - allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(true) + 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 - context ':read_incident_management_escalation_policy' do - let(:policy) { :read_incident_management_escalation_policy } + describe ':compliance_framework_available' do + let(:policy) { :admin_compliance_framework } - 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 + 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 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 + let(:current_user) { public_send(role) } - it { is_expected.to(be_disallowed(policy)) } + before do + stub_licensed_features(compliance_framework: feature_enabled) + enable_admin_mode!(current_user) if admin_mode end - end - it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + end end - context ':admin_incident_management_escalation_policy' do - let(:policy) { :admin_incident_management_escalation_policy } + describe 'Incident Management on-call schedules' do + let(:current_user) { public_send(role) } + let(:admin_mode) { false } - 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 + before do + enable_admin_mode!(current_user) if admin_mode + stub_licensed_features(oncall_schedules: true) end - with_them do - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + context ':read_incident_management_oncall_schedule' do + let(:policy) { :read_incident_management_oncall_schedule } - context 'with unavailable escalation policies' do - before do - allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(false) - end + 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 - it { is_expected.to(be_disallowed(policy)) } + with_them do + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + + context 'with unavailable license' do + before do + stub_licensed_features(oncall_schedules: false) + end + + it { is_expected.to(be_disallowed(policy)) } + end end - end - it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer - end - end + it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter + 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 ':admin_incident_management_oncall_schedule' do + let(:policy) { :admin_incident_management_oncall_schedule } - 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 + 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 'when the group is read only' do - let(:read_only) { true } + with_them do + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } - it { is_expected.to(be_disallowed(*abilities)) } - end + context 'with unavailable license' do + before do + stub_licensed_features(oncall_schedules: false) + end - context 'when the group is not read only' do - let(:read_only) { false } + it { is_expected.to(be_disallowed(policy)) } + 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] + it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer end - - it { is_expected.to(be_allowed(*(abilities - abilities_not_currently_enabled))) } end - end - context 'project access tokens' do - context 'GitLab.com Core resource access tokens', :saas do + describe 'Escalation Policies' do + let(:current_user) { public_send(role) } + let(:admin_mode) { false } + before do - stub_ee_application_setting(should_check_namespace_plan: true) + enable_admin_mode!(current_user) if admin_mode + allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(true) end - context 'with admin access' do - let(:current_user) { owner } + context ':read_incident_management_escalation_policy' do + let(:policy) { :read_incident_management_escalation_policy } - before do - project.add_owner(owner) + 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.to be_allowed(:read_resource_access_tokens) } - it { is_expected.to be_allowed(:destroy_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 '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) } + it { is_expected.to(be_disallowed(policy)) } + end end + + it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter end - context 'with non admin access' do - let(:current_user) { developer } + context ':admin_incident_management_escalation_policy' do + let(:policy) { :admin_incident_management_escalation_policy } - before do - project.add_developer(developer) + 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 '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 escalation policies' do + before do + allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(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 + + it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer 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) } + 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 'with maintainer access' do - let(:current_user) { maintainer } + 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 + + context 'when the group is read only' do + let(:read_only) { true } + + it { is_expected.to(be_disallowed(*abilities)) } + 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 'project access tokens' do + context 'GitLab.com Core resource access tokens', :saas do before do - project.add_maintainer(maintainer) + stub_ee_application_setting(should_check_namespace_plan: true) end - it_behaves_like 'GitLab.com Paid plan resource access tokens' + context 'with admin access' do + let(:current_user) { owner } + + before do + project.add_owner(owner) + end - context 'create resource access tokens' do - it { is_expected.to be_allowed(:create_resource_access_tokens) } + context 'when project belongs to a group' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } - 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.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 'when resource access token creation is not allowed' do - before do - group.namespace_settings.update_column(:resource_access_token_creation_allowed, false) - end + context 'with non admin access' do + let(:current_user) { developer } + + before do + project.add_developer(developer) + 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.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 '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) } + + context 'with maintainer access' do + let(:current_user) { maintainer } + + before do + project.add_maintainer(maintainer) 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) } + 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 - context 'cannot create resource access tokens' do 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 - end - context 'read resource access tokens' do - it { is_expected.to be_allowed(:read_resource_access_tokens) } - end + context 'read resource access tokens' do + it { is_expected.to be_allowed(:read_resource_access_tokens) } + end - context 'destroy resource access tokens' do - it { is_expected.to be_allowed(:destroy_resource_access_tokens) } + context 'destroy resource access tokens' do + it { is_expected.to be_allowed(:destroy_resource_access_tokens) } + end end - end - context 'with developer access' do - let(:current_user) { developer } + context 'with developer access' do + let(:current_user) { developer } - before do - project.add_developer(developer) - end + before do + project.add_developer(developer) + end - context 'create resource access tokens' do - it { is_expected.not_to be_allowed(:create_resource_access_tokens) } - end + 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 + context 'read resource access tokens' do + it { is_expected.not_to be_allowed(:read_resource_access_tokens) } + end - context 'destroy resource access tokens' do - it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + context 'destroy resource access tokens' do + it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + end end - end - context 'with auditor access' do - let(:current_user) { auditor } + context 'with auditor access' do + let(:current_user) { auditor } - context 'read resource access tokens' do - it { is_expected.to be_allowed(:read_resource_access_tokens) } - end + 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 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) } + context 'cannot destroy resource access tokens' do + it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } + end end 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) } + 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 - ] - end + 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 - stub_licensed_features(issues_analytics: true, code_review_analytics: true, project_merge_request_analytics: true, cycle_analytics_for_projects: true) + 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) - end + project_with_analytics_disabled.add_developer(developer) + project_with_analytics_private.add_developer(developer) + project_with_analytics_enabled.add_developer(developer) + end - context 'when analytics is disabled for the project' do - let(:project) { project_with_analytics_disabled } + context 'when analytics is disabled for the project' do + let(:project) { project_with_analytics_disabled } - %w[guest planner developer admin auditor].each do |role| - context "for #{role} user" do - let(:current_user) { send(role) } + %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(*all_read_analytics_permissions) } + it { is_expected.to be_disallowed(*all_read_analytics_permissions) } + end end end - end - context 'when analytics is private for the project' do - let(:project) { project_with_analytics_private } + context 'when analytics is private for the project' do + let(:project) { project_with_analytics_private } - %w[guest planner].each do |role| - context "for #{role} user" do - let(:current_user) { send(role) } + %w[guest planner].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(*all_read_analytics_permissions) } + it { is_expected.to be_disallowed(*all_read_analytics_permissions) } + end end - end - context 'for developer' do - let(:current_user) { developer } + context 'for developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } - end + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end - context 'for admin', :enable_admin_mode do - let(:current_user) { admin } + context 'for admin', :enable_admin_mode do + let(:current_user) { admin } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } - end + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end - context 'for auditor' do - let(:current_user) { auditor } + context 'for auditor' do + let(:current_user) { auditor } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end end - end - context 'when analytics is enabled for the project' do - let(:project) { project_with_analytics_enabled } + context 'when analytics is enabled for the project' do + let(:project) { project_with_analytics_enabled } - %w[guest planner].each do |role| - context "for #{role} user" do - let(:current_user) { send(role) } + %w[guest planner].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - 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) } + 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 - end - context 'for developer' do - let(:current_user) { developer } + context 'for developer' do + let(:current_user) { developer } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } - end + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end - context 'for admin', :enable_admin_mode do - let(:current_user) { admin } + context 'for admin', :enable_admin_mode do + let(:current_user) { admin } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } - end + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + end - context 'for auditor' do - let(:current_user) { auditor } + context 'for auditor' do + let(:current_user) { auditor } - it { is_expected.to be_allowed(*all_read_analytics_permissions) } + it { is_expected.to be_allowed(*all_read_analytics_permissions) } + 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 - with_them do - let(:current_user) { public_send(role) } + describe ':build_read_project' do + let(:policy) { :build_read_project } - before do - project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(project_visibility)) + 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 - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + with_them do + let(:current_user) { public_send(role) } + + before do + 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 - describe 'pending member permissions' do - let_it_be(:current_user) { create(:user) } - let_it_be(:group) { create(:group, :public) } + describe 'pending member permissions' do + let_it_be(:current_user) { create(:user) } + let_it_be(:group) { create(:group, :public) } - context 'with a pending membership in a private project' do - let_it_be(:project) { create(:project, :private, public_builds: false) } + 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 + where(:role) do + Gitlab::Access.sym_options.keys.map(&:to_sym) + 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) + 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) - expect_private_project_permissions_as_if_non_member + expect_private_project_permissions_as_if_non_member + end end end - end - context 'with a group invited to a project' do - let_it_be(:project) { create(:project, :private, public_builds: false) } + context 'with a group invited to a project' do + let_it_be(:project) { create(:project, :private, public_builds: false) } - before_all do - create(:project_group_link, project: project, group: group) - end + before_all do + create(:project_group_link, project: project, group: group) + end - where(:role) do - Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) - 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) + 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) - expect_private_project_permissions_as_if_non_member + expect_private_project_permissions_as_if_non_member + 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 '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) } - before_all do - create(:group_group_link, shared_with_group: group, shared_group: other_group) - end + before_all do + create(:group_group_link, shared_with_group: group, shared_group: other_group) + end - where(:role) do - Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) - 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 other group's project as if the user is not a member" do - create(:group_member, :awaiting, role, source: group, user: current_user) + 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) - expect_private_project_permissions_as_if_non_member + expect_private_project_permissions_as_if_non_member + end end end - 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 '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) } - where(:role) do - Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym) - 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 subgroup project as if the user is not a member' do - create(:group_member, :awaiting, role, source: group, user: current_user) + 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) - expect_private_project_permissions_as_if_non_member + expect_private_project_permissions_as_if_non_member + 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 + 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 ':read_approvers' do + let(:policy) { :read_approvers } - where(:role, :allowed) do - :guest | false - :planner | false - :reporter | false - :developer | false - :maintainer | true - :auditor | true - :owner | true - :admin | true - end + where(:role, :allowed) do + :guest | false + :planner | false + :reporter | false + :developer | false + :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 + end - it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } + end end end - end - context 'importing members from another project' do - let(:current_user) { owner } + context 'importing members from another project' do + let(:current_user) { owner } - context 'for a personal project' do - it { is_expected.to be_allowed(:import_project_members_from_another_project) } - end + context 'for a personal project' do + it { is_expected.to be_allowed(:import_project_members_from_another_project) } + end - context 'for a project in a group' do - let(:project) { create(:project, group: create(:group)) } + context 'for a project in a group' do + let(:project) { create(:project, group: create(:group)) } - context 'when the project has locked their membership' do - context 'via the parent group' do - before do - project.group.update!(membership_lock: true) + context 'when the project has locked their membership' do + context 'via the parent group' do + before do + project.group.update!(membership_lock: true) + end + + it { is_expected.to be_disallowed(:import_project_members_from_another_project) } end - it { is_expected.to be_disallowed(:import_project_members_from_another_project) } - end + context 'via LDAP' do + before do + stub_application_setting(lock_memberships_to_ldap: true) + end - context 'via LDAP' do - before do - stub_application_setting(lock_memberships_to_ldap: true) + it { is_expected.to be_disallowed(:import_project_members_from_another_project) } end - it { is_expected.to be_disallowed(:import_project_members_from_another_project) } - end + context 'via SAML' do + before do + stub_application_setting(lock_memberships_to_saml: true) + end - context 'via SAML' do - before do - stub_application_setting(lock_memberships_to_saml: true) + it { is_expected.to be_disallowed(:import_project_members_from_another_project) } end - - it { is_expected.to be_disallowed(:import_project_members_from_another_project) } 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 } - - let_it_be(:banned_group) { create(:group) } - let_it_be(:banned_subgroup) { create(:group, parent: banned_group) } - before do - stub_licensed_features(unique_project_download_limit: true) - create(:namespace_ban, user: current_user, namespace: banned_group) - end + describe 'inviting a group' do + let_it_be_with_reload(:current_user) { developer } + let_it_be_with_reload(:project) { public_project } - it { is_expected.to be_allowed(:read_project) } + let_it_be(:banned_group) { create(:group) } + let_it_be(:banned_subgroup) { create(:group, parent: banned_group) } - context 'when the user is banned from the invited group' do before do - create(:project_group_link, project: project, group: banned_group) + stub_licensed_features(unique_project_download_limit: true) + create(:namespace_ban, user: current_user, namespace: banned_group) end - it { is_expected.to be_disallowed(:read_project) } - end - - context 'when the user is banned from the invited subgroup' do - before do - create(:project_group_link, project: project, group: banned_subgroup) - end + it { is_expected.to be_allowed(:read_project) } - it { is_expected.to be_disallowed(:read_project) } - end - end + context 'when the user is banned from the invited group' do + before do + create(:project_group_link, project: project, group: banned_group) + end - describe 'user banned from namespace' do - let_it_be_with_reload(:current_user) { create(:user) } + it { is_expected.to be_disallowed(:read_project) } + end - let_it_be(:group) { create(:group, :private) } - let_it_be(:project) { create(:project, :private, public_builds: false, group: group) } + context 'when the user is banned from the invited subgroup' do + before do + create(:project_group_link, project: project, group: banned_subgroup) + end - before do - stub_licensed_features(unique_project_download_limit: true) - project.add_developer(current_user) + it { is_expected.to be_disallowed(:read_project) } + end end - context 'when user is not banned' do - it { is_expected.to be_allowed(:read_project) } - 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) } - context 'when user is banned' do before do - create(:namespace_ban, user: current_user, namespace: group.root_ancestor) + stub_licensed_features(unique_project_download_limit: true) + project.add_developer(current_user) end - it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } + context 'when user is not banned' do + it { is_expected.to be_allowed(:read_project) } + end - context 'as an owner of the project' do + context 'when user is banned' do before do - project.add_owner(current_user) + create(:namespace_ban, user: current_user, namespace: group.root_ancestor) end it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } - 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 '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(*described_class.own_ability_map.map.keys) } + end - context 'as an admin' do - let_it_be(:current_user) { admin } + 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 'when admin mode is enabled', :enable_admin_mode do - it { is_expected.to be_allowed(:read_project) } + it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } end - 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) } + context 'as an admin' do + let_it_be(:current_user) { admin } - it { is_expected.to be_disallowed(:read_project) } - end + context 'when admin mode is enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:read_project) } + end + 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) + 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(:read_project) } end - it { is_expected.to be_allowed(:read_project) } - 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 - 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_allowed(:read_project) } end - it { is_expected.to be_allowed(:read_project) } - end - end - end + context 'when licensed feature unique_project_download_limit is not available' do + before do + stub_licensed_features(unique_project_download_limit: false) + 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 + it { is_expected.to be_allowed(:read_project) } + end + end end - with_them do - let(:current_user) { public_send(role) } + describe 'create_objective' do + let(:okr_policies) { [:create_objective, :create_key_result] } - before do - enable_admin_mode!(current_user) if role == :admin - stub_licensed_features(okrs: true) + where(:role, :allowed) do + :guest | true + :planner | true + :reporter | true + :developer | true + :maintainer | true + :auditor | false + :owner | true + :admin | true end - context 'when okrs_mvc feature flag is enabled' do - it { is_expected.to(allowed ? be_allowed(*okr_policies) : be_disallowed(*okr_policies)) } - end + with_them do + let(:current_user) { public_send(role) } - context 'when okrs_mvc feature flag is disabled' do before do - stub_feature_flags(okrs_mvc: false) + enable_admin_mode!(current_user) if role == :admin + stub_licensed_features(okrs: true) end - it { is_expected.to be_disallowed(*okr_policies) } - end + context 'when okrs_mvc feature flag is enabled' do + it { is_expected.to(allowed ? be_allowed(*okr_policies) : be_disallowed(*okr_policies)) } + end - context 'when okrs license feature is not available' do - before do - stub_licensed_features(okrs: false) + context 'when okrs_mvc feature flag is disabled' do + before do + stub_feature_flags(okrs_mvc: false) + end + + it { is_expected.to be_disallowed(*okr_policies) } end - it { is_expected.to be_disallowed(*okr_policies) } + 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 - 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 'read_member_role' do + let_it_be_with_reload(:project) { private_project_in_group } + let_it_be_with_reload(:current_user) { create(:user) } - 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 + let(:permission) { :read_member_role } - enable_admin_mode!(current_user) if role == :admin + where(:role, :allowed) do + :guest | true + :planner | true + :reporter | true + :developer | true + :maintainer | true + :auditor | false + :owner | true + :admin | true end - context 'when custom_roles feature is enabled' do + with_them do before do - stub_licensed_features(custom_roles: true) - end + 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 - it do - is_expected.to(allowed ? be_allowed(permission) : be_disallowed(permission)) + enable_admin_mode!(current_user) if role == :admin end - end - context 'when custom_roles feature is disabled' do - before do - stub_licensed_features(custom_roles: false) + context 'when custom_roles feature is enabled' do + before do + stub_licensed_features(custom_roles: true) + end + + it do + is_expected.to(allowed ? be_allowed(permission) : be_disallowed(permission)) + end end - it { is_expected.to be_disallowed(permission) } + context 'when custom_roles feature is disabled' do + before do + stub_licensed_features(custom_roles: false) + end + + it { is_expected.to be_disallowed(permission) } + end end end - end - context 'hidden projects' do - let(:project) { create(:project, :repository, hidden: true) } - let(:current_user) { create(:user) } - - before do - project.add_owner(current_user) - end + context 'hidden projects' do + let(:project) { create(:project, :repository, hidden: true) } + let(:current_user) { create(:user) } - it { is_expected.to be_disallowed(:download_code) } - it { is_expected.to be_disallowed(:build_download_code) } - end + before do + project.add_owner(current_user) + 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 - ) + it { is_expected.to be_disallowed(:download_code) } + it { is_expected.to be_disallowed(:build_download_code) } end - let_it_be(:project_member_guest) do - create( - :project_member, - :guest, - user: guest, - project: project, - access_level: Gitlab::Access::GUEST - ) - 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 + + let_it_be(:project_member_guest) do + create( + :project_member, + :guest, + user: guest, + project: project, + access_level: Gitlab::Access::GUEST + ) + end - let(:member_role_abilities) { {} } - let(:allowed_abilities) { [] } - let(:disallowed_abilities) { [] } - let(:current_user) { guest } - let(:licensed_features) { {} } + let(:member_role_abilities) { {} } + let(:allowed_abilities) { [] } + let(:disallowed_abilities) { [] } + let(:current_user) { guest } + let(:licensed_features) { {} } - subject { described_class.new(current_user, project) } + subject { described_class.new(current_user, project) } - def create_member_role(member, abilities = member_role_abilities) - params = abilities.merge(namespace: project.group) + 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 + create(:member_role, :guest, params).tap do |role| + role.members << member + end end - end - shared_examples 'custom roles abilities' do - context 'with custom_roles license disabled' do - before do - create_member_role(group_member_guest) + 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 - stub_licensed_features(licensed_features.merge(custom_roles: false)) + it { expect_disallowed(*allowed_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 'with custom_roles license enabled' do - before do - stub_licensed_features(licensed_features.merge(custom_roles: 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 'custom role for parent group' do - context 'when a role enables the abilities' do - before do - create_member_role(group_member_guest) + it { expect_allowed(*allowed_abilities) } + it { expect_disallowed(*disallowed_abilities) } end - it { expect_allowed(*allowed_abilities) } - it { expect_disallowed(*disallowed_abilities) } + context 'when a role does not enable the abilities' do + it { expect_disallowed(*allowed_abilities) } + end end - 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 - context 'custom role on project membership' do - context 'when a role enables the abilities' do - before do - create_member_role(project_member_guest) + it { expect_allowed(*allowed_abilities) } + it { expect_disallowed(*disallowed_abilities) } 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) } + context 'when a role does not enable the abilities' do + it { expect_disallowed(*allowed_abilities) } + end 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 '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 + it_behaves_like 'custom roles abilities' + 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 '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_behaves_like 'custom roles abilities' end - it_behaves_like 'custom roles abilities' - 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 + ] + 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 - ] + it_behaves_like 'custom roles abilities' + + it 'does not enable to admin_vulnerability' do + expect(subject).to be_disallowed(:admin_vulnerability) + end end - it_behaves_like 'custom roles abilities' + 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] } - it 'does not enable to admin_vulnerability' do - expect(subject).to be_disallowed(:admin_vulnerability) + it_behaves_like 'custom roles abilities' 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] } + 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 - 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 - ] + 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 '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' - it_behaves_like 'custom roles 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) + 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] } + it_behaves_like 'custom roles abilities' + end - it_behaves_like 'custom roles abilities' + 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 - 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) + it { is_expected.to be_disallowed(:read_merge_request, :admin_merge_request, :download_code) } 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] } 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 + 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 { 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 `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 archive_project true' do - let(:member_role_abilities) { { archive_project: true } } - let(:allowed_abilities) { [:archive_project, :view_edit_page] } + 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_behaves_like 'custom roles abilities' - end + let(:disallowed_abilities) do + [:modify_security_policy] + end - 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 '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) - 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 + create_member_role(group_member_guest, { read_dependency: true }) + create_member_role(project_member_guest, { read_code: true }) + end - let(:disallowed_abilities) do - [:modify_security_policy] + 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 '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) + 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] } - 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 - it { is_expected.to be_allowed(:read_dependency) } - it { is_expected.to be_allowed(:read_code) } - end + 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] } - 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' - 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 custom role with the `admin_push_rules` ability' do - let(:member_role_abilities) { { admin_push_rules: true } } - let(:allowed_abilities) { [:admin_push_rules] } + 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' + 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 '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 - ) + let(:member_role_abilities) { { admin_compliance_framework: true } } - create_member_role(group_member_guest) + let(:allowed_abilities) do + [ + :admin_compliance_framework, + :read_compliance_dashboard, + :read_compliance_adherence_report, + :read_compliance_violations_report + ] 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 - ) - end + it_behaves_like 'custom roles abilities' 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 - } - end + 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, admin_compliance_framework: true } } + let(:member_role_abilities) { { read_compliance_dashboard: true } } - let(:allowed_abilities) do - [ - :admin_compliance_framework, - :read_compliance_dashboard, - :read_compliance_adherence_report, - :read_compliance_violations_report - ] + let(:allowed_abilities) do + [ + :read_compliance_dashboard, + :read_compliance_adherence_report, + :read_compliance_violations_report + ] + end + + it_behaves_like 'custom roles abilities' end - it_behaves_like 'custom roles abilities' - 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] } - 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 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(:allowed_abilities) do - [ - :read_compliance_dashboard, - :read_compliance_adherence_report, - :read_compliance_violations_report - ] + 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 - 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' - it_behaves_like 'custom roles abilities' - end + context 'when `target_branch_rules` feature is available' do + let(:licensed_features) { { target_branch_rules: true } } + let(:allowed_abilities) { [:admin_target_branch_rule] } - 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 '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 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 end - it_behaves_like 'custom roles abilities' - - context 'when `target_branch_rules` feature is available' do - let(:licensed_features) { { target_branch_rules: true } } - let(:allowed_abilities) { [:admin_target_branch_rule] } + context 'for a custom role with the `admin_integrations` ability' do + let(:member_role_abilities) { { admin_integrations: true } } + let(:allowed_abilities) { [:admin_integrations] } 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 custom role with the `read_runners` ability' do + let(:member_role_abilities) { { read_runners: true } } + let(:allowed_abilities) { [:read_project_runners, :read_runner] } 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] } - it_behaves_like 'custom roles abilities' - end + 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 - 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] } + it_behaves_like 'custom roles abilities' + end - 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) { { security_dashboard: true } } + + 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_runner, + :read_pre_receive_secret_detection_info + ] + end - 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 - - it_behaves_like 'custom roles abilities' end - 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] } + 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 - end + 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 - 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) } + with_them do + before do + allow(project).to receive(:can_suggest_reviewers?).and_return(suggested_reviewers_available) - context 'when user is suggested_reviewers_bot' do - let(:current_user) { Users::Internal.suggested_reviewers_bot } + allow(::Gitlab::CurrentSettings) + .to receive(:personal_access_tokens_disabled?) + .and_return(!token_creation_allowed) + end - where(:suggested_reviewers_available, :token_creation_allowed, :allowed) do - false | false | false - false | true | false - true | false | false - true | true | true + it 'always allows permissions except when feature disabled' do + if allowed + expect_allowed(*permissions) + else + expect_disallowed(*permissions) + end + end + end end - with_them do + context 'when user is not suggested_reviewers_bot' do + let(:current_user) { developer } + before do - allow(project).to receive(:can_suggest_reviewers?).and_return(suggested_reviewers_available) + allow(project).to receive(:can_suggest_reviewers?).and_return(true) allow(::Gitlab::CurrentSettings) .to receive(:personal_access_tokens_disabled?) - .and_return(!token_creation_allowed) + .and_return(false) end - it 'always allows permissions except when feature disabled' do - if allowed - expect_allowed(*permissions) - else - expect_disallowed(*permissions) - end + it 'does not allow permissions' do + expect_disallowed(*permissions) end end end - 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) - - allow(::Gitlab::CurrentSettings) - .to receive(:personal_access_tokens_disabled?) - .and_return(false) - end + describe 'read_project_runners' do + context 'with auditor' do + let(:current_user) { auditor } - it 'does not allow permissions' do - expect_disallowed(*permissions) + it { is_expected.to be_allowed(:read_project_runners) } end end - end - - describe 'read_project_runners' do - context 'with auditor' do - let(:current_user) { auditor } - - it { is_expected.to be_allowed(:read_project_runners) } - 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' + end + + context 'and the project is public in group' do + let(:project) { public_project_in_group } - it_behaves_like 'allows to create pipeline' + 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 } - - 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) } + 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 } - 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 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 token scope is disabled' do - let(:token_scope_enabled) { false } + let_it_be(:other_private_project) { create(:project, :private) } - 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) } + 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 pipeline is executed in project where bot is not invited' do - let(:scope_project) { other_private_project } + context 'when token scope is disabled' do + let(:token_scope_enabled) { false } - 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 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 enabled' do - let(:token_scope_enabled) { true } + 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 } - 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 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 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 not invited' do + let(:scope_project) { other_private_project } - context 'when security_dashboard is not enabled' do - it { is_expected.to be_disallowed(:create_vulnerability_state_transition) } + 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 security_dashboard is enabled' do + context 'when security policy bot is on the project' do before do - stub_licensed_features(security_dashboard: true) + project.add_guest(security_policy_bot) end - it { is_expected.to be_allowed(:create_vulnerability_state_transition) } - end - end - 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 + before do + stub_licensed_features(security_dashboard: true) + end - context 'when project is a security policy project' do - before do - create(:security_orchestration_policy_configuration, security_policy_management_project: project) + it { is_expected.to be_allowed(:create_vulnerability_state_transition) } + end end + end + + describe 'download_code_spp_repository policy' do + let(:current_user) { guest } it { is_expected.not_to be_allowed(:download_code_spp_repository) } - context 'and project allows spp_repository_pipeline_access' do + context 'when project is a security policy project' do before do - project.project_setting.update!(spp_repository_pipeline_access: true) + create(:security_orchestration_policy_configuration, security_policy_management_project: project) end - context 'and the project is private' do - let(:project) { private_project } + it { is_expected.not_to be_allowed(:download_code_spp_repository) } - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + context 'and project allows spp_repository_pipeline_access' do + before do + project.project_setting.update!(spp_repository_pipeline_access: true) + end + + context 'and the project is private' do + let(:project) { private_project } - context 'and the project is internal' do - let(:project) { internal_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' 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 public' do + let(:project) { public_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) } + end - it { is_expected.to be_allowed(:download_code_spp_repository) } - end - end + context 'and the project is public in group' do + let(:project) { public_project_in_group } - context 'and namespace allows spp_repository_pipeline_access' do - before do - project.group.namespace_settings.update!(spp_repository_pipeline_access: true) + 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 namespace allows spp_repository_pipeline_access' do + before do + project.group.namespace_settings.update!(spp_repository_pipeline_access: true) + end - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + context 'and the project is private in group' do + let(:project) { private_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 - it { is_expected.to be_allowed(:download_code_spp_repository) } - end + 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 } - 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 - it { is_expected.to be_allowed(:download_code_spp_repository) } - end - end + context 'and the project is public in group' do + let(:project) { public_project_in_group } - context 'and application setting allows spp_repository_pipeline_access' do - before do - stub_application_setting(spp_repository_pipeline_access: true) + it { is_expected.to be_allowed(:download_code_spp_repository) } + end end - context 'and the project is private' do - let(:project) { private_project } + 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 } + + 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 internal' do + let(:project) { internal_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 public' do - let(:project) { public_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 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 - 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 } + 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) } + let_it_be(:other_private_project) { create(:project, :private) } - 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 - ) - end + 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 + ) + end - context 'when token scope is disabled' do - let(:token_scope_enabled) { false } + context 'when token scope is disabled' do + let(:token_scope_enabled) { false } - context 'when accessing from the same project' do - 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 'when accessing from other project' do - let(:scope_project) { other_private_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 - end - context 'when token scope is enabled' do - let(:token_scope_enabled) { true } + 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) } - end + context 'when accessing from the same project' do + it { is_expected.to be_allowed(:download_code_spp_repository) } + end - context 'when accessing from other project' do - let(:scope_project) { other_private_project } + context 'when accessing from other project' do + let(:scope_project) { other_private_project } - it { is_expected.to be_disallowed(:download_code_spp_repository) } + it { is_expected.to be_disallowed(:download_code_spp_repository) } + end end end end - end - describe 'generate_description' do - let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } - let(:current_user) { guest } - let(:project) { private_project } + describe 'generate_description' do + let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } + let(:current_user) { guest } + let(:project) { private_project } + + before do + allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) + end + + context "when feature is authorized" do + before do + allow(authorizer).to receive(:allowed?).and_return(true) + end - before do - allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) - end + context 'when user can create issue' do + it { is_expected.to be_allowed(:generate_description) } + end - context "when feature is authorized" do - before do - allow(authorizer).to receive(:allowed?).and_return(true) - end + context 'when user cannot create issue' do + let(:current_user) { create(:user) } - context 'when user can create issue' do - it { is_expected.to be_allowed(:generate_description) } + it { is_expected.to be_disallowed(:generate_description) } + end end - context 'when user cannot create issue' do - let(:current_user) { create(:user) } + context "when feature is not authorized" do + before do + allow(authorizer).to receive(:allowed?).and_return(false) + end it { is_expected.to be_disallowed(:generate_description) } end end - context "when feature is not authorized" do + describe 'summarize_new_merge_request policy' do + let_it_be(:namespace) { group } + let_it_be(:project) { private_project } + let_it_be(:current_user) { maintainer } + + let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } + before do - allow(authorizer).to receive(:allowed?).and_return(false) + allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) + allow(project).to receive(:namespace).and_return(namespace) end - it { is_expected.to be_disallowed(:generate_description) } - end - end + context "when feature is authorized" do + before do + allow(authorizer).to receive(:allowed?).and_return(true) + end - describe 'summarize_new_merge_request policy' do - let_it_be(:namespace) { group } - let_it_be(:project) { private_project } - let_it_be(:current_user) { maintainer } + it { is_expected.to be_allowed(:summarize_new_merge_request) } - let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } + 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 - before do - allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) - allow(project).to receive(:namespace).and_return(namespace) - end + it { is_expected.to be_disallowed(:summarize_new_merge_request) } + end - context "when feature is authorized" do - before do - allow(authorizer).to receive(:allowed?).and_return(true) - end + context 'when user cannot create_merge_request_in' do + let(:current_user) { guest } - it { is_expected.to be_allowed(:summarize_new_merge_request) } + it { is_expected.to be_disallowed(:summarize_new_merge_request) } + end + end - 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 'read_observability policy' do - let(:current_user) { reporter } - - before do - stub_licensed_features(observability: true) - end + describe 'when observability_features is disabled' do + before do + stub_feature_flags(observability_features: false) + end - describe 'when observability_features is disabled' do - before do - stub_feature_flags(observability_features: false) + it { is_expected.to be_disallowed(:read_observability) } end - it { is_expected.to be_disallowed(:read_observability) } - 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 feature flag is enabled for root namespace' do - before do - stub_feature_flags(observability_features: project.root_namespace) + it { is_expected.to be_allowed(:read_observability) } end - it { is_expected.to be_allowed(: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 the project does not have the correct license' do - before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: false) + it { is_expected.to be_disallowed(:read_observability) } end - it { is_expected.to be_disallowed(:read_observability) } - end + describe 'when the user does not have permission' do + let(:current_user) { guest } - describe 'when the user does not have permission' do - let(:current_user) { guest } + before do + stub_feature_flags(observability_features: true) + stub_licensed_features(observability: true) + end - before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: true) + 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 has permission' do + 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_allowed(:read_observability) } - end - end - - describe 'write_observability policy' do - let(:current_user) { developer } - - before do - stub_licensed_features(observability: true) - 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 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_disallowed(:write_observability) } end - it { is_expected.to be_disallowed(:write_observability) } - 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 feature flag is enabled for root namespace' do - before do - stub_feature_flags(observability_features: project.root_namespace) + it { is_expected.to be_allowed(:write_observability) } end - it { is_expected.to be_allowed(:write_observability) } - 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_tracing feature flag is enabled for root namespace' do - before do - stub_feature_flags(observability_tracing: project.root_namespace) + it { is_expected.to be_allowed(:write_observability) } end - it { is_expected.to be_allowed(: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 the project does not have the correct license' do - before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: false) + it { is_expected.to be_disallowed(:write_observability) } end - it { is_expected.to be_disallowed(:write_observability) } - end + describe 'when the user does not have permission' do + let(:current_user) { reporter } - 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) + end - 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_disallowed(: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 user has permission' do + describe "#admin_vulnerability" do before do - stub_feature_flags(observability_features: true) - stub_licensed_features(observability: true) + stub_licensed_features(security_dashboard: true) end - it { is_expected.to be_allowed(:write_observability) } - end - end + let(:expected_permissions) do + [ + :admin_vulnerability, + :read_vulnerability, + :create_vulnerability_feedback, + :destroy_vulnerability_feedback, + :update_vulnerability_feedback, + :create_vulnerability_state_transition + ] + end - describe "#admin_vulnerability" do - before do - stub_licensed_features(security_dashboard: true) - end + context "with guest" do + let(:current_user) { guest } - 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 planner" do + let(:current_user) { planner } - 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 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 reporter" do - let(:current_user) { reporter } + 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 developer" do - let(:current_user) { developer } + 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 maintainer" do - let(:current_user) { maintainer } + context "with owner" do + let(:current_user) { owner } - it { is_expected.to be_allowed(*expected_permissions) } + it { is_expected.to be_allowed(*expected_permissions) } + end end - context "with owner" do + 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 - 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 - - 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) + 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 - 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) + 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 - 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) + 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 - if params[:allowed] - it { expect_allowed(:read_ai_agents) } - else - it { expect_disallowed(:read_ai_agents) } + 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 - end - end + with_them do + before do + stub_feature_flags(agent_registry: feature_flag_enabled) + stub_licensed_features(ai_agents: licensed_feature) + 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) + 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 - if params[:allowed] - it { expect_allowed(:write_ai_agents) } - else - it { expect_disallowed(:write_ai_agents) } + if params[:allowed] + it { expect_allowed(:write_ai_agents) } + else + it { expect_disallowed(:write_ai_agents) } + end end end - end - describe 'access_duo_chat' do - let_it_be(:current_user) { create(:user) } - let(:project) { create(:project, :public, group: group) } + describe 'access_duo_chat' do + let_it_be(:current_user) { create(:user) } + let(:project) { create(:project, :public, group: group) } - subject { described_class.new(current_user, project) } + subject { described_class.new(current_user, project) } - context 'when on SaaS instance', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } + context 'when on SaaS instance', :saas do + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } - 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 container is a group with AI enabled' do + include_context 'with duo features enabled and ai chat available for group on SaaS' - context 'when user is a member of the group' do - before do - group.add_guest(current_user) + context 'when user is a member of the group' do + before do + group.add_guest(current_user) + end + + it { is_expected.to be_allowed(:access_duo_chat) } + + context 'when the group does not have an Premium SaaS license' do + let_it_be(:group) { create(:group) } + + 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 'user can view project' do + it 'is allowed' do + is_expected.to be_allowed(:access_duo_chat) + end + end - context 'when the group does not have an Premium SaaS license' do - let_it_be(:group) { create(:group) } + context 'user cannot view project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end - it { is_expected.to be_disallowed(:access_duo_chat) } + 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 - with_them do - before do - project.update!(duo_features_enabled: duo_features_enabled) - end + describe 'access to project for duo workflow' do + let_it_be_with_reload(:project) { public_project } - it do - is_expected.to cs_matcher + 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 - 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) } + + before do + enable_admin_mode!(current_user) if role == :admin + end - 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 + 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 + + 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 - 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) + 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 index c90c16b5d8ae34..06aff9ce8566db 100644 --- a/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb +++ b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb @@ -23,7 +23,7 @@ container_scanning_for_registry: true, project_level_compliance_dashboard: true, group_level_compliance_dashboard: true) - + stub_feature_flags(custom_ability_admin_security_testing: true) sign_in(user) end -- GitLab From 6a584d0af7e7190caae7366c2e73a46ab0013bcb Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Thu, 16 Jan 2025 17:05:02 +0200 Subject: [PATCH 13/26] Fix the abilities doc --- doc/user/custom_roles/abilities.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index b3121793c5bd4e..fd7fd51fbadebe 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,8 +80,8 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Link to a security policy project | Allows linking security policy projects. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | | Edit and manage security testing configurations and settings. | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | | -| [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | +| Manage security testing | Edit and manage security testing configurations and settings. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | Group,
Project | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | +| Link to a security policy project | Allows linking security policy projects. | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab From 2be2cff7f0d35577f01ae4a0276a71e8fc33ffc8 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Sun, 19 Jan 2025 13:51:14 +0200 Subject: [PATCH 14/26] Fix the unit test for project --- ee/spec/policies/project_policy_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 2e3dda2443bc48..1e5269535fea30 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -3116,7 +3116,6 @@ def create_member_role(member, abilities = member_role_abilities) context 'for a custom role with the `admin_security_testing` ability' do let(:member_role_abilities) { { admin_security_testing: true } } let(:licensed_features) { { security_dashboard: true } } - let(:allowed_abilities) do [ :access_security_and_compliance, @@ -3143,6 +3142,11 @@ def create_member_role(member, abilities = member_role_abilities) ] end + before do + # Ensure merge requests are enabled for the project + project.project_feature.update!(merge_requests_access_level: ProjectFeature::ENABLED) + end + it_behaves_like 'custom roles abilities' end end -- GitLab From 2b50ce7dcb6081dc4d74db5be009937b2a2b5b74 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 21 Jan 2025 16:52:06 +0200 Subject: [PATCH 15/26] Fix the mr comments for the project and the group policy --- .../security/security_testing.rb | 14 ----- ee/app/policies/ee/group_policy.rb | 46 ++++++++++------ ee/app/policies/ee/project_policy.rb | 55 ++++++++++++------- .../admin_security_testing.yml | 4 +- .../custom_ability_admin_security_testing.yml | 2 +- ee/spec/policies/group_policy_spec.rb | 6 +- ee/spec/policies/project_policy_spec.rb | 14 ++++- .../admin_security_testing/request_spec.rb | 3 +- 8 files changed, 84 insertions(+), 60 deletions(-) delete mode 100644 ee/app/graphql/types/permission_types/security/security_testing.rb diff --git a/ee/app/graphql/types/permission_types/security/security_testing.rb b/ee/app/graphql/types/permission_types/security/security_testing.rb deleted file mode 100644 index d08cc1fb8c140c..00000000000000 --- a/ee/app/graphql/types/permission_types/security/security_testing.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Types - module PermissionTypes - module Security - class SecurityTesting < BasePermissionType - graphql_name 'SecurityTestingPermissions' - description 'Check permissions for the current user on security testing features' - - abilities :admin_security_testing - end - end - end -end diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index e967281c168abf..9947bf23b8084d 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -560,7 +560,6 @@ module GroupPolicy rule { security_dashboard_enabled & can?(:maintainer_access) }.policy do enable :admin_vulnerability - enable :admin_security_testing end rule { custom_role_enables_read_dependency }.policy do @@ -575,36 +574,47 @@ module GroupPolicy enable :admin_vulnerability end - condition(:custom_ability_admin_security_testing) do - custom_roles_allowed? && custom_role_enables_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) & custom_ability_admin_security_testing }.policy do + rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do enable :access_security_and_compliance enable :read_compliance_dashboard enable :read_security_configuration + enable :read_group_security_dashboard + enable :read_security_resource + + # create sast configuration + enable :push_code + enable :download_code + enable :read_merge_request + enable :create_merge_request_from + enable :build_download_code + + # container_scanning_for_registry + enable :enable_container_scanning_for_registry + + # security_scans_api enable :access_security_scans_api + + # coverage_fuzzing + enable :read_coverage_fuzzing + enable :create_coverage_fuzzing_corpus + + # on_demand_scans enable :read_on_demand_dast_scan enable :create_on_demand_dast_scan enable :edit_on_demand_dast_scan + + enable :read_group_runners # read runner tags when creating scan + enable :create_pipeline # run a scan + 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_group_security_dashboard enable :read_project_security_exclusions - enable :read_coverage_fuzzing - enable :create_coverage_fuzzing_corpus - enable :enable_container_scanning_for_registry - enable :push_code - enable :create_merge_request_from - enable :create_pipeline - enable :build_download_code - enable :read_merge_request - enable :download_code - enable :read_group_runners - enable :read_pre_receive_secret_detection_info end rule { custom_role_enables_admin_group_member }.policy do diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index c3a778798bc8fc..adec22917eb3f3 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -460,37 +460,50 @@ module ProjectPolicy enable :admin_security_testing end - condition(:custom_ability_admin_security_testing) do - custom_roles_allowed? && custom_role_enables_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) & custom_ability_admin_security_testing }.policy do + rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do enable :access_security_and_compliance - enable :read_compliance_dashboard enable :read_security_configuration - enable :access_security_scans_api - enable :read_on_demand_dast_scan - enable :create_on_demand_dast_scan - enable :edit_on_demand_dast_scan - enable :enable_pre_receive_secret_detection + enable :read_compliance_dashboard enable :read_project_security_dashboard - enable :read_project_security_exclusions - enable :read_coverage_fuzzing - enable :create_coverage_fuzzing_corpus - enable :enable_container_scanning_for_registry + enable :read_security_resource + + # create sast configuration enable :push_code - enable :create_merge_request_from - enable :create_pipeline - enable :build_download_code - enable :read_merge_request enable :download_code - enable :read_project_runners - enable :read_runner + 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 diff --git a/ee/config/custom_abilities/admin_security_testing.yml b/ee/config/custom_abilities/admin_security_testing.yml index acdd1adbd3e6f8..91fe93e2e32823 100644 --- a/ee/config/custom_abilities/admin_security_testing.yml +++ b/ee/config/custom_abilities/admin_security_testing.yml @@ -5,8 +5,8 @@ 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.8" +milestone: "17.9" group_ability: true project_ability: true requirements: [] -available_from_access_level: 40 +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 index 66113dabc82109..8bda7bdf94ad6f 100644 --- a/ee/config/feature_flags/beta/custom_ability_admin_security_testing.yml +++ b/ee/config/feature_flags/beta/custom_ability_admin_security_testing.yml @@ -3,7 +3,7 @@ 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.7' +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 d26115313359a1..15bc97a367f078 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -3958,7 +3958,11 @@ def create_member_role(member, abilities = member_role_abilities) context 'for a custom role with the `admin_security_testing` ability' do let(:member_role_abilities) { { admin_security_testing: true } } - let(:licensed_features) { { security_dashboard: true } } + let(:licensed_features) do + { security_dashboard: true, + pre_receive_secret_detection: true, + group_level_compliance_dashboard: true } + end let(:allowed_abilities) do [ diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 1e5269535fea30..6d7e9d8ce4cac5 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -3115,7 +3115,16 @@ def create_member_role(member, abilities = member_role_abilities) context 'for a custom role with the `admin_security_testing` ability' do let(:member_role_abilities) { { admin_security_testing: true } } - let(:licensed_features) { { security_dashboard: 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, @@ -3137,7 +3146,6 @@ def create_member_role(member, abilities = member_role_abilities) :read_merge_request, :download_code, :read_project_runners, - :read_runner, :read_pre_receive_secret_detection_info ] end @@ -3145,6 +3153,8 @@ def create_member_role(member, abilities = member_role_abilities) before do # 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 it_behaves_like 'custom roles abilities' 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 index 06aff9ce8566db..8dbd6af84abe98 100644 --- a/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb +++ b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb @@ -22,7 +22,8 @@ pre_receive_secret_detection: true, container_scanning_for_registry: true, project_level_compliance_dashboard: true, - group_level_compliance_dashboard: true) + group_level_compliance_dashboard: true, + coverage_fuzzing: true) stub_feature_flags(custom_ability_admin_security_testing: true) sign_in(user) end -- GitLab From bd7cef25232091a0239bc4726e9ee6073ad2070b Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 21 Jan 2025 16:56:37 +0200 Subject: [PATCH 16/26] Fix the custom roles docs --- doc/user/custom_roles/abilities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index fd7fd51fbadebe..c7c67bd1e85987 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,7 +80,7 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Manage security testing | Edit and manage security testing configurations and settings. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | Group,
Project | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | +| Manage security testing | Edit and manage security testing configurations and settings. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | Group,
Project | GitLab [17.9](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | Link to a security policy project | Allows linking security policy projects. | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab From 3919d64188b3324eb41cebb40ce9819634c2b8b3 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 21 Jan 2025 20:22:06 +0200 Subject: [PATCH 17/26] Fix minor CR comments --- doc/api/member_roles.md | 2 +- ee/app/policies/ee/group_policy.rb | 2 +- ee/app/policies/ee/project_policy.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index 057343fdd0ce6f..926ab758126b4c 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -25,7 +25,7 @@ DETAILS: > - [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. -> - [Admin security testing introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) in GitLab 17.8. +> - [Admin security testing introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) in GitLab 17.9. Use this API to interact with member roles for your GitLab.com groups or entire self-managed instance. diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 9947bf23b8084d..67d9de951315c0 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -585,7 +585,7 @@ module GroupPolicy enable :read_group_security_dashboard enable :read_security_resource - # create sast configuration + # create scanner configuration enable :push_code enable :download_code enable :read_merge_request diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index adec22917eb3f3..65f18a63067a91 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -471,7 +471,7 @@ module ProjectPolicy enable :read_project_security_dashboard enable :read_security_resource - # create sast configuration + # create scanner configuration enable :push_code enable :download_code enable :read_merge_request -- GitLab From 38864e096e844a89ee27d3109ee9a39c55e287cb Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Wed, 22 Jan 2025 11:46:09 +0200 Subject: [PATCH 18/26] Fix the group policy and the test of the group policy --- ee/app/policies/ee/group_policy.rb | 28 +------------------ .../admin_security_testing.yml | 1 + ee/spec/policies/group_policy_spec.rb | 19 ++----------- 3 files changed, 4 insertions(+), 44 deletions(-) diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 67d9de951315c0..438067b39caaa0 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 @@ -584,37 +585,10 @@ module GroupPolicy enable :read_security_configuration enable :read_group_security_dashboard enable :read_security_resource - - # create scanner configuration - enable :push_code - enable :download_code - enable :read_merge_request - enable :create_merge_request_from - enable :build_download_code - - # container_scanning_for_registry - enable :enable_container_scanning_for_registry - - # security_scans_api - enable :access_security_scans_api - - # coverage_fuzzing - enable :read_coverage_fuzzing - enable :create_coverage_fuzzing_corpus - - # on_demand_scans - enable :read_on_demand_dast_scan - enable :create_on_demand_dast_scan - enable :edit_on_demand_dast_scan - - enable :read_group_runners # read runner tags when creating scan - enable :create_pipeline # run a scan 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 { custom_role_enables_admin_group_member }.policy do diff --git a/ee/config/custom_abilities/admin_security_testing.yml b/ee/config/custom_abilities/admin_security_testing.yml index 91fe93e2e32823..21d9bde664b709 100644 --- a/ee/config/custom_abilities/admin_security_testing.yml +++ b/ee/config/custom_abilities/admin_security_testing.yml @@ -9,4 +9,5 @@ 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/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index 15bc97a367f078..41925bfb8d60dc 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -3969,24 +3969,9 @@ def create_member_role(member, abilities = member_role_abilities) :access_security_and_compliance, :read_compliance_dashboard, :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_group_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_group_runners, - :read_pre_receive_secret_detection_info + :read_security_resource, + :enable_pre_receive_secret_detection ] end -- GitLab From ab904611a0515d6abe82920fc46f9b9c50533ba7 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Mon, 27 Jan 2025 20:28:48 +0200 Subject: [PATCH 19/26] Fix the feature flag tests and doc --- doc/api/member_roles.md | 2 +- .../admin_security_testing/request_spec.rb | 59 +++++-------------- 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index 926ab758126b4c..b0db271ba2bef1 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -25,7 +25,7 @@ DETAILS: > - [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. -> - [Admin security testing introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) in GitLab 17.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/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb index 8dbd6af84abe98..9172925f6f8e1d 100644 --- a/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb +++ b/ee/spec/requests/custom_roles/admin_security_testing/request_spec.rb @@ -3,15 +3,12 @@ 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(:group) { create(:group) } - let_it_be(:project) { create(:project, group: group) } - let_it_be_with_reload(:role) { create(:member_role, :guest, admin_security_testing: true, namespace: group) } - let_it_be(:member) { create(:group_member, :guest, user: user, source: group, member_role: role) } - let_it_be(:project_member) { create(:project_member, :guest, user: user, source: project) } - let_it_be(:member_role) do - create(:member_role, :guest, admin_security_testing: true, members: [project_member], namespace: project.group) - end + + 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( @@ -21,28 +18,12 @@ security_scans_api: true, pre_receive_secret_detection: true, container_scanning_for_registry: true, - project_level_compliance_dashboard: true, - group_level_compliance_dashboard: true, coverage_fuzzing: true) stub_feature_flags(custom_ability_admin_security_testing: true) sign_in(user) end describe "Controllers endpoints" do - describe Projects::Security::ComplianceDashboardsController do - it 'can access the show endpoint' do - get namespace_project_security_compliance_dashboard_path(group, project) - expect(response).to have_gitlab_http_status(:ok) - end - end - - describe Groups::Security::ComplianceDashboardsController do - it 'can access the show endpoint' do - get group_security_compliance_dashboard_path(group) - expect(response).to have_gitlab_http_status(:ok) - end - end - describe Projects::Security::ApiFuzzingConfigurationController do it 'can access the show endpoint' do get project_security_configuration_api_fuzzing_path(project) @@ -103,17 +84,7 @@ describe 'GraphQL mutations' do include GraphqlHelpers - let_it_be_with_refind(:policy_project) { create(:project, group: group) } let(:full_path) { project.full_path } - let(:base_params) do - { - project_path: full_path - - } - end - - let(:additional_params) { {} } - let(:input) { base_params.merge(additional_params) } let(:fields) do <<~FIELDS @@ -124,55 +95,55 @@ let(:mutation_name) { nil } let(:mutation) { graphql_mutation(mutation_name, input, fields) } - let_it_be_with_refind(:policy_project_id) { GitlabSchema.id_from_object(policy_project).to_s } - 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(response).to have_gitlab_http_status(:ok) + 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(response).to have_gitlab_http_status(:ok) + expect_graphql_errors_to_be_empty end end describe Mutations::Security::CiConfiguration::SetContainerScanningForRegistry do let(:mutation_name) { :setContainerScanningForRegistry } - let(:additional_params) { { namespace_path: group.full_path, enable: true } } + let(:input) { { namespace_path: full_path, enable: true } } it 'has access via a custom role' do execute_mutation - expect(response).to have_gitlab_http_status(:ok) + expect_graphql_errors_to_be_empty end end describe Mutations::Security::CiConfiguration::SetGroupSecretPushProtection do let(:mutation_name) { :setGroupSecretPushProtection } - let(:additional_params) { { namespace_path: group.full_path, secret_push_protection_enabled: true } } + let(:input) { { namespace_path: group.full_path, secret_push_protection_enabled: true } } it 'has access via a custom role' do execute_mutation - expect(response).to have_gitlab_http_status(:ok) + expect_graphql_errors_to_be_empty end end describe Mutations::Security::CiConfiguration::SetPreReceiveSecretDetection do let(:mutation_name) { :setPreReceiveSecretDetection } - let(:additional_params) { { namespace_path: full_path, enable: true } } + let(:input) { { namespace_path: full_path, enable: true } } it 'has access via a custom role' do execute_mutation - expect(response).to have_gitlab_http_status(:ok) + expect_graphql_errors_to_be_empty end end end -- GitLab From 44f838607a89650b7d136f27ca581584fcf5c7b7 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 28 Jan 2025 11:04:24 +0200 Subject: [PATCH 20/26] Fix project policy test brack in the rebase --- ee/spec/policies/project_policy_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 6d7e9d8ce4cac5..3d34a18de58007 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -3010,7 +3010,7 @@ def create_member_role(member, abilities = member_role_abilities) } end - let(:member_role_abilities) { { admin_compliance_framework: true } } + let(:member_role_abilities) { { read_compliance_dashboard: true, admin_compliance_framework: true } } let(:allowed_abilities) do [ -- GitLab From a11b4873a7a0558c89df3540a7685d09801b6e90 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 28 Jan 2025 12:42:51 +0200 Subject: [PATCH 21/26] Fix project and group policy by removing read_compliance_dashboard permission --- ee/app/policies/ee/group_policy.rb | 1 - ee/app/policies/ee/project_policy.rb | 1 - ee/spec/policies/group_policy_spec.rb | 1 - 3 files changed, 3 deletions(-) diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 438067b39caaa0..f686c2414a6da4 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -581,7 +581,6 @@ module GroupPolicy rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do enable :access_security_and_compliance - enable :read_compliance_dashboard enable :read_security_configuration enable :read_group_security_dashboard enable :read_security_resource diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 65f18a63067a91..5e511e76fc0199 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -467,7 +467,6 @@ module ProjectPolicy rule { security_dashboard_enabled & can?(:admin_security_testing) }.policy do enable :access_security_and_compliance enable :read_security_configuration - enable :read_compliance_dashboard enable :read_project_security_dashboard enable :read_security_resource diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index 41925bfb8d60dc..a0785058b827bf 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -3967,7 +3967,6 @@ def create_member_role(member, abilities = member_role_abilities) let(:allowed_abilities) do [ :access_security_and_compliance, - :read_compliance_dashboard, :read_security_configuration, :read_group_security_dashboard, :read_security_resource, -- GitLab From 58922da5e898ae1e64e8d40aa497face451fc53f Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 28 Jan 2025 13:04:26 +0200 Subject: [PATCH 22/26] Fix member roles doc lint --- doc/api/member_roles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index b0db271ba2bef1..7a03686cdeb016 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -25,7 +25,7 @@ DETAILS: > - [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. -> - [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. +> - [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. -- GitLab From 48e8202977f57faef1f6bbbc279d1e57bdce38a4 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 28 Jan 2025 13:14:11 +0200 Subject: [PATCH 23/26] Try to fix member roles doc lint --- doc/api/member_roles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index 7a03686cdeb016..1d2408bda353ae 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -24,7 +24,7 @@ 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. -- GitLab From d5c11beade97eb8059985c8ab8263bb34be42696 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Wed, 29 Jan 2025 11:37:56 +0200 Subject: [PATCH 24/26] Fix the abilities doc --- doc/user/custom_roles/abilities.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index c7c67bd1e85987..58fd6de1cd29b2 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,7 +80,6 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Manage security testing | Edit and manage security testing configurations and settings. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | Group,
Project | GitLab [17.9](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | Link to a security policy project | Allows linking security policy projects. | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab From 26d2d256cbf3a0394643ce4b8242fedc13ffaef3 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Wed, 29 Jan 2025 11:39:50 +0200 Subject: [PATCH 25/26] Compile the abilities doc --- doc/user/custom_roles/abilities.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index 58fd6de1cd29b2..c7c67bd1e85987 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,6 +80,7 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| +| Manage security testing | Edit and manage security testing configurations and settings. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | Group,
Project | GitLab [17.9](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | Link to a security policy project | Allows linking security policy projects. | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab From 306efe21034c87af7a7f4eeb6bf7b617ee88bc0d Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Wed, 29 Jan 2025 13:24:27 +0200 Subject: [PATCH 26/26] Fix the abilities doc --- doc/user/custom_roles/abilities.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index c7c67bd1e85987..58fd6de1cd29b2 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -80,7 +80,6 @@ Any dependencies are noted in the `Description` column for each permission. | Permission | Description | API Attribute | Scope | Introduced | |:-----------|:------------|:--------------|:------|:-----------| -| Manage security testing | Edit and manage security testing configurations and settings. | [`admin_security_testing`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176628) | Group,
Project | GitLab [17.9](https://gitlab.com/gitlab-org/gitlab/-/issues/508649) | | Link to a security policy project | Allows linking security policy projects. | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,
Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | ## Source code management -- GitLab