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