Commit 3d35fb7b authored by Mehmet Emin INAC's avatar Mehmet Emin INAC Committed by David Fernandez

Allow enabling/disabling the "Security & Compliance" features

This change will allow users to enable/disable the features provided
under the "Security & Compliance" roof for projects.
parent 83d210a9
...@@ -75,6 +75,11 @@ export default { ...@@ -75,6 +75,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
securityAndComplianceAvailable: {
type: Boolean,
required: false,
default: false,
},
visibilityHelpPath: { visibilityHelpPath: {
type: String, type: String,
required: false, required: false,
...@@ -141,6 +146,7 @@ export default { ...@@ -141,6 +146,7 @@ export default {
metricsDashboardAccessLevel: featureAccessLevel.PROJECT_MEMBERS, metricsDashboardAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
analyticsAccessLevel: featureAccessLevel.EVERYONE, analyticsAccessLevel: featureAccessLevel.EVERYONE,
requirementsAccessLevel: featureAccessLevel.EVERYONE, requirementsAccessLevel: featureAccessLevel.EVERYONE,
securityAndComplianceAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
operationsAccessLevel: featureAccessLevel.EVERYONE, operationsAccessLevel: featureAccessLevel.EVERYONE,
containerRegistryEnabled: true, containerRegistryEnabled: true,
lfsEnabled: true, lfsEnabled: true,
...@@ -264,6 +270,10 @@ export default { ...@@ -264,6 +270,10 @@ export default {
featureAccessLevel.PROJECT_MEMBERS, featureAccessLevel.PROJECT_MEMBERS,
this.requirementsAccessLevel, this.requirementsAccessLevel,
); );
this.securityAndComplianceAccessLevel = Math.min(
featureAccessLevel.PROJECT_MEMBERS,
this.securityAndComplianceAccessLevel,
);
this.operationsAccessLevel = Math.min( this.operationsAccessLevel = Math.min(
featureAccessLevel.PROJECT_MEMBERS, featureAccessLevel.PROJECT_MEMBERS,
this.operationsAccessLevel, this.operationsAccessLevel,
...@@ -552,6 +562,17 @@ export default { ...@@ -552,6 +562,17 @@ export default {
name="project[project_feature_attributes][requirements_access_level]" name="project[project_feature_attributes][requirements_access_level]"
/> />
</project-setting-row> </project-setting-row>
<project-setting-row
v-if="securityAndComplianceAvailable"
:label="s__('ProjectSettings|Security & Compliance')"
:help-text="s__('ProjectSettings|Security & Compliance for this project')"
>
<project-feature-setting
v-model="securityAndComplianceAccessLevel"
:options="featureAccessLevelOptions"
name="project[project_feature_attributes][security_and_compliance_access_level]"
/>
</project-setting-row>
<project-setting-row <project-setting-row
ref="wiki-settings" ref="wiki-settings"
:label="s__('ProjectSettings|Wiki')" :label="s__('ProjectSettings|Wiki')"
......
...@@ -3,6 +3,7 @@ export default { ...@@ -3,6 +3,7 @@ export default {
return { return {
packagesEnabled: false, packagesEnabled: false,
requirementsEnabled: false, requirementsEnabled: false,
securityAndComplianceEnabled: false,
}; };
}, },
watch: { watch: {
......
...@@ -212,7 +212,8 @@ ...@@ -212,7 +212,8 @@
= render_if_exists "layouts/nav/test_cases_link", project: @project = render_if_exists "layouts/nav/test_cases_link", project: @project
= render_if_exists 'layouts/nav/sidebar/project_security_link' # EE-specific - if project_nav_tab? :security_and_compliance
= render_if_exists 'layouts/nav/sidebar/project_security_link' # EE-specific
- if project_nav_tab? :operations - if project_nav_tab? :operations
= nav_link(controller: sidebar_operations_paths) do = nav_link(controller: sidebar_operations_paths) do
......
---
title: Add `security_and_compliance_access_level` column into the `project_features`
table
merge_request: 52551
author:
type: added
# frozen_string_literal: true
class AddSecurityDashboardAccessLevelIntoProjectFeatures < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
PRIVATE_ACCESS_LEVEL = 10
def up
with_lock_retries do
add_column :project_features, :security_and_compliance_access_level, :integer, default: PRIVATE_ACCESS_LEVEL, null: false
end
end
def down
with_lock_retries do
remove_column :project_features, :security_and_compliance_access_level
end
end
end
b5c219d1b1443ddf482f26d8280a1c7318456affce3ad57a082eb8f9efc32206
\ No newline at end of file
...@@ -15837,7 +15837,8 @@ CREATE TABLE project_features ( ...@@ -15837,7 +15837,8 @@ CREATE TABLE project_features (
metrics_dashboard_access_level integer, metrics_dashboard_access_level integer,
requirements_access_level integer DEFAULT 20 NOT NULL, requirements_access_level integer DEFAULT 20 NOT NULL,
operations_access_level integer DEFAULT 20 NOT NULL, operations_access_level integer DEFAULT 20 NOT NULL,
analytics_access_level integer DEFAULT 20 NOT NULL analytics_access_level integer DEFAULT 20 NOT NULL,
security_and_compliance_access_level integer DEFAULT 10 NOT NULL
); );
CREATE SEQUENCE project_features_id_seq CREATE SEQUENCE project_features_id_seq
......
...@@ -3,6 +3,7 @@ export default { ...@@ -3,6 +3,7 @@ export default {
return { return {
packagesEnabled: true, packagesEnabled: true,
requirementsEnabled: true, requirementsEnabled: true,
securityAndComplianceEnabled: true,
}; };
}, },
watch: { watch: {
......
...@@ -49,7 +49,7 @@ module EE ...@@ -49,7 +49,7 @@ module EE
override :project_feature_attributes override :project_feature_attributes
def project_feature_attributes def project_feature_attributes
super + [:requirements_access_level] super + [:requirements_access_level, :security_and_compliance_access_level]
end end
override :project_params_attributes override :project_params_attributes
......
...@@ -59,14 +59,16 @@ module EE ...@@ -59,14 +59,16 @@ module EE
override :project_permissions_settings override :project_permissions_settings
def project_permissions_settings(project) def project_permissions_settings(project)
super.merge( super.merge(
requirementsAccessLevel: project.requirements_access_level requirementsAccessLevel: project.requirements_access_level,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level
) )
end end
override :project_permissions_panel_data override :project_permissions_panel_data
def project_permissions_panel_data(project) def project_permissions_panel_data(project)
super.merge( super.merge(
requirementsAvailable: project.feature_available?(:requirements) requirementsAvailable: project.feature_available?(:requirements),
securityAndComplianceAvailable: project.feature_available?(:security_and_compliance)
) )
end end
...@@ -322,7 +324,9 @@ module EE ...@@ -322,7 +324,9 @@ module EE
private private
def get_project_security_nav_tabs(project, current_user) def get_project_security_nav_tabs(project, current_user)
nav_tabs = [] return [] unless can?(current_user, :access_security_and_compliance, project)
nav_tabs = [:security_and_compliance]
if can?(current_user, :read_project_security_dashboard, project) if can?(current_user, :read_project_security_dashboard, project)
nav_tabs << :security nav_tabs << :security
......
...@@ -12,5 +12,13 @@ module EE ...@@ -12,5 +12,13 @@ module EE
def requirements_access_level=(value) def requirements_access_level=(value)
write_feature_attribute_string(:requirements_access_level, value) write_feature_attribute_string(:requirements_access_level, value)
end end
def security_and_compliance_enabled=(value)
write_feature_attribute_boolean(:security_and_compliance_access_level, value)
end
def security_and_compliance_access_level=(value)
write_feature_attribute_string(:security_and_compliance_access_level, value)
end
end end
end end
...@@ -199,7 +199,7 @@ module EE ...@@ -199,7 +199,7 @@ module EE
delegate :closest_gitlab_subscription, to: :namespace delegate :closest_gitlab_subscription, to: :namespace
delegate :jira_vulnerabilities_integration_enabled?, to: :jira_service, allow_nil: true delegate :jira_vulnerabilities_integration_enabled?, to: :jira_service, allow_nil: true
delegate :requirements_access_level, to: :project_feature, allow_nil: true delegate :requirements_access_level, :security_and_compliance_access_level, to: :project_feature, allow_nil: true
delegate :pipeline_configuration_full_path, to: :compliance_management_framework, allow_nil: true delegate :pipeline_configuration_full_path, to: :compliance_management_framework, allow_nil: true
alias_attribute :compliance_pipeline_configuration_full_path, :pipeline_configuration_full_path alias_attribute :compliance_pipeline_configuration_full_path, :pipeline_configuration_full_path
......
...@@ -4,7 +4,7 @@ module EE ...@@ -4,7 +4,7 @@ module EE
module ProjectFeature module ProjectFeature
extend ActiveSupport::Concern extend ActiveSupport::Concern
EE_FEATURES = %i(requirements).freeze EE_FEATURES = %i(requirements security_and_compliance).freeze
prepended do prepended do
set_available_features(EE_FEATURES) set_available_features(EE_FEATURES)
...@@ -19,6 +19,7 @@ module EE ...@@ -19,6 +19,7 @@ module EE
end end
default_value_for :requirements_access_level, value: Featurable::ENABLED, allows_nil: false default_value_for :requirements_access_level, value: Featurable::ENABLED, allows_nil: false
default_value_for :security_and_compliance_access_level, value: Featurable::PRIVATE, allows_nil: false
private private
......
...@@ -12,6 +12,7 @@ class License < ApplicationRecord ...@@ -12,6 +12,7 @@ class License < ApplicationRecord
EE_ALL_PLANS = [STARTER_PLAN, PREMIUM_PLAN, ULTIMATE_PLAN].freeze EE_ALL_PLANS = [STARTER_PLAN, PREMIUM_PLAN, ULTIMATE_PLAN].freeze
EES_FEATURES = %i[ EES_FEATURES = %i[
security_and_compliance
audit_events audit_events
blocked_issues blocked_issues
board_iteration_lists board_iteration_lists
......
...@@ -111,6 +111,11 @@ module EE ...@@ -111,6 +111,11 @@ module EE
@subject.feature_available?(:reject_unsigned_commits) @subject.feature_available?(:reject_unsigned_commits)
end end
with_scope :subject
condition(:security_and_compliance_enabled) do
@subject.feature_available?(:security_and_compliance) && feature_available?(:security_and_compliance)
end
with_scope :subject with_scope :subject
condition(:security_dashboard_enabled) do condition(:security_dashboard_enabled) do
@subject.feature_available?(:security_dashboard) @subject.feature_available?(:security_dashboard)
...@@ -214,6 +219,10 @@ module EE ...@@ -214,6 +219,10 @@ module EE
rule { can?(:read_project) & iterations_available }.enable :read_iteration rule { can?(:read_project) & iterations_available }.enable :read_iteration
rule { security_and_compliance_enabled & can?(:developer_access) }.policy do
enable :access_security_and_compliance
end
rule { security_dashboard_enabled & can?(:developer_access) }.policy do rule { security_dashboard_enabled & can?(:developer_access) }.policy do
enable :read_vulnerability enable :read_vulnerability
enable :read_vulnerability_scanner enable :read_vulnerability_scanner
......
---
title: Implement "Security & Compliance" visibility settings
merge_request: 52551
author:
type: added
...@@ -31,6 +31,9 @@ module EE ...@@ -31,6 +31,9 @@ module EE
expose :requirements_enabled do |project, options| expose :requirements_enabled do |project, options|
project.feature_available?(:requirements, options[:current_user]) project.feature_available?(:requirements, options[:current_user])
end end
expose :security_and_compliance_enabled do |project, options|
project.feature_available?(:security_and_compliance, options[:current_user])
end
expose :compliance_frameworks do |project, _| expose :compliance_frameworks do |project, _|
[project.compliance_framework_setting&.compliance_management_framework&.name].compact [project.compliance_framework_setting&.compliance_management_framework&.name].compact
end end
......
...@@ -16,7 +16,8 @@ module EE ...@@ -16,7 +16,8 @@ module EE
:metrics_dashboard_access_level, :metrics_dashboard_access_level,
:analytics_access_level, :analytics_access_level,
:operations_access_level, :operations_access_level,
:requirements_access_level].freeze :requirements_access_level,
:security_and_compliance_access_level].freeze
def initialize(current_user, model, project) def initialize(current_user, model, project)
@project = project @project = project
......
...@@ -266,44 +266,81 @@ RSpec.describe ProjectsHelper do ...@@ -266,44 +266,81 @@ RSpec.describe ProjectsHelper do
describe '#get_project_nav_tabs' do describe '#get_project_nav_tabs' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
where(:ability, :nav_tabs) do let_it_be(:user) { create(:user) }
:read_dependencies | [:dependencies]
:read_feature_flag | [:operations]
:read_licenses | [:licenses]
:read_project_security_dashboard | [:security, :security_configuration]
:read_threat_monitoring | [:threat_monitoring]
:read_incident_management_oncall_schedule | [:oncall_schedule]
end
with_them do subject { helper.get_project_nav_tabs(project, user) }
let_it_be(:user) { create(:user) }
before do before do
allow(helper).to receive(:can?) { false } allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:can?).and_return(false)
end allow(helper).to receive(:can?).with(user, ability, project).and_return(feature_available?)
end
subject do describe 'tabs' do
helper.send(:get_project_nav_tabs, project, user) where(:ability, :nav_tabs) do
:read_feature_flag | [:operations]
:read_incident_management_oncall_schedule | [:oncall_schedule]
end end
context 'when the feature is not available' do with_them do
before do context 'when the feature is available' do
allow(helper).to receive(:can?).with(user, ability, project).and_return(false) let(:feature_available?) { true }
it { is_expected.to include(*nav_tabs) }
end end
it 'does not include the nav tabs' do context 'when the feature is not available' do
is_expected.not_to include(*nav_tabs) let(:feature_available?) { false }
it { is_expected.not_to include(*nav_tabs) }
end end
end end
end
describe 'Security & Compliance tabs' do
where(:ability, :nav_tabs) do
:read_project_security_dashboard | [:security, :security_configuration]
:read_on_demand_scans | [:on_demand_scans]
:read_dependencies | [:dependencies]
:read_licenses | [:licenses]
:read_threat_monitoring | [:threat_monitoring]
end
context 'when the feature is available' do with_them do
before do before do
allow(helper).to receive(:can?).with(user, ability, project).and_return(true) allow(helper).to receive(:can?).with(user, :access_security_and_compliance, project).and_return(security_compliance_available?)
end end
it 'includes the nav tabs' do context 'when the "Security & Compliance" is accessible' do
is_expected.to include(*nav_tabs) let(:security_compliance_available?) { true }
context 'when the feature is not available' do
let(:feature_available?) { false }
it { is_expected.not_to include(*nav_tabs) }
end
context 'when the feature is available' do
let(:feature_available?) { true }
it { is_expected.to include(*nav_tabs) }
end
end
context 'when the "Security & Compliance" is not accessible' do
let(:security_compliance_available?) { false }
context 'when the feature is not available' do
let(:feature_available?) { false }
it { is_expected.not_to include(*nav_tabs) }
end
context 'when the feature is available' do
let(:feature_available?) { true }
it { is_expected.not_to include(*nav_tabs) }
end
end end
end end
end end
...@@ -317,6 +354,7 @@ RSpec.describe ProjectsHelper do ...@@ -317,6 +354,7 @@ RSpec.describe ProjectsHelper do
before do before do
allow(helper).to receive(:can?).and_return(false) allow(helper).to receive(:can?).and_return(false)
allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(user, :access_security_and_compliance, project).and_return(true)
end end
context 'when user can read project security dashboard and audit events' do context 'when user can read project security dashboard and audit events' do
...@@ -369,6 +407,7 @@ RSpec.describe ProjectsHelper do ...@@ -369,6 +407,7 @@ RSpec.describe ProjectsHelper do
before do before do
allow(helper).to receive(:can?).and_return(false) allow(helper).to receive(:can?).and_return(false)
allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(user, :access_security_and_compliance, project).and_return(true)
end end
context 'when user can read project security dashboard and audit events' do context 'when user can read project security dashboard and audit events' do
...@@ -510,4 +549,26 @@ RSpec.describe ProjectsHelper do ...@@ -510,4 +549,26 @@ RSpec.describe ProjectsHelper do
end end
end end
end end
describe '#project_permissions_settings' do
let(:expected_settings) { { requirementsAccessLevel: 20, securityAndComplianceAccessLevel: 10 } }
subject { helper.project_permissions_settings(project) }
it { is_expected.to include(expected_settings) }
end
describe '#project_permissions_panel_data' do
let(:user) { instance_double(User, admin?: false) }
let(:expected_data) { { requirementsAvailable: false, securityAndComplianceAvailable: true } }
subject { helper.project_permissions_panel_data(project) }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).and_return(false)
end
it { is_expected.to include(expected_data) }
end
end end
...@@ -515,6 +515,92 @@ RSpec.describe ProjectPolicy do ...@@ -515,6 +515,92 @@ RSpec.describe ProjectPolicy do
end end
end end
describe 'access_security_and_compliance' do
context 'when the "Security & Compliance" is enabled' do
before do
project.project_feature.update!(security_and_compliance_access_level: Featurable::PRIVATE)
end
%w[owner maintainer developer].each do |role|
context "when the role is #{role}" do
let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:access_security_and_compliance) }
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(:access_security_and_compliance) }
end
context 'when admin mode disabled' do
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
end
%w[reporter guest].each do |role|
context "when the role is #{role}" do
let(:current_user) { public_send(role) }
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
end
context 'with non member' do
let(:current_user) { non_member }
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
end
context 'when the "Security & Compliance" is not enabled' do
before do
project.project_feature.update!(security_and_compliance_access_level: Featurable::DISABLED)
end
%w[owner maintainer developer reporter guest].each do |role|
context "when the role is #{role}" do
let(:current_user) { public_send(role) }
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
end
context 'with admin' do
let(:current_user) { admin }
context 'when admin mode enabled', :enable_admin_mode do
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
context 'when admin mode disabled' do
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
end
context 'with non member' do
let(:current_user) { non_member }
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:access_security_and_compliance) }
end
end
end
shared_context 'when security dashboard feature is not available' do shared_context 'when security dashboard feature is not available' do
before do before do
stub_licensed_features(security_dashboard: false) stub_licensed_features(security_dashboard: false)
......
...@@ -22973,6 +22973,12 @@ msgstr "" ...@@ -22973,6 +22973,12 @@ msgstr ""
msgid "ProjectSettings|Requirements management system for this project" msgid "ProjectSettings|Requirements management system for this project"
msgstr "" msgstr ""
msgid "ProjectSettings|Security & Compliance"
msgstr ""
msgid "ProjectSettings|Security & Compliance for this project"
msgstr ""
msgid "ProjectSettings|Set the default behavior and availability of this option in merge requests. Changes made are also applied to existing merge requests." msgid "ProjectSettings|Set the default behavior and availability of this option in merge requests. Changes made are also applied to existing merge requests."
msgstr "" msgstr ""
......
...@@ -583,6 +583,7 @@ ProjectFeature: ...@@ -583,6 +583,7 @@ ProjectFeature:
- requirements_access_level - requirements_access_level
- analytics_access_level - analytics_access_level
- operations_access_level - operations_access_level
- security_and_compliance_access_level
- created_at - created_at
- updated_at - updated_at
ProtectedBranch::MergeAccessLevel: ProtectedBranch::MergeAccessLevel:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment