ee/spec/policies/project_policy_spec.rb (4,013 lines of code) (raw):

# frozen_string_literal: true require 'spec_helper' RSpec.describe ProjectPolicy, feature_category: :system_access do include ExternalAuthorizationServiceHelpers include AdminModeHelper include_context 'ProjectPolicy context' using RSpec::Parameterized::TableSyntax let(:project) { public_project } let_it_be(:auditor) { create(:user, :auditor) } subject { described_class.new(current_user, project) } before do stub_licensed_features( license_scanning: true, quality_management: true, cycle_analytics_for_projects: true ) end context 'basic permissions' do let(:additional_guest_permissions) { %i[read_limit_alert] } let(:additional_reporter_permissions) do %i[read_software_license_policy admin_value_stream read_product_analytics read_path_locks] end let(:additional_developer_permissions) do %i[ admin_vulnerability_feedback read_project_audit_events read_project_security_dashboard admin_vulnerability_issue_link admin_vulnerability_external_issue_link read_security_resource read_vulnerability_scanner admin_vulnerability read_vulnerability create_vulnerability_export read_merge_train create_path_locks ] end let(:additional_maintainer_permissions) do %i[push_code_to_protected_branches] end let(:auditor_permissions) do %i[ download_code download_wiki_code read_project read_project_metadata read_issue_board read_issue_board_list read_project_for_iids read_issue_iid read_merge_request_iid read_wiki read_issue read_label read_issue_link read_milestone read_snippet read_project_member read_note read_cycle_analytics read_pipeline read_build read_commit_status read_container_image read_environment read_deployment read_merge_request read_pages award_emoji read_project_security_dashboard read_security_resource read_vulnerability_scanner read_software_license_policy read_merge_train read_release read_project_audit_events read_cluster read_terraform_state read_project_merge_request_analytics read_on_demand_dast_scan read_alert_management_alert ] end it_behaves_like 'project policies as anonymous' it_behaves_like 'project policies as guest' it_behaves_like 'project policies as planner' it_behaves_like 'project policies as reporter' it_behaves_like 'project policies as developer' it_behaves_like 'project policies as maintainer' it_behaves_like 'project policies as owner' it_behaves_like 'project policies as admin with admin mode' it_behaves_like 'project policies as admin without admin mode' context 'auditor' do let(:current_user) { auditor } let(:auditor_permission_exclusions) { [:fork_project, :create_merge_request_in] } let(:auditor_as_guest_exclusions) do %i[create_note read_confidential_issues create_project create_issue create_note upload_file admin_issue_link] end before do stub_licensed_features(security_dashboard: true, license_scanning: true) end context 'who is not a team member' do it do is_expected.to be_disallowed(*(developer_permissions - auditor_permissions)) is_expected.to be_disallowed(*maintainer_permissions) is_expected.to be_disallowed(*owner_permissions) is_expected.to be_disallowed(*(guest_permissions - auditor_permissions)) is_expected.to be_disallowed(*(planner_permissions - auditor_permissions - [:read_confidential_issues])) is_expected.to be_allowed(*auditor_permission_exclusions) is_expected.to be_allowed(*auditor_permissions) end context 'with private project' do let(:project) { private_project } let(:auditor_permission_exclusions) { [:fork_project, :create_merge_request_in, :read_project_for_iids] } it do is_expected.to be_disallowed(*(developer_permissions - auditor_permissions)) is_expected.to be_disallowed(*maintainer_permissions) is_expected.to be_disallowed(*owner_permissions) is_expected.to be_disallowed(*(guest_permissions - auditor_permissions)) is_expected.to be_disallowed(*(planner_permissions - auditor_permissions - [:read_confidential_issues])) is_expected.to be_disallowed(*auditor_permission_exclusions) is_expected.to be_allowed(*(auditor_permissions - auditor_permission_exclusions)) end end end context 'who is a team member' do before do project.add_guest(current_user) end it do is_expected.to be_disallowed(*(developer_permissions - auditor_permissions)) is_expected.to be_disallowed(*maintainer_permissions) is_expected.to be_disallowed(*owner_permissions) is_expected.to be_disallowed(*(planner_permissions - auditor_permissions - auditor_as_guest_exclusions)) is_expected.to be_allowed(*(guest_permissions - auditor_permissions)) is_expected.to be_allowed(*auditor_permissions) end end it_behaves_like 'project private features with read_all_resources ability' do let(:user) { current_user } end context 'with project feature related policies' do # Required parameters: # - project_feature: Hash defining project feature mapping abilities. shared_examples 'project feature visibility' do |project_features| # For each project feature, check that an auditor is always allowed read # permissions unless the feature is disabled. project_features.each do |feature, permissions| context "with project feature #{feature}" do where(:project_visibility, :access_level, :allowed) do :public | ProjectFeature::ENABLED | true :public | ProjectFeature::PRIVATE | true :public | ProjectFeature::DISABLED | false :internal | ProjectFeature::ENABLED | true :internal | ProjectFeature::PRIVATE | true :internal | ProjectFeature::DISABLED | false :private | ProjectFeature::ENABLED | true :private | ProjectFeature::PRIVATE | true :private | ProjectFeature::DISABLED | false end with_them do let(:project) { send("#{project_visibility}_project") } it 'always allows permissions except when feature disabled' do project.project_feature.update!("#{feature}": access_level) if allowed expect_allowed(*permissions) else expect_disallowed(*permissions) end end end end end end include_examples 'project feature visibility', { container_registry_access_level: [:read_container_image], merge_requests_access_level: [:read_merge_request], monitor_access_level: [:read_alert_management_alert] } end end end context 'iterations' do context 'in a personal project' do let(:current_user) { owner } context 'when feature is disabled' do before do stub_licensed_features(iterations: false) end it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) } end context 'when feature is enabled' do before do stub_licensed_features(iterations: true) end it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) } end end context 'in a group project' do let(:project) { public_project_in_group } let(:current_user) { maintainer } context 'when feature is disabled' do before do stub_licensed_features(iterations: false) end it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) } end context 'when feature is enabled' do before do stub_licensed_features(iterations: true) end it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) } context 'when issues are disabled but merge requests are enabled' do before do project.update!(issues_enabled: false) end it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) } end context 'when issues are enabled but merge requests are enabled' do before do project.update!(merge_requests_enabled: false) end it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) } end context 'when both issues and merge requests are disabled' do before do project.update!(issues_enabled: false, merge_requests_enabled: false) end it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) } end where(:the_user, :allowed, :disallowed) do ref(:developer) | [:read_iteration, :create_iteration, :admin_iteration] | [] ref(:planner) | [:read_iteration, :create_iteration, :admin_iteration] | [] ref(:guest) | [:read_iteration] | [:create_iteration, :admin_iteration] ref(:non_member) | [:read_iteration] | [:create_iteration, :admin_iteration] ref(:anonymous) | [:read_iteration] | [:create_iteration, :admin_iteration] end with_them do let(:current_user) { the_user } it { is_expected.to be_allowed(*allowed) } it { is_expected.to be_disallowed(*disallowed) } end context 'when the project is private' do let(:project) { private_project } context 'when user is not a member' do let(:current_user) { non_member } it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) } end context 'when user is logged out' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) } end end end end end context 'issues feature' do let(:current_user) { owner } context 'when the feature is disabled' do before do project.update!(issues_enabled: false) end it 'disables boards permissions' do expect_disallowed :admin_issue_board, :create_test_case end it 'disables issues analytics' do expect_disallowed :read_issues_analytics end end end context 'merge requests feature' do let(:current_user) { owner } let_it_be(:group) { create(:group, :private) } let_it_be(:project) { create(:project, group: group) } context 'when the feature is disabled' do before do project.update!(merge_requests_enabled: false) end it 'disables issues analytics' do expect_disallowed :read_project_merge_request_analytics end end end context 'admin_mirror' do context 'with remote mirror setting enabled' do context 'with admin' do let(:current_user) { admin } context 'when admin mode enabled', :enable_admin_mode do it { is_expected.to be_allowed(:admin_mirror) } end context 'when admin mode disabled' do it { is_expected.to be_disallowed(:admin_mirror) } end end context 'with owner' do let(:current_user) { owner } it { is_expected.to be_allowed(:admin_mirror) } end context 'with developer' do let(:current_user) { developer } it { is_expected.to be_disallowed(:admin_mirror) } end end context 'with remote mirror setting disabled' do before do stub_application_setting(mirror_available: false) end context 'with admin' do let(:current_user) { admin } context 'when admin mode enabled', :enable_admin_mode do it { is_expected.to be_allowed(:admin_mirror) } end context 'when admin mode disabled' do it { is_expected.to be_disallowed(:admin_mirror) } end end context 'with owner' do let(:current_user) { owner } it { is_expected.to be_disallowed(:admin_mirror) } end end context 'with remote mirrors feature disabled' do before do stub_licensed_features(repository_mirrors: false) end context 'with admin' do let(:current_user) { admin } it { is_expected.to be_disallowed(:admin_mirror) } end context 'with owner' do let(:current_user) { owner } it { is_expected.to be_disallowed(:admin_mirror) } end end context 'with remote mirrors feature enabled' do before do stub_licensed_features(repository_mirrors: true) end context 'with admin' do let(:current_user) { admin } context 'when admin mode enabled', :enable_admin_mode do it { is_expected.to be_allowed(:admin_mirror) } end context 'when admin mode disabled' do it { is_expected.to be_disallowed(:admin_mirror) } end end context 'with owner' do let(:current_user) { owner } it { is_expected.to be_allowed(:admin_mirror) } end end end context 'reading a project' do context 'with an external authorization service' do before do enable_external_authorization_service_check end it 'allows auditors' do stub_licensed_features(auditor_user: true) auditor = create(:user, :auditor) expect(described_class.new(auditor, project)).to be_allowed(:read_project) end end context 'when SAML SSO is enabled for resource' do let(:saml_provider) { create(:saml_provider, enabled: true, enforced_sso: false) } let(:identity) { create(:group_saml_identity, saml_provider: saml_provider) } let(:root_group) { saml_provider.group } let(:project) { create(:project, group: root_group) } let(:member_with_identity) { identity.user } let(:member_without_identity) { create(:user) } let(:project_member_without_identity) { create(:user) } let(:non_member) { create(:user) } let(:not_signed_in_user) { nil } before do stub_licensed_features(group_saml: true) root_group.add_developer(member_with_identity) root_group.add_developer(member_without_identity) project.add_developer(project_member_without_identity) end subject { described_class.new(current_user, resource) } shared_examples 'does not allow read project' do it 'does not allow read project' do is_expected.not_to allow_action(:read_project) end end shared_examples 'allows to read project' do it 'allows read project' do is_expected.to allow_action(:read_project) end end shared_examples 'does not allow to read project due to its visibility level' do it 'does not allow to read project due to its visibility level', :aggregate_failures do expect(resource.root_ancestor.saml_provider.enforced_sso?).to eq(false) is_expected.not_to allow_action(:read_project) end end # See https://docs.gitlab.com/ee/user/group/saml_sso/#sso-enforcement where(:resource, :resource_visibility_level, :enforced_sso?, :user, :user_is_resource_owner?, :user_with_saml_session?, :user_is_admin?, :enable_admin_mode?, :user_is_auditor?, :shared_examples) do # Project/Group visibility: Private; Enforce SSO setting: Off ref(:project) | 'private' | false | ref(:member_with_identity) | false | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | false | ref(:member_with_identity) | true | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | false | ref(:member_with_identity) | false | true | nil | nil | nil | 'allows to read project' ref(:project) | 'private' | false | ref(:member_with_identity) | false | false | true | false | nil | 'does not allow read project' ref(:project) | 'private' | false | ref(:member_with_identity) | false | false | true | true | nil | 'allows to read project' ref(:project) | 'private' | false | ref(:member_with_identity) | false | false | nil | nil | true | 'allows to read project' ref(:project) | 'private' | false | ref(:member_without_identity) | false | nil | nil | nil | nil | 'allows to read project' ref(:project) | 'private' | false | ref(:non_member) | nil | nil | nil | nil | nil | 'does not allow to read project due to its visibility level' ref(:project) | 'private' | false | ref(:non_member) | nil | nil | true | false | nil | 'does not allow to read project due to its visibility level' ref(:project) | 'private' | false | ref(:non_member) | nil | nil | true | true | nil | 'allows to read project' ref(:project) | 'private' | false | ref(:non_member) | nil | nil | nil | nil | true | 'allows to read project' ref(:project) | 'private' | false | ref(:not_signed_in_user) | nil | nil | nil | nil | nil | 'does not allow to read project due to its visibility level' # Project/Group visibility: Private; Enforce SSO setting: On ref(:project) | 'private' | true | ref(:member_with_identity) | false | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:member_with_identity) | true | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:member_with_identity) | false | true | nil | nil | nil | 'allows to read project' ref(:project) | 'private' | true | ref(:member_with_identity) | false | false | true | false | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:member_with_identity) | false | false | true | true | nil | 'allows to read project' ref(:project) | 'private' | true | ref(:member_with_identity) | false | false | nil | nil | true | 'allows to read project' ref(:project) | 'private' | true | ref(:member_without_identity) | false | nil | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:member_without_identity) | true | nil | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:member_without_identity) | false | nil | true | false | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:member_without_identity) | false | nil | true | true | nil | 'allows to read project' ref(:project) | 'private' | true | ref(:member_without_identity) | false | nil | nil | nil | true | 'allows to read project' ref(:project) | 'private' | true | ref(:project_member_without_identity) | false | nil | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:project_member_without_identity) | false | nil | true | false | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:project_member_without_identity) | false | nil | true | true | nil | 'allows to read project' ref(:project) | 'private' | true | ref(:project_member_without_identity) | false | nil | nil | nil | true | 'allows to read project' ref(:project) | 'private' | true | ref(:non_member) | nil | nil | nil | nil | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:non_member) | nil | nil | true | false | nil | 'does not allow read project' ref(:project) | 'private' | true | ref(:non_member) | nil | nil | true | true | nil | 'allows to read project' ref(:project) | 'private' | true | ref(:non_member) | nil | nil | nil | nil | true | 'allows to read project' ref(:project) | 'private' | true | ref(:not_signed_in_user) | nil | nil | nil | nil | nil | 'does not allow read project' # Project/Group visibility: Public; Enforce SSO setting: Off ref(:project) | 'public' | false | ref(:member_with_identity) | false | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'public' | false | ref(:member_with_identity) | true | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'public' | false | ref(:member_with_identity) | false | true | nil | nil | nil | 'allows to read project' ref(:project) | 'public' | false | ref(:member_with_identity) | false | false | true | false | nil | 'does not allow read project' ref(:project) | 'public' | false | ref(:member_with_identity) | false | false | true | true | nil | 'allows to read project' ref(:project) | 'public' | false | ref(:member_with_identity) | false | false | nil | nil | true | 'allows to read project' ref(:project) | 'public' | false | ref(:member_without_identity) | false | nil | nil | nil | nil | 'allows to read project' ref(:project) | 'public' | false | ref(:non_member) | nil | nil | nil | nil | nil | 'allows to read project' ref(:project) | 'public' | false | ref(:not_signed_in_user) | nil | nil | nil | nil | nil | 'allows to read project' # Project/Group visibility: Public; Enforce SSO setting: On ref(:project) | 'public' | true | ref(:member_with_identity) | false | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:member_with_identity) | true | false | nil | nil | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:member_with_identity) | false | true | nil | nil | nil | 'allows to read project' ref(:project) | 'public' | true | ref(:member_with_identity) | false | false | true | false | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:member_with_identity) | false | false | true | true | nil | 'allows to read project' ref(:project) | 'public' | true | ref(:member_with_identity) | false | false | nil | nil | true | 'allows to read project' ref(:project) | 'public' | true | ref(:member_without_identity) | false | nil | nil | nil | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:member_without_identity) | true | nil | nil | nil | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:member_without_identity) | false | nil | true | false | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:member_without_identity) | false | nil | true | true | nil | 'allows to read project' ref(:project) | 'public' | true | ref(:member_without_identity) | false | nil | nil | nil | true | 'allows to read project' ref(:project) | 'public' | true | ref(:project_member_without_identity) | false | nil | nil | nil | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:project_member_without_identity) | false | nil | true | false | nil | 'does not allow read project' ref(:project) | 'public' | true | ref(:project_member_without_identity) | false | nil | true | true | nil | 'allows to read project' ref(:project) | 'public' | true | ref(:project_member_without_identity) | false | nil | nil | nil | true | 'allows to read project' ref(:project) | 'public' | true | ref(:non_member) | nil | nil | nil | nil | nil | 'allows to read project' ref(:project) | 'public' | true | ref(:not_signed_in_user) | nil | nil | nil | nil | nil | 'allows to read project' end with_them do context "when 'Enforce SSO-only authentication for web activity for this group' option is #{params[:enforced_sso?] ? 'enabled' : 'not enabled'}" do around do |example| session = {} session['warden.user.user.key'] = [[user.id], user.authenticatable_salt] if user.is_a?(User) Gitlab::Session.with_session(session) do example.run end end before do saml_provider.update!(enforced_sso: enforced_sso?) end context "when resource is #{params[:resource_visibility_level]}" do before do resource.update!(visibility_level: Gitlab::VisibilityLevel.string_options[resource_visibility_level]) end context 'for user', enable_admin_mode: params[:enable_admin_mode?] do before do if user_is_resource_owner? resource.root_ancestor.member(user).update_column(:access_level, Gitlab::Access::OWNER) end Gitlab::Auth::GroupSaml::SsoEnforcer.new(saml_provider).update_session if user_with_saml_session? user.update!(admin: true) if user_is_admin? user.update!(auditor: true) if user_is_auditor? end let(:current_user) { user } include_examples params[:shared_examples] end end end end end context 'with ip restriction' do let(:current_user) { create(:admin) } let(:group) { create(:group, :public) } let(:project) { create(:project, group: group) } before do allow(Gitlab::IpAddressState).to receive(:current).and_return('192.168.0.2') stub_licensed_features(group_ip_restriction: true) group.add_maintainer(current_user) end context 'group without restriction' do it { is_expected.to be_allowed(:read_project) } it { is_expected.to be_allowed(:read_issue) } it { is_expected.to be_allowed(:read_merge_request) } it { is_expected.to be_allowed(:read_milestone) } it { is_expected.to be_allowed(:read_container_image) } it { is_expected.to be_allowed(:read_package) } it { is_expected.to be_allowed(:create_package) } it { is_expected.to be_allowed(:destroy_package) } it { is_expected.to be_allowed(:admin_package) } end context 'group with restriction' do before do create(:ip_restriction, group: group, range: range) end context 'address is within the range' do let(:range) { '192.168.0.0/24' } it { is_expected.to be_allowed(:read_project) } it { is_expected.to be_allowed(:read_issue) } it { is_expected.to be_allowed(:read_merge_request) } it { is_expected.to be_allowed(:read_milestone) } it { is_expected.to be_allowed(:read_container_image) } it { is_expected.to be_allowed(:create_container_image) } it { is_expected.to be_allowed(:read_package) } it { is_expected.to be_allowed(:create_package) } it { is_expected.to be_allowed(:destroy_package) } it { is_expected.to be_allowed(:admin_package) } end context 'address is outside the range' do let(:range) { '10.0.0.0/8' } it { is_expected.to be_disallowed(:read_project) } it { is_expected.to be_disallowed(:read_issue) } it { is_expected.to be_disallowed(:read_merge_request) } it { is_expected.to be_disallowed(:read_milestone) } it { is_expected.to be_disallowed(:read_container_image) } it { is_expected.to be_disallowed(:create_container_image) } it { is_expected.to be_disallowed(:read_package) } it { is_expected.to be_disallowed(:create_package) } it { is_expected.to be_disallowed(:destroy_package) } it { is_expected.to be_disallowed(:admin_package) } context 'with admin enabled', :enable_admin_mode do it { is_expected.to be_allowed(:read_project) } it { is_expected.to be_allowed(:read_issue) } it { is_expected.to be_allowed(:read_merge_request) } it { is_expected.to be_allowed(:read_milestone) } it { is_expected.to be_allowed(:read_container_image) } it { is_expected.to be_allowed(:create_container_image) } it { is_expected.to be_allowed(:read_package) } it { is_expected.to be_allowed(:create_package) } it { is_expected.to be_allowed(:destroy_package) } it { is_expected.to be_allowed(:admin_package) } end context 'with admin disabled' do it { is_expected.to be_disallowed(:read_project) } it { is_expected.to be_disallowed(:read_issue) } it { is_expected.to be_disallowed(:read_merge_request) } it { is_expected.to be_disallowed(:read_milestone) } it { is_expected.to be_disallowed(:read_container_image) } it { is_expected.to be_disallowed(:create_container_image) } it { is_expected.to be_disallowed(:read_package) } it { is_expected.to be_disallowed(:create_package) } it { is_expected.to be_disallowed(:destroy_package) } it { is_expected.to be_disallowed(:admin_package) } end context 'with auditor' do let(:current_user) { create(:user, :auditor) } it { is_expected.to be_allowed(:read_project) } it { is_expected.to be_allowed(:read_issue) } it { is_expected.to be_allowed(:read_merge_request) } it { is_expected.to be_allowed(:read_milestone) } it { is_expected.to be_allowed(:read_container_image) } it { is_expected.to be_allowed(:create_container_image) } it { is_expected.to be_allowed(:read_package) } it { is_expected.to be_allowed(:create_package) } it { is_expected.to be_allowed(:destroy_package) } it { is_expected.to be_allowed(:admin_package) } end end end context 'without group' do let(:project) { create(:project, :repository, namespace: current_user.namespace) } it { is_expected.to be_allowed(:read_project) } end end end describe 'access_security_and_compliance' do shared_examples 'correct access to security and compliance' do before do project.project_feature.update!(security_and_compliance_access_level: access_level) end context 'when "Security and compliance" is disabled' do let(:access_level) { Featurable::DISABLED } it { is_expected.to be_disallowed(:access_security_and_compliance) } it { is_expected.to be_disallowed(:admin_vulnerability) } it { is_expected.to be_disallowed(:read_vulnerability) } end context 'when "Security and compliance" is enabled' do let(:access_level) { Featurable::PRIVATE } it { is_expected.to be_allowed(:access_security_and_compliance) } end end context 'when the user is developer' do let(:current_user) { developer } it_behaves_like 'correct access to security and compliance' end context 'when the user has a custom role that enables read_vulnerability' do let(:current_user) { guest } let_it_be(:project) { create(:project, :in_group) } before do stub_licensed_features(custom_roles: true) project_member = create(:project_member, :guest, user: current_user, source: project) create(:member_role, :guest, read_vulnerability: true, members: [project_member], namespace: project.group) end it_behaves_like 'correct access to security and compliance' end context 'when the user is auditor' do let(:current_user) { create(:user, :auditor) } it_behaves_like 'correct access to security and compliance' 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) } end end %w[owner maintainer].each do |role| context "with #{role}" do let(:current_user) { send(role) } it { is_expected.to be_allowed(permission) } end end %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) } 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) } end end describe 'vulnerability permissions' 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) } it { is_expected.to be_disallowed(:create_vulnerability_archive_export) } 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 end it { is_expected.to be_allowed(*permissions) } context 'with user other than security bot' do let_it_be(:current_user) { create(:user) } it { is_expected.to be_disallowed(*permissions) } 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]) 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 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 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 context 'with auditor role' do where(role: %w[auditor]) 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 security_orchestration_policy_configuration is present' do let_it_be(:security_policy_management_project) { create(:project) } let(:current_user) { developer } before do create(:security_orchestration_policy_configuration, project: project, security_policy_management_project: security_policy_management_project) end context 'when current_user is guest of security_policy_management_project' do let(:project) { security_policy_management_project } before do security_policy_management_project.add_guest(developer) end it { is_expected.to be_disallowed(:read_security_orchestration_policy_project) } it { is_expected.to be_disallowed(:modify_security_policy) } end context 'when current_user is reporter of security_policy_management_project' do let(:project) { security_policy_management_project } before do security_policy_management_project.add_reporter(developer) end it { is_expected.to be_allowed(:read_security_orchestration_policy_project) } it { is_expected.to be_disallowed(:modify_security_policy) } end context 'when current_user is developer of security_policy_management_project' do let(:project) { security_policy_management_project } before do security_policy_management_project.add_developer(developer) end it { is_expected.to be_allowed(:modify_security_policy) } end context 'when current_user is not member of security_policy_management_project' do let(:project) { security_policy_management_project } it { is_expected.to be_disallowed(:read_security_orchestration_policy_project) } it { is_expected.to be_disallowed(:modify_security_policy) } end end 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]) with_them do let(:current_user) { public_send(role) } it { is_expected.to be_allowed(:read_coverage_fuzzing) } end end 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 disabled' do it { is_expected.to be_disallowed(:read_coverage_fuzzing) } end end context 'with less than developer role' do where(role: %w[reporter planner guest]) 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 context 'with anonymous' do let(:current_user) { anonymous } it { is_expected.to be_disallowed(:read_coverage_fuzzing) } end end 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 describe 'remove_project when default_project_deletion_protection is set to true' do before do stub_application_setting(default_project_deletion_protection: true) end context 'with admin' do let(:current_user) { admin } context 'when admin mode enabled', :enable_admin_mode do it { is_expected.to be_allowed(:remove_project) } end context 'when admin mode disabled' do it { is_expected.to be_disallowed(:remove_project) } context 'and admin owns the project' do let_it_be(:project) { create(:project, :public, namespace: admin.namespace) } it { is_expected.to be_disallowed(:remove_project) } end 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 context 'with maintainer' do let(:current_user) { maintainer } 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 it { is_expected.to be_disallowed(:admin_feature_flags_issue_links) } end end context 'with developer' do let(:current_user) { developer } 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) 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 describe 'admin_software_license_policy' do context 'without license scanning feature available' do before do stub_licensed_features(license_scanning: false) 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 '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) } end end %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) } end end %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) } end end end describe 'read_software_license_policy' do context 'without license scanning feature available' do before do stub_licensed_features(license_scanning: false) end let(:current_user) { admin } it { is_expected.to be_disallowed(:read_software_license_policy) } end 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 access to repository' do let(:project) { public_project } it { is_expected.to be_allowed(:read_dependency) } end context 'with limited access to repository' do let(:project) { create(:project, :public, :repository_private) } it { is_expected.not_to be_allowed(:read_dependency) } end end context 'with private project' do let(:project) { private_project } 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 disabled' do it { is_expected.to be_disallowed(:read_dependency) } end end %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) } end end %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) } end end end end context 'when dependency list feature not available' do let(:current_user) { admin } it { is_expected.not_to be_allowed(:read_dependency) } end end describe 'read_licenses' do context 'when license management feature available' do context 'with public project' do let(:current_user) { non_member } context 'with public access to repository' do it { is_expected.to be_allowed(:read_licenses) } 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) } it { is_expected.to be_allowed(:read_licenses) } end 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 %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) } 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 describe 'publish_status_page' do let(:feature) { :status_page } let(:policy) { :publish_status_page } 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 with_them do let(:current_user) { public_send(role) if role } before do stub_licensed_features(feature => true) enable_admin_mode!(current_user) if admin_mode end 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 describe 'add_project_to_instance_security_dashboard' do let(:policy) { :add_project_to_instance_security_dashboard } context 'when user is auditor' do let(:current_user) { create(:user, :auditor) } it { is_expected.to be_allowed(policy) } end context 'when user is not auditor' do context 'with developer access' do let(:current_user) { developer } 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 'visual review bot' do let(:current_user) { Users::Internal.visual_review_bot } it { expect_disallowed(:create_note) } it { expect_disallowed(:read_note) } it { expect_disallowed(:resolve_note) } end context 'when push_rules is not enabled by the current license' do before do stub_licensed_features(push_rules: false) end let(:current_user) { maintainer } it { is_expected.to be_disallowed(:change_push_rules) } end context 'when push_rules is enabled by the current license' do before do stub_licensed_features(push_rules: true) end let(:current_user) { maintainer } 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 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 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 } it { is_expected.to be_disallowed(:read_commit_committer_name_check) } it { is_expected.to be_disallowed(:change_commit_committer_name_check) } end end context 'reject_unsigned_commits is not enabled by the current license' do before do stub_licensed_features(reject_unsigned_commits: false) end let(:current_user) { maintainer } it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) } it { is_expected.not_to be_allowed(:read_reject_unsigned_commits) } end context 'reject_unsigned_commits is enabled by the current license' do before do stub_licensed_features(reject_unsigned_commits: true) 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 '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) 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 '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 context 'when the user is a maintainer' do let(:current_user) { maintainer } it { is_expected.to be_allowed(:read_reject_non_dco_commits) } it { is_expected.to be_allowed(:change_reject_non_dco_commits) } end 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 'when dora4 analytics is available' do before do stub_licensed_features(dora4_analytics: true) end context 'when the user is a developer' do let(:current_user) { developer } it { is_expected.to be_allowed(:read_dora4_analytics) } 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 dora4 analytics is not available' do let(:current_user) { developer } before do stub_licensed_features(dora4_analytics: false) end it { is_expected.not_to be_allowed(:read_dora4_analytics) } end describe ':read_enterprise_ai_analytics' do let(:project) { private_project_in_group } let(:guest) { inherited_guest } let(:planner) { inherited_planner } let(:reporter) { inherited_reporter } context 'when on SAAS', :saas do let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } it_behaves_like 'ai permission to', :read_enterprise_ai_analytics end context '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 describe ':read_pro_ai_analytics' do let(:project) { private_project_in_group } let(:guest) { inherited_guest } let(:reporter) { inherited_reporter } context 'when on SAAS', :saas do context 'with pro subscription' do let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_pro, namespace: group) } it_behaves_like 'ai permission to', :read_pro_ai_analytics end context 'with enterprise subscription' do let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) } it_behaves_like 'ai permission to', :read_pro_ai_analytics end end context 'when on self-managed' do context 'with pro subscription' do let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_pro, :self_managed) } it_behaves_like 'ai permission to', :read_pro_ai_analytics end context 'with enterprise subscription' do let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) } it_behaves_like 'ai permission to', :read_pro_ai_analytics end end end describe ':read_code_review_analytics' do let(:policy) { :read_code_review_analytics } 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' | true end with_them do let(:current_user) { public_send(role) } let(:project) { public_send(:"#{project_visibility}_project") } before do enable_admin_mode!(current_user) if role == :admin end it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } context 'with code review analytics is not available in license' do before do stub_licensed_features(code_review_analytics: false) end it { is_expected.to be_disallowed(:read_code_review_analytics) } end end end shared_examples 'merge request approval settings' do |admin_override_allowed = false| let(:project) { private_project } context 'with merge request approvers rules available in license' do where(:role, :setting, :admin_mode, :allowed) do :guest | true | nil | false :planner | true | nil | false :reporter | true | nil | false :developer | true | nil | false :maintainer | false | nil | true :maintainer | true | nil | false :owner | false | nil | true :owner | true | nil | false :admin | false | false | false :admin | false | true | true :admin | true | false | false :admin | true | true | admin_override_allowed end with_them do let(:current_user) { public_send(role) } before do stub_licensed_features(admin_merge_request_approvers_rules: true) stub_application_setting(app_setting => setting) enable_admin_mode!(current_user) if admin_mode end it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end context 'with merge request approvers rules not available in license' do where(:role, :setting, :admin_mode, :allowed) do :guest | true | nil | false :planner | true | nil | false :reporter | true | nil | false :developer | true | nil | false :maintainer | false | nil | true :maintainer | true | nil | true :owner | false | nil | true :owner | true | nil | true :admin | false | false | false :admin | false | true | true :admin | true | false | false :admin | true | true | true 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 it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end end describe ':admin_merge_request_approval_settings' do let(:project) { private_project } where(:role, :licensed, :allowed) do :guest | true | false :planner | true | false :reporter | true | false :developer | true | false :maintainer | false | false :maintainer | true | true :owner | false | false :owner | true | true :admin | true | true :admin | false | false end with_them do let(:current_user) { public_send(role) } before do stub_licensed_features(merge_request_approvers: licensed) enable_admin_mode!(current_user) if role == :admin end it { is_expected.to(allowed ? be_allowed(:admin_merge_request_approval_settings) : be_disallowed(:admin_merge_request_approval_settings)) } end end describe ':modify_approvers_rules' do it_behaves_like 'merge request approval settings', true do let(:app_setting) { :disable_overriding_approvers_per_merge_request } let(:policy) { :modify_approvers_rules } end end describe ':modify_merge_request_author_setting' do it_behaves_like 'merge request approval settings' do let(:app_setting) { :prevent_merge_requests_author_approval } let(:policy) { :modify_merge_request_author_setting } end end describe ':modify_merge_request_committer_setting' do it_behaves_like 'merge request approval settings' do let(:app_setting) { :prevent_merge_requests_committers_approval } let(:policy) { :modify_merge_request_committer_setting } end end it_behaves_like 'resource with requirement permissions' do let(:resource) { project } end describe 'Quality Management test case' do let(:policy) { :create_test_case } where(:role, :admin_mode, :allowed) do :guest | nil | false :planner | nil | true :reporter | nil | true :developer | nil | true :maintainer | nil | true :owner | nil | true :admin | false | false :admin | true | true end before do stub_licensed_features(quality_management: true) enable_admin_mode!(current_user) if admin_mode end with_them do let(:current_user) { public_send(role) } it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } context 'with unavailable license' do before do stub_licensed_features(quality_management: false) end it { is_expected.to(be_disallowed(policy)) } end end end shared_examples_for 'prevents CI cancellation ability' do context 'when feature is enabled' do where(:restricted_role, :actual_role, :allowed) do :developer | :planner | false :developer | :guest | false :developer | :reporter | false :developer | :developer | true :developer | :maintainer | true :developer | :owner | true :maintainer | :planner | false :maintainer | :guest | false :maintainer | :reporter | false :maintainer | :developer | false :maintainer | :maintainer | true :maintainer | :owner | true :no_one | :planner | false :no_one | :guest | false :no_one | :reporter | false :no_one | :developer | false :no_one | :maintainer | false :no_one | :owner | false end with_them do let(:current_user) { public_send(actual_role) } 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 describe 'prevents cancel_pipeline when CI cancllation restricted' do let(:ability) { :cancel_pipeline } it_behaves_like 'prevents CI cancellation ability' end describe 'prevents cancel_build when CI cancllation restricted' do let(:ability) { :cancel_build } it_behaves_like 'prevents CI cancellation ability' end describe 'project level compliance features' do shared_examples 'project level compliance feature' do |feature, permission| context 'when enabled' do before do stub_licensed_features({ feature => true }) end context 'when project is in group' do let(:project) { public_project_in_group } context 'when user is eligible for access' do where(role: %w[owner auditor]) with_them do let(:current_user) { public_send(role) } it { is_expected.to be_allowed(permission) } end end context 'allows admin', :enable_admin_mode do let(:current_user) { admin } 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 describe 'project level compliance dashboard' do it_behaves_like 'project level compliance feature', :project_level_compliance_dashboard, :read_compliance_dashboard end describe 'project level compliance adherence report' do it_behaves_like 'project level compliance feature', :project_level_compliance_adherence_report, :read_compliance_adherence_report end describe 'project level compliance violations report' do it_behaves_like 'project level compliance feature', :project_level_compliance_violations_report, :read_compliance_violations_report end end describe 'project level admin_compliance_framework check delegates to group', :eager_load do let(:project) { public_project_in_group } 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 with_them do let(:current_user) { role == :admin ? admin : owner } before do project.group.public_send("add_#{role}", current_user) unless role == :admin stub_licensed_features(compliance_framework: feature_enabled, custom_compliance_frameworks: feature_enabled) enable_admin_mode!(current_user) if admin_mode end it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end describe 'Incident Management on-call schedules' do let(:current_user) { public_send(role) } let(:admin_mode) { false } before do enable_admin_mode!(current_user) if admin_mode stub_licensed_features(oncall_schedules: true) end context ':read_incident_management_oncall_schedule' do let(:policy) { :read_incident_management_oncall_schedule } 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 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 it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter end context ':admin_incident_management_oncall_schedule' do let(:policy) { :admin_incident_management_oncall_schedule } where(:role, :admin_mode, :allowed) do :guest | nil | false :planner | nil | false :reporter | nil | false :developer | nil | false :maintainer | nil | true :owner | nil | true :admin | false | false :admin | true | true :auditor | false | false end 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 it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer end end describe 'Escalation Policies' do let(:current_user) { public_send(role) } let(:admin_mode) { false } before do enable_admin_mode!(current_user) if admin_mode allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(true) end context ':read_incident_management_escalation_policy' do let(:policy) { :read_incident_management_escalation_policy } where(:role, :admin_mode, :allowed) do :guest | nil | false :planner | nil | false :reporter | nil | true :developer | nil | true :maintainer | nil | true :owner | nil | true :admin | false | false :admin | true | true :auditor | false | true end 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 it { is_expected.to(be_disallowed(policy)) } end end it_behaves_like 'monitor feature visibility', allow_lowest_role: :reporter end context ':admin_incident_management_escalation_policy' do let(:policy) { :admin_incident_management_escalation_policy } 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 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 it { is_expected.to(be_disallowed(policy)) } end end it_behaves_like 'monitor feature visibility', allow_lowest_role: :maintainer end 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 do |feature| [ :"create_#{feature}", :"update_#{feature}", :"admin_#{feature}" ] end + described_class.readonly_abilities end 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. 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 stub_ee_application_setting(should_check_namespace_plan: true) end context 'with admin access' do let(:current_user) { owner } before do project.add_owner(owner) 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.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 '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 it_behaves_like 'GitLab.com Paid plan resource access tokens' context 'create resource access tokens' do it { is_expected.to be_allowed(:create_resource_access_tokens) } context 'with a personal namespace project' do let(:namespace) { create(:namespace_with_plan, plan: :bronze_plan) } let(:project) { create(:project, namespace: namespace) } it { is_expected.to be_allowed(:create_resource_access_tokens) } end context 'when resource access token creation is not allowed' do before do group.namespace_settings.update_column(:resource_access_token_creation_allowed, false) end it { is_expected.not_to be_allowed(:create_resource_access_tokens) } end context 'when parent group has resource access token creation disabled' do let(:resource_access_token_creation_allowed) { false } let(:ns_for_parent) { create(:namespace_settings, resource_access_token_creation_allowed: resource_access_token_creation_allowed) } let(:parent) { create(:group_with_plan, plan: :bronze_plan, namespace_settings: ns_for_parent) } let(:group) { create(:group, parent: parent) } let(:project) { create(:project, group: group) } context 'cannot create resource access tokens' do it { is_expected.not_to be_allowed(:create_resource_access_tokens) } end end end context 'read resource access tokens' do it { is_expected.to be_allowed(:read_resource_access_tokens) } end context 'destroy resource access tokens' do it { is_expected.to be_allowed(:destroy_resource_access_tokens) } end end context 'with developer access' do let(:current_user) { developer } 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 '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) } end end 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 'cannot create resource access tokens' do it { is_expected.not_to be_allowed(:create_resource_access_tokens) } end context 'cannot destroy resource access tokens' do it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) } end end end end 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 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 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) } it { is_expected.to be_disallowed(*all_read_analytics_permissions) } end end end 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) } it { is_expected.to be_disallowed(*all_read_analytics_permissions) } end end context 'for developer' do let(:current_user) { developer } it { is_expected.to be_allowed(*all_read_analytics_permissions) } end context 'for admin', :enable_admin_mode do let(:current_user) { admin } it { is_expected.to be_allowed(*all_read_analytics_permissions) } end context 'for auditor' do let(:current_user) { auditor } it { is_expected.to be_allowed(*all_read_analytics_permissions) } end end 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) } 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 context 'for developer' do let(:current_user) { developer } it { is_expected.to be_allowed(*all_read_analytics_permissions) } end context 'for admin', :enable_admin_mode do let(:current_user) { admin } it { is_expected.to be_allowed(*all_read_analytics_permissions) } end context 'for auditor' do let(:current_user) { auditor } it { is_expected.to be_allowed(*all_read_analytics_permissions) } end end end end 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) } 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 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) } 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) expect_private_project_permissions_as_if_non_member end end end 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 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) expect_private_project_permissions_as_if_non_member 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) } 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 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 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) } 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) expect_private_project_permissions_as_if_non_member end end end def expect_private_project_permissions_as_if_non_member expect_disallowed(*guest_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*team_member_reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) expect_disallowed(*owner_permissions) end describe ':read_approvers' do let(:policy) { :read_approvers } 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) } before do enable_admin_mode!(current_user) if role == :admin end it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end end context 'for membership locking' do let(:current_user) { owner } context 'for a personal project' do it { is_expected.to be_allowed(:import_project_members_from_another_project) } it { is_expected.to be_allowed(:invite_member) } end 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) end it { is_expected.to be_disallowed(:import_project_members_from_another_project) } it { is_expected.to be_disallowed(:invite_member) } end context 'via LDAP' do before do stub_application_setting(lock_memberships_to_ldap: true) end it { is_expected.to be_disallowed(:import_project_members_from_another_project) } it { is_expected.to be_disallowed(:invite_member) } end context 'via SAML' do before do stub_application_setting(lock_memberships_to_saml: true) end it { is_expected.to be_disallowed(:import_project_members_from_another_project) } it { is_expected.to be_disallowed(:invite_member) } 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 it { is_expected.to be_allowed(:read_project) } context 'when the user is banned from the invited group' do before do create(:project_group_link, project: project, group: 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_disallowed(:read_project) } end 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) } before do stub_licensed_features(unique_project_download_limit: true) project.add_developer(current_user) end context 'when user is not banned' do it { is_expected.to be_allowed(:read_project) } end context 'when user is banned' do before do create(:namespace_ban, user: current_user, namespace: group.root_ancestor) end it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) } 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 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) } 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 admin mode is enabled', :enable_admin_mode do it { is_expected.to be_allowed(:read_project) } 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) } it { is_expected.to be_disallowed(:read_project) } end context 'when licensed feature unique_project_download_limit is not available' do before do stub_licensed_features(unique_project_download_limit: false) end it { is_expected.to be_allowed(:read_project) } end end end describe 'create_objective' do let(:okr_policies) { [:create_objective, :create_key_result] } where(:role, :allowed) do :guest | true :planner | true :reporter | true :developer | true :maintainer | true :auditor | false :owner | true :admin | true end with_them do let(:current_user) { public_send(role) } before do enable_admin_mode!(current_user) if role == :admin stub_licensed_features(okrs: 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 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 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 describe 'read_member_role' do let_it_be_with_reload(:project) { private_project_in_group } let_it_be_with_reload(:current_user) { create(:user) } let(:permission) { :read_member_role } where(:role, :allowed) do :guest | true :planner | true :reporter | true :developer | true :maintainer | true :auditor | false :owner | true :admin | true end with_them do before do if role == :admin current_user.update!(admin: true) elsif role == :auditor current_user.update!(auditor: true) else create(:project_member, role, source: project, user: current_user) end enable_admin_mode!(current_user) if role == :admin end 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 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 context 'hidden projects' do let(:project) { create(:project, :repository, hidden: true) } let(:current_user) { create(:user) } before do project.add_owner(current_user) end it { is_expected.to be_disallowed(:download_code) } it { is_expected.to be_disallowed(:build_download_code) } 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) { {} } subject { described_class.new(current_user, project) } before do stub_licensed_features(custom_roles: true) end def create_member_role(member, abilities = member_role_abilities) params = abilities.merge(namespace: project.group) create(:member_role, :guest, params).tap do |role| role.members << member end end 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 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 'custom role for parent group' do context 'when a role enables the abilities' do before do create_member_role(group_member_guest) end it { expect_allowed(*allowed_abilities) } it { expect_disallowed(*disallowed_abilities) } end context 'when a role does not enable the abilities' do it { expect_disallowed(*allowed_abilities) } end end context 'custom role on project membership' do context 'when a role enables the abilities' do before do create_member_role(project_member_guest) end it { expect_allowed(*allowed_abilities) } it { expect_disallowed(*disallowed_abilities) } end context 'when a role does not enable the abilities' do it { expect_disallowed(*allowed_abilities) } end end end end context 'for a member role with read_code true' do let(:member_role_abilities) { { read_code: true } } let(:allowed_abilities) { [:read_code] } it_behaves_like 'custom roles abilities' context 'when repository access level is set as disabled' do before do project.project_feature.update_column(:repository_access_level, ProjectFeature::DISABLED) create_member_role(project_member_guest) end after do project.project_feature.update_column(:repository_access_level, ProjectFeature::ENABLED) end it { expect_disallowed(:read_code) } end end context 'for a member role with admin_runners true' do let(:member_role_abilities) { { admin_runners: true } } let(:allowed_abilities) do [ :admin_runner, :create_runner, :read_runner ] 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, :create_vulnerability_archive_export, :read_security_resource, :read_vulnerability, :read_vulnerability_feedback, :read_vulnerability_scanner ] end it_behaves_like 'custom roles abilities' it 'does not enable to admin_vulnerability' do expect(subject).to be_disallowed(:admin_vulnerability) 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] } it_behaves_like 'custom roles abilities' end context 'for a member role with admin_vulnerability true' do let(:member_role_abilities) { { read_vulnerability: true, admin_vulnerability: true } } let(:allowed_abilities) do [ :admin_vulnerability, :create_vulnerability_feedback, :destroy_vulnerability_feedback, :read_vulnerability, :read_vulnerability_feedback, :update_vulnerability_feedback, :create_vulnerability_state_transition ] end it_behaves_like 'custom roles abilities' end 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 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' 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 it_behaves_like 'custom roles abilities' end context 'when the merge requests access level is set as disabled' do before do project.project_feature.update_column(:merge_requests_access_level, ProjectFeature::DISABLED) end it { is_expected.to be_disallowed(:read_merge_request, :admin_merge_request, :download_code) } end end 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 '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_behaves_like 'custom roles abilities' 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 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 let(:disallowed_abilities) do [:modify_security_policy] 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) create_member_role(group_member_guest, { read_dependency: true }) create_member_role(project_member_guest, { read_code: true }) end it { is_expected.to be_allowed(:read_dependency) } it { is_expected.to be_allowed(:read_code) } end context 'for a custom role with the `admin_cicd_variables` ability' do let(:member_role_abilities) { { admin_cicd_variables: true } } let(:allowed_abilities) { [:admin_cicd_variables] } it_behaves_like 'custom roles abilities' end context 'for a custom role with the `admin_protected_environments` ability' do let(:member_role_abilities) { { admin_protected_environments: true } } let(:allowed_abilities) { [:admin_protected_environments] } it_behaves_like 'custom roles abilities' 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_behaves_like 'custom roles abilities' 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 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 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 let(:member_role_abilities) { { read_compliance_dashboard: true, admin_compliance_framework: true } } let(:allowed_abilities) do [ :admin_compliance_framework, :read_compliance_dashboard, :read_compliance_adherence_report, :read_compliance_violations_report ] end it_behaves_like 'custom roles abilities' 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 } } let(:allowed_abilities) do [ :read_compliance_dashboard, :read_compliance_adherence_report, :read_compliance_violations_report ] 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] } it_behaves_like 'custom roles abilities' end context 'for a member role with `manage_deploy_tokens` true' do let(:member_role_abilities) { { manage_deploy_tokens: true } } let(:allowed_abilities) { [:manage_deploy_tokens, :read_deploy_token, :create_deploy_token, :destroy_deploy_token] } it_behaves_like 'custom roles abilities' end context 'for a custom role with the `manage_merge_request_settings` ability' do let(:member_role_abilities) { { read_code: true, manage_merge_request_settings: true } } let(:allowed_abilities) do [ :manage_merge_request_settings, :edit_approval_rule, :modify_approvers_rules, :modify_merge_request_author_setting, :modify_merge_request_committer_setting ] end it_behaves_like 'custom roles abilities' context 'when `target_branch_rules` feature is available' do let(:licensed_features) { { target_branch_rules: true } } let(:allowed_abilities) { [:admin_target_branch_rule] } it_behaves_like 'custom roles abilities' end context 'when `merge_request_approvers` feature is available' do let(:licensed_features) { { merge_request_approvers: true } } let(:allowed_abilities) { [:admin_merge_request_approval_settings] } 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 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 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 it_behaves_like 'custom roles abilities' end context 'for a custom role with the `admin_security_testing` ability' do let(:member_role_abilities) { { admin_security_testing: true } } let(:licensed_features) do { security_dashboard: true, security_scans_api: true, security_on_demand_scans: true, coverage_fuzzing: true, secret_push_protection: true, container_scanning_for_registry: true, project_level_compliance_dashboard: true } end let(:allowed_abilities) do [ :access_security_and_compliance, :read_security_configuration, :access_security_scans_api, :read_on_demand_dast_scan, :create_on_demand_dast_scan, :edit_on_demand_dast_scan, :enable_secret_push_protection, :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_secret_push_protection_info ] end 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' end end context 'admin custom role', :enable_admin_mode do let_it_be(:project) { create(:project, :private, public_builds: false) } let_it_be(:non_admin_user) { create(:user) } let(:member_role_abilities) { [] } let(:allowed_abilities) { [] } let(:current_user) { non_admin_user } subject { described_class.new(current_user, project) } shared_examples 'admin custom roles abilities' do context 'when custom roles feature is unavailable' do before do create(:admin_member_role, *member_role_abilities, user: current_user) stub_licensed_features(custom_roles: false) end it { expect_disallowed(*allowed_abilities) } end context 'when custom roles feature is available' do before do stub_licensed_features(custom_roles: true) end context 'when a role enables the abilities' do before do create(:admin_member_role, *member_role_abilities, user: current_user) end it { expect_allowed(*allowed_abilities) } end context 'when a role does not enable the abilities' do it { expect_disallowed(*allowed_abilities) } end end end context 'for an admin member role with read_admin_cicd true' do let(:member_role_abilities) { [:read_admin_cicd] } let(:allowed_abilities) { %i[read_project_metadata] } it_behaves_like 'admin custom roles abilities' end end describe 'permissions for suggested reviewers bot', :saas do let(:permissions) { [:admin_project_member, :create_resource_access_tokens] } let(:namespace) { build_stubbed(:namespace) } let(:project) { build_stubbed(:project, namespace: namespace) } context 'when user is suggested_reviewers_bot' do let(:current_user) { Users::Internal.suggested_reviewers_bot } where(:suggested_reviewers_available, :token_creation_allowed, :allowed) do false | false | false false | true | false true | false | false true | true | true end with_them do before do allow(project).to receive(:can_suggest_reviewers?).and_return(suggested_reviewers_available) allow(::Gitlab::CurrentSettings) .to receive(:personal_access_tokens_disabled?) .and_return(!token_creation_allowed) end it 'always allows permissions except when feature disabled' do if allowed expect_allowed(*permissions) else expect_disallowed(*permissions) end end end end 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 it 'does not allow permissions' do expect_disallowed(*permissions) 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 with_them do before do stub_licensed_features(runner_performance_insights_for_namespace: licensed) enable_admin_mode!(admin) if enable_admin_mode 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) end end end end describe 'workspace creation' do context 'with no user' do let(:current_user) { nil } it { is_expected.to be_disallowed(:create_workspace) } end context 'with an authorized user' do let(:current_user) { developer } it { is_expected.to be_allowed(:create_workspace) } 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 } 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) } before do project.add_guest(security_policy_bot) end it { is_expected.to be_allowed(:create_pipeline) } end 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 context 'and the project is internal' do let(:project) { internal_project } it_behaves_like 'allows to create pipeline' end 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' end end end end end describe 'build_download_code policy' do let(:project) { private_project } context 'as a guest member' do let(:current_user) { guest } 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 } 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) end end it { is_expected.to be_allowed(:build_download_code) } end end end end 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) } 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) } 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 it { is_expected.to be_allowed(:create_bot_pipeline) } end context 'and the project is private' do let(:project) { private_project } it_behaves_like 'allows to create_bot_pipeline' end context 'and the project is internal' do let(:project) { internal_project } it_behaves_like 'allows to create_bot_pipeline' end context 'and the project is public' do let(:project) { public_project } it_behaves_like 'allows to create_bot_pipeline' end context 'and the project is public in group' do let(:project) { public_project_in_group } it_behaves_like 'allows to create_bot_pipeline' 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) } before do allow(::Gitlab::CurrentSettings).to receive(:enforce_ci_inbound_job_token_scope_enabled?).and_return(instance_level_token_scope_enabled) 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 instance_level_token_scope_enabled is false' do let(:instance_level_token_scope_enabled) { false } context 'when token scope is disabled' do let(:token_scope_enabled) { false } context 'when pipeline is executed in project where bot is invited' do it { is_expected.to be_allowed(:create_pipeline) } it { is_expected.to be_allowed(:create_bot_pipeline) } it { is_expected.to be_allowed(:build_download_code) } end context 'when pipeline is executed in project where bot is not invited' do let(:scope_project) { other_private_project } 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 token scope is enabled' do let(:token_scope_enabled) { true } context 'when pipeline is executed in project where bot is invited' do it { is_expected.to be_allowed(:create_pipeline) } it { is_expected.to be_allowed(:create_bot_pipeline) } it { is_expected.to be_allowed(:build_download_code) } end context 'when pipeline is executed in project where bot is not invited' do let(:scope_project) { other_private_project } it { is_expected.to be_disallowed(:create_pipeline) } it { is_expected.to be_disallowed(:create_bot_pipeline) } it { is_expected.to be_disallowed(:build_download_code) } end end end context 'when instance_level_token_scope_enabled is true' do let(:instance_level_token_scope_enabled) { true } context 'when token scope is disabled' do let(:token_scope_enabled) { false } context 'when pipeline is executed in project where bot is invited' do it { is_expected.to be_allowed(:create_pipeline) } it { is_expected.to be_allowed(:create_bot_pipeline) } it { is_expected.to be_allowed(:build_download_code) } end context 'when pipeline is executed in project where bot is not invited' do let(:scope_project) { other_private_project } it { is_expected.to be_disallowed(:create_pipeline) } it { is_expected.to be_disallowed(:create_bot_pipeline) } it { is_expected.to be_disallowed(:build_download_code) } end end end end context 'when security policy bot is on the project' do before do project.add_guest(security_policy_bot) end context 'when security_dashboard is not enabled' do it { is_expected.to be_disallowed(:create_vulnerability_state_transition) } end context 'when security_dashboard is enabled' do before do stub_licensed_features(security_dashboard: true) 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 } it { is_expected.not_to be_allowed(:download_code_spp_repository) } context 'when project is a security policy project' do before do create(:security_orchestration_policy_configuration, security_policy_management_project: project) end it { is_expected.not_to be_allowed(:download_code_spp_repository) } 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 } it { is_expected.to be_allowed(:download_code_spp_repository) } end context 'and the project is internal' do let(:project) { internal_project } it { is_expected.to be_allowed(:download_code_spp_repository) } end context 'and the project is public' do let(:project) { public_project } it { is_expected.to be_allowed(:download_code_spp_repository) } end context 'and the project is public in group' do let(:project) { public_project_in_group } it { is_expected.to be_allowed(:download_code_spp_repository) } end end context 'and namespace allows spp_repository_pipeline_access' do before do project.group.namespace_settings.update!(spp_repository_pipeline_access: true) end context 'and the project is private in group' do let(:project) { private_project_in_group } it { is_expected.to be_allowed(:download_code_spp_repository) } end 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 context 'and the project is public in group' do let(:project) { public_project_in_group } it { is_expected.to be_allowed(:download_code_spp_repository) } end end context 'and application setting allows spp_repository_pipeline_access' do before do stub_application_setting(spp_repository_pipeline_access: true) 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 } it { is_expected.to be_allowed(:download_code_spp_repository) } end context 'and the project is public' do let(:project) { public_project } it { is_expected.to be_allowed(:download_code_spp_repository) } end context 'and the project is public in group' do let(:project) { public_project_in_group } it { is_expected.to be_allowed(:download_code_spp_repository) } end end end context 'when user is authenticated via CI_JOB_TOKEN', :request_store do let(:job) { build_stubbed(:ci_build, project: scope_project, user: current_user) } let(:scope_project) { project } let_it_be(:other_private_project) { create(:project, :private) } before do allow(::Gitlab::CurrentSettings).to receive(:enforce_ci_inbound_job_token_scope_enabled?).and_return(instance_level_token_scope_enabled) 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 instance_level_token_scope_enabled is false' do let(:instance_level_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 other project' do let(:scope_project) { other_private_project } it { is_expected.to be_allowed(:download_code_spp_repository) } end end context 'when token scope is enabled' do let(:token_scope_enabled) { true } context 'when accessing from the same project' do it { is_expected.to be_allowed(:download_code_spp_repository) } end context 'when accessing from other project' do let(:scope_project) { other_private_project } it { is_expected.to be_disallowed(:download_code_spp_repository) } end end end context 'when instance_level_token_scope_enabled is true' do let(:instance_level_token_scope_enabled) { true } context 'when token scope is enabled' 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 other project' do let(:scope_project) { other_private_project } 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 } 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 context 'when user can create issue' do it { is_expected.to be_allowed(:generate_description) } end context 'when user cannot create issue' do let(:current_user) { create(:user) } it { is_expected.to be_disallowed(:generate_description) } end end 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 describe 'access_summarize_new_merge_request' do let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } let(:current_user) { user_can_create_mr ? developer : nil } where(:feature_flag_enabled, :llm_authorized, :user_can_create_mr, :expected_result) do true | true | true | be_allowed(:access_summarize_new_merge_request) true | true | false | be_disallowed(:access_summarize_new_merge_request) true | false | true | be_disallowed(:access_summarize_new_merge_request) false | true | true | be_disallowed(:access_summarize_new_merge_request) end with_them do before do # Setup feature flag stub_feature_flags(add_ai_summary_for_new_mr: feature_flag_enabled) # Setup LLM authorizer allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) allow(authorizer).to receive(:allowed?).and_return(llm_authorized) end it { is_expected.to expected_result } end end describe 'admin_target_branch_rule policy' do let(:current_user) { owner } describe 'when the project does not have the correct license' do before do stub_licensed_features(target_branch_rules: false) end it { is_expected.to be_disallowed(:admin_target_branch_rule) } end describe 'when the user does not have permissions' do let(:current_user) { auditor } 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 it { is_expected.to be_allowed(:admin_target_branch_rule) } end end describe 'read_target_branch_rule policy' do let(:current_user) { owner } describe 'when the user has permission' do before do stub_licensed_features(target_branch_rules: 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 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 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 it { is_expected.to be_disallowed(:read_observability) } end 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 it { is_expected.to be_disallowed(:read_observability) } end 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 'write_observability policy' do let(:current_user) { developer } before do stub_licensed_features(observability: true) end describe 'when observability_features feature flag is disabled' do before do stub_feature_flags(observability_features: false) 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 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 it { is_expected.to be_disallowed(:write_observability) } end describe 'when the user does not have permission' do let(:current_user) { reporter } before do stub_feature_flags(observability_features: true) stub_licensed_features(observability: true) end it { is_expected.to be_disallowed(:write_observability) } end 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 "#admin_vulnerability" do before do stub_licensed_features(security_dashboard: true) end let(:expected_permissions) do [ :admin_vulnerability, :read_vulnerability, :create_vulnerability_feedback, :destroy_vulnerability_feedback, :update_vulnerability_feedback, :create_vulnerability_state_transition ] end context "with guest" do let(:current_user) { guest } it { is_expected.to be_disallowed(:admin_vulnerability) } end context "with planner" do let(:current_user) { planner } it { is_expected.to be_disallowed(:admin_vulnerability) } end context "with reporter" do let(:current_user) { reporter } it { is_expected.to be_disallowed(:admin_vulnerability) } end context "with developer" do let(:current_user) { developer } it { is_expected.to be_disallowed(:admin_vulnerability) } end context "with maintainer" do let(:current_user) { maintainer } it { is_expected.to be_allowed(*expected_permissions) } end context "with owner" do let(:current_user) { owner } 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) 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) end end end end describe 'read_ai_agents' do where(:feature_flag_enabled, :licensed_feature, :current_user, :allowed) do true | true | ref(:owner) | true true | true | ref(:reporter) | true true | true | ref(:planner) | true true | true | ref(:guest) | true true | true | ref(:non_member) | false true | false | ref(:owner) | false true | false | ref(:reporter) | false true | false | ref(:planner) | false true | false | ref(:guest) | false true | false | ref(:non_member) | false false | true | ref(:owner) | false false | true | ref(:reporter) | false false | true | ref(:planner) | false false | true | ref(:guest) | false false | true | ref(:non_member) | false false | false | ref(:owner) | false false | false | ref(:reporter) | false false | false | ref(:planner) | false false | false | ref(:guest) | false false | false | ref(:non_member) | false end with_them do before do stub_feature_flags(agent_registry: feature_flag_enabled) stub_licensed_features(ai_agents: licensed_feature) end if params[:allowed] it { expect_allowed(:read_ai_agents) } else it { expect_disallowed(:read_ai_agents) } end end end describe '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) } end end end 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) } 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 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 context 'when user is not a member of the parent group' do context 'when the user has AI enabled via another group' do context 'user can view project' do it 'is allowed' do is_expected.to be_allowed(:access_duo_chat) end end context 'user cannot view project' do before do project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) end it 'is not allowed' do is_expected.to be_disallowed(:access_duo_chat) end end end end context 'when user is a member of the project' do before do project.add_guest(current_user) end context 'when the user has AI enabled through parent group' do it 'is allowed' do is_expected.to be_allowed(:access_duo_chat) end 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 } before do project.add_guest(current_user) 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) 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 } 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 it do is_expected.to cs_matcher end end end describe 'access_duo_core_features' do let_it_be(:current_user) { create(:user) } let_it_be_with_reload(:project) { create(:project, :public, group: group) } subject { described_class.new(current_user, project) } context 'when on GitLab.com', :saas do before do stub_ee_application_setting(should_check_namespace_plan: true) end context 'with a project in a group with Duo Core enabled' do before do group.namespace_settings.reload.update!(duo_core_features_enabled: true) end context 'with duo core addon' do include_context 'with duo core addon' context 'when user is a guest' do before do group.add_guest(current_user) end it { is_expected.to be_disallowed(:access_duo_core_features) } end context 'when user is a member of the project' do where(:access_level) { %i[reporter developer maintainer owner] } with_them do before do project.add_member(current_user, access_level) end it { is_expected.to be_allowed(:access_duo_core_features) } end end context 'when user is a member of a project in a subgroup' do before do project = create(:project, group: create(:group, parent: group)) project.add_reporter(current_user) end it { is_expected.to be_allowed(:access_duo_core_features) } end end ['with duo pro addon', 'with duo enterprise addon'].each do |context| context context do include_context context it { is_expected.to be_disallowed(:access_duo_core_features) } end end end context 'with a project in a group with Duo Core disabled' do before do group.namespace_settings.reload.update!(duo_core_features_enabled: false) project.add_member(current_user, :owner) end ['with duo core addon', 'with duo pro addon', 'with duo enterprise addon'].each do |context| context context do include_context context it { is_expected.to be_disallowed(:access_duo_core_features) } end end 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 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] } 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 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 describe 'read_runner_cloud_provisioning_info policy' do let(:current_user) { maintainer } 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 'the user is a maintainer' do let(:current_user) { maintainer } it { is_expected.to be_allowed(:read_runner_cloud_provisioning_info) } end context 'the user is a guest' do let(:current_user) { guest } it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_info) } end end end describe 'read_runner_gke_provisioning_info policy' do let(:current_user) { maintainer } 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 'the user is a maintainer' do let(:current_user) { maintainer } it { is_expected.to be_allowed(:read_runner_gke_provisioning_info) } end context 'the user is a guest' do let(:current_user) { guest } it { is_expected.to be_disallowed(:read_runner_gke_provisioning_info) } end end end describe 'provision_cloud_runner policy' do let(:current_user) { maintainer } 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 'the user is a maintainer' do let(:current_user) { maintainer } it { is_expected.to be_allowed(:provision_cloud_runner) } end context 'the user is a guest' do let(:current_user) { guest } it { is_expected.to be_disallowed(:provision_cloud_runner) } end end end describe 'provision_gke_runner policy' do let(:current_user) { maintainer } 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 'the user is a maintainer' do let(:current_user) { maintainer } it { is_expected.to be_allowed(:provision_gke_runner) } end context 'the user is a guest' do let(:current_user) { guest } it { is_expected.to be_disallowed(:provision_gke_runner) } 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) end it { is_expected.to match_expected_result } end 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) end with_them do before do stub_saas_features(google_cloud_support: saas_feature_enabled) end it { is_expected.to match_expected_result } end end 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 context 'with correct license' do before do stub_licensed_features(project_saved_replies: true) end it { is_expected.to be_allowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) } context 'when the user is a guest' do let(:current_user) { guest } it { is_expected.to be_allowed(:read_saved_replies) } it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } end context 'when the user is a reporter' do let(:current_user) { reporter } it { is_expected.to be_allowed(:read_saved_replies) } it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } end context 'when the user is a developer' do let(:current_user) { developer } 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 guest member of the project' do let(:current_user) { guest } it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) } end end end describe 'enable_secret_push_protection' do where(:current_user, :licensed, :match_expected_result) do ref(:owner) | true | be_allowed(:enable_secret_push_protection) ref(:maintainer) | true | be_allowed(:enable_secret_push_protection) ref(:developer) | true | be_disallowed(:enable_secret_push_protection) ref(:owner) | false | be_disallowed(:enable_secret_push_protection) ref(:maintainer) | false | be_disallowed(:enable_secret_push_protection) ref(:developer) | false | be_disallowed(:enable_secret_push_protection) end with_them do before do stub_licensed_features(secret_push_protection: licensed) end it { is_expected.to match_expected_result } end describe 'when the project does not have the correct license' do let(:current_user) { owner } it { is_expected.to be_disallowed(:enable_secret_push_protection) } end 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 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 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 with_them do before do stub_licensed_features(container_scanning_for_registry: true) end it { is_expected.to match_expected_result } end 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 describe 'read_secret_push_protection_info' do where(:current_user, :match_expected_result) do ref(:owner) | be_allowed(:read_secret_push_protection_info) ref(:maintainer) | be_allowed(:read_secret_push_protection_info) ref(:developer) | be_allowed(:read_secret_push_protection_info) ref(:planner) | be_disallowed(:read_secret_push_protection_info) ref(:guest) | be_disallowed(:read_secret_push_protection_info) ref(:non_member) | be_disallowed(:read_secret_push_protection_info) end with_them do before do stub_licensed_features(secret_push_protection: true) end 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 with_them do it { is_expected.to match_expected_result } end 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 end 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 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 end 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 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 end 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 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) } before do enable_admin_mode!(current_user) if role == :admin 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 } before do stub_licensed_features(security_scans_api: false) end it 'is not allowed' do is_expected.to be_disallowed(:access_security_scans_api) 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 with_them do before do stub_licensed_features(security_scans_api: true) allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:sast).and_return( instance_double(CloudConnector::BaseAvailableServiceData, free_access?: free_access)) end it { is_expected.to(allowed ? be_allowed(:access_security_scans_api) : be_disallowed(:access_security_scans_api)) } end end end describe 'access_ai_review_mr' do let(:current_user) { owner } 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 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: :review_merge_request).and_return(allowed_to_use) end it { is_expected.to enabled_for_user } end end end describe 'invite_group_members policy' do let(:app_setting) { :disable_invite_members } let(:policy) { :invite_project_members } let(:group) { create(:group) } context 'when on saas', :saas do before do allow(project).to receive(:group).and_return(group) stub_saas_features(group_disable_invite_members: true) end context 'with disable_invite_members is available in license' do where(:role, :parent_group_setting, :application_setting, :allowed) do :guest | true | true | false :planner | true | true | false :reporter | true | true | false :developer | true | true | false :maintainer | false | true | true :maintainer | false | false | true :maintainer | true | true | false :maintainer | true | false | false :owner | false | true | true :owner | false | false | true :owner | true | true | false :owner | true | false | false :admin | false | true | true :admin | false | false | true :admin | false | true | true :admin | false | false | true end with_them do let(:current_user) { public_send(role) } before do stub_licensed_features(disable_invite_members: true) stub_application_setting(app_setting => application_setting) allow(project.group).to receive(:disable_invite_members?).and_return(parent_group_setting) enable_admin_mode!(current_user) if role == :admin end it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end context 'with disable_invite_members not available in license' do where(:role, :parent_group_setting, :application_setting, :allowed) do :guest | true | true | false :planner | true | true | false :reporter | true | true | false :developer | true | true | false :maintainer | false | true | true :maintainer | false | false | true :maintainer | true | true | true :maintainer | true | false | true :owner | false | true | true :owner | false | false | true :owner | true | false | true :owner | true | true | true :admin | false | true | true :admin | true | false | true end with_them do let(:current_user) { public_send(role) } before do stub_licensed_features(disable_invite_members: false) stub_application_setting(app_setting => application_setting) allow(project.group).to receive(:disable_invite_members?).and_return(parent_group_setting) enable_admin_mode!(current_user) if role == :admin end it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) } end end end context 'with disable_invite_members 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 end with_them do let(:current_user) { public_send(role) } before do stub_licensed_features(disable_invite_members: 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 'with disable_invite_members 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 with_them do let(:current_user) { public_send(role) } before do stub_licensed_features(disable_invite_members: false) 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 end describe 'pages_multiple_versions_available' do let(:current_user) { maintainer } before do stub_licensed_features(pages_multiple_versions: licensed) end context 'when pages_multiple_versions feature is available' do let(:licensed) { true } it 'allows access to pages multiple deployments' do is_expected.to allow_action(:pages_multiple_versions) end end context 'when pages_multiple_versions feature is not available' do let(:licensed) { false } it 'denies access to pages multiple deployments' do is_expected.not_to allow_action(:pages_multiple_versions) end end end describe 'create_epic' do where(:current_user, :match_role_permissions) do ref(:owner) | be_allowed(:create_epic) ref(:maintainer) | be_allowed(:create_epic) ref(:developer) | be_allowed(:create_epic) ref(:reporter) | be_allowed(:create_epic) ref(:planner) | be_allowed(:create_epic) ref(:guest) | be_disallowed(:create_epic) ref(:non_member) | be_disallowed(:create_epic) end with_them do context 'when epics feature is available' do before do stub_licensed_features(epics: true) end it { is_expected.to match_role_permissions } context 'when project_work_item_epics feature flag is disabled' do before do stub_feature_flags(project_work_item_epics: false) end it { is_expected.to be_disallowed(:create_epic) } end context 'when issues are disabled for the project' do before do project.update!(issues_enabled: false) end it { is_expected.to be_disallowed(:create_epic) } end end context 'when epic feature is not available' do before do stub_licensed_features(epics: false) end it { is_expected.to be_disallowed(:create_epic) } end end end describe 'access_description_composer' do let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) } let(:current_user) { can_read_mr ? reporter : nil } where(:duo_features_enabled, :feature_flag_enabled, :llm_authorized, :can_read_mr, :expected_result) do true | true | true | true | be_allowed(:access_description_composer) true | true | true | false | be_disallowed(:access_description_composer) true | false | true | true | be_disallowed(:access_description_composer) true | true | false | true | be_disallowed(:access_description_composer) false | true | true | true | be_disallowed(:access_description_composer) end with_them do before do allow(project) .to receive_message_chain(:project_setting, :duo_features_enabled?) .and_return(duo_features_enabled) stub_feature_flags(mr_description_composer: feature_flag_enabled) if current_user allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer) allow(authorizer).to receive(:allowed?).and_return(llm_authorized) end end it { is_expected.to expected_result } end end end