Commit e754a6bf authored by Max Woolf's avatar Max Woolf Committed by Mikołaj Wawrzyniak

Assign a custom compliance label to a project

This commit adds the ability to assign
a custom compliance label to a project when
the compliance framework exists in the root ancestor
for the project.

This feature is behind a feature flag.
parent dbfefac2
...@@ -40,6 +40,26 @@ You can select a framework label to identify that your project has certain compl ...@@ -40,6 +40,26 @@ You can select a framework label to identify that your project has certain compl
NOTE: NOTE:
Compliance framework labels do not affect your project settings. Compliance framework labels do not affect your project settings.
#### Custom compliance frameworks
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9.
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-custom-compliance-frameworks). **(PREMIUM ONLY)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
GitLab 13.8 introduces custom compliance frameworks at the group-level. A group owner can create a compliance framework label
and assign it to any number of projects within that group or sub-groups. When this feature is enabled, projects can only
be assigned compliance framework labels that already exist within that group.
If existing [Compliance frameworks](#compliance-framework) are not sufficient, you can now create
your own.
New compliance framework labels can be created and updated using GraphQL.
### Sharing and permissions ### Sharing and permissions
For your repository, you can set up features such as public access, repository features, For your repository, you can set up features such as public access, repository features,
...@@ -299,3 +319,22 @@ Add the URL of a Jaeger server to allow your users to [easily access the Jaeger ...@@ -299,3 +319,22 @@ Add the URL of a Jaeger server to allow your users to [easily access the Jaeger
[Add Storage credentials](../../../operations/incident_management/status_page.md#sync-incidents-to-the-status-page) [Add Storage credentials](../../../operations/incident_management/status_page.md#sync-incidents-to-the-status-page)
to enable the syncing of public Issues to a [deployed status page](../../../operations/incident_management/status_page.md#create-a-status-page-project). to enable the syncing of public Issues to a [deployed status page](../../../operations/incident_management/status_page.md#create-a-status-page-project).
### Enable or disable custom compliance frameworks **(PREMIUM ONLY)**
Enabling or disabling custom compliance frameworks is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:ff_custom_compliance_frameworks)
```
To disable it:
```ruby
Feature.disable(:ff_custom_compliance_frameworks)
```
...@@ -71,6 +71,8 @@ module EE ...@@ -71,6 +71,8 @@ module EE
framework_identifier = settings.delete(:framework) framework_identifier = settings.delete(:framework)
if framework_identifier.blank? if framework_identifier.blank?
settings.merge!(_destroy: true) settings.merge!(_destroy: true)
elsif ::Feature.enabled?(:ff_custom_compliance_frameworks)
settings[:compliance_management_framework] = project.namespace.root_ancestor.compliance_management_frameworks.find(framework_identifier)
else else
settings[:compliance_management_framework] = ComplianceManagement::Framework.find_or_create_legacy_default_framework(project, framework_identifier) settings[:compliance_management_framework] = ComplianceManagement::Framework.find_or_create_legacy_default_framework(project, framework_identifier)
end end
......
- return unless current_user.can?(:admin_compliance_framework, @project) - return unless current_user.can?(:admin_compliance_framework, @project)
.row .row
.form-group.col-md-9.mb-5 .form-group.col-md-9.gl-mb-6
= f.fields_for :compliance_framework_setting, ComplianceManagement::ComplianceFramework::ProjectSettings.new do |cf| - if Feature.enabled?(:ff_custom_compliance_frameworks)
= cf.label :framework, _('Compliance framework (optional)'), class: 'label-bold' - frameworks = @project.namespace.root_ancestor.compliance_management_frameworks
%p.text-secondary= _('Select required regulatory standard') = f.fields_for :compliance_framework_setting, ComplianceManagement::ComplianceFramework::ProjectSettings.new do |cf|
- selected_default_framework = @project.compliance_framework_setting&.compliance_management_framework&.default_framework_definition&.identifier = cf.label :framework, _('Compliance framework (optional)'), class: 'gl-font-weight-bold'
= cf.select :framework, options_for_select(compliance_framework_options, selected_default_framework), { selected: '', disabled: '', prompt: _('Choose your framework'), include_blank: _('None') }, class: 'form-control' - if frameworks.any?
%p.text-secondary= _('Select required regulatory standard')
- selected_default_framework = @project.compliance_framework_setting&.compliance_management_framework&.id
= cf.select :framework, options_for_select(frameworks.map { |fw| [fw.name, fw.id] }, selected_default_framework), { selected: '', disabled: '', prompt: _('Choose your framework'), include_blank: _('None') }, class: 'form-control'
- else
%p.text-secondary
= _("No compliance frameworks are in use. Create one using the GraphQL API.")
- else
= f.fields_for :compliance_framework_setting, ComplianceManagement::ComplianceFramework::ProjectSettings.new do |cf|
= cf.label :framework, _('Compliance framework (optional)'), class: 'gl-font-weight-bold'
%p.text-secondary= _('Select required regulatory standard')
- selected_default_framework = @project.compliance_framework_setting&.compliance_management_framework&.default_framework_definition&.identifier
= cf.select :framework, options_for_select(compliance_framework_options, selected_default_framework), { selected: '', disabled: '', prompt: _('Choose your framework'), include_blank: _('None') }, class: 'form-control'
...@@ -524,42 +524,104 @@ RSpec.describe ProjectsController do ...@@ -524,42 +524,104 @@ RSpec.describe ProjectsController do
end end
context 'compliance framework settings' do context 'compliance framework settings' do
let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.last } shared_examples 'no compliance framework is set' do
let(:params) { { compliance_framework_setting_attributes: { framework: framework.identifier } } } it 'does not change compliance framework for project' do
context 'when unlicensed' do
before do
stub_licensed_features(compliance_framework: false)
end
it 'ignores any compliance framework params' do
put :update, put :update,
params: { params: {
namespace_id: project.namespace, namespace_id: project.namespace,
id: project, id: project,
project: params project: params
} }
project.reload project.reload
expect(project.compliance_framework_setting).to be_nil expect(project.compliance_framework_setting).to be_nil
end end
end end
context 'when unlicensed' do
let(:framework) { create(:compliance_framework, namespace: project.namespace.root_ancestor) }
let(:params) { { compliance_framework_setting_attributes: { framework: framework.id } } }
before do
stub_licensed_features(compliance_framework: false)
end
it_behaves_like 'no compliance framework is set'
context 'custom frameworks are disabled' do
let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.last }
let(:params) { { compliance_framework_setting_attributes: { framework: framework.identifier } } }
before do
stub_feature_flags(ff_custom_compliance_frameworks: false)
end
it_behaves_like 'no compliance framework is set'
end
end
context 'when licensed' do context 'when licensed' do
let(:framework) { create(:compliance_framework, namespace: project.namespace.root_ancestor) }
let(:params) { { compliance_framework_setting_attributes: { framework: framework.id } } }
before do before do
stub_licensed_features(compliance_framework: true) stub_licensed_features(compliance_framework: true)
end end
it 'sets the compliance framework' do context 'current_user is a project maintainer' do
put :update, let_it_be(:maintainer) { create(:user) }
params: {
before do
project.add_maintainer(maintainer)
sign_in(maintainer)
end
it 'sets the compliance framework' do
put :update,
params: {
namespace_id: project.namespace, namespace_id: project.namespace,
id: project, id: project,
project: params project: params
} }
project.reload project.reload
expect(project.compliance_framework_setting.compliance_management_framework).to eq(framework)
end
context 'custom frameworks are disabled' do
let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS.last }
let(:params) { { compliance_framework_setting_attributes: { framework: framework.identifier } } }
expect(project.compliance_framework_setting.compliance_management_framework.name).to eq(framework.name) before do
stub_feature_flags(ff_custom_compliance_frameworks: false)
end
it 'sets the compliance framework based on the framework identifier' do
put :update,
params: {
namespace_id: project.namespace,
id: project,
project: params
}
project.reload
expect(project.compliance_framework_setting.compliance_management_framework.name).to eq(framework.name)
end
it 'raises an error when using framework IDs for custom frameworks' do
framework = create(:compliance_framework, namespace: project.namespace.root_ancestor)
params = { compliance_framework_setting_attributes: { framework: framework.id } }
expect do
put :update,
params: {
namespace_id: project.namespace,
id: project,
project: params
}
end.to raise_error(KeyError)
end
end
end end
end end
end end
......
...@@ -258,11 +258,12 @@ RSpec.describe Projects::UpdateService, '#execute' do ...@@ -258,11 +258,12 @@ RSpec.describe Projects::UpdateService, '#execute' do
end end
end end
context 'when compliance frameworks is set' do context 'when custom compliance frameworks are disabled' do
let(:project_setting) { create(:compliance_framework_project_setting, :gdpr) } let(:project_setting) { create(:compliance_framework_project_setting, :gdpr) }
before do before do
stub_licensed_features(compliance_framework: true) stub_licensed_features(compliance_framework: true)
stub_feature_flags(ff_custom_compliance_frameworks: false)
project.update!(compliance_framework_setting: project_setting) project.update!(compliance_framework_setting: project_setting)
end end
...@@ -292,6 +293,49 @@ RSpec.describe Projects::UpdateService, '#execute' do ...@@ -292,6 +293,49 @@ RSpec.describe Projects::UpdateService, '#execute' do
end end
end end
context 'when ff_custom_compliance_frameworks flag is enabled' do
let(:framework) { create(:compliance_framework, namespace: project.namespace) }
let(:opts) { { compliance_framework_setting_attributes: { framework: framework.id } } }
before do
stub_feature_flags(ff_custom_compliance_frameworks: true)
end
context 'when current_user has :admin_compliance_framework ability' do
before do
stub_licensed_features(compliance_framework: true)
end
it 'updates the framework' do
expect { update_project(project, user, opts) }.to change {
project
.reload
.compliance_management_frameworks
}.from([]).to([framework])
end
it 'unassigns a framework from a project' do
project.compliance_management_frameworks = [framework]
expect { update_project(project, user, { compliance_framework_setting_attributes: { framework: nil } }) }.to change {
project
.reload
.compliance_management_frameworks
}.from([framework]).to([])
end
end
context 'when current_user does not have :admin_compliance_framework ability' do
before do
stub_licensed_features(compliance_framework: false)
end
it 'does not set a framework' do
expect { update_project(project, user, opts) }.not_to change { project.reload.compliance_management_frameworks.count }
end
end
end
context 'when compliance framework feature is disabled' do context 'when compliance framework feature is disabled' do
let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS_BY_IDENTIFIER[:sox] } let(:framework) { ComplianceManagement::Framework::DEFAULT_FRAMEWORKS_BY_IDENTIFIER[:sox] }
let(:opts) { { compliance_framework_setting_attributes: { framework: framework.identifier } } } let(:opts) { { compliance_framework_setting_attributes: { framework: framework.identifier } } }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'projects/edit.html.haml' do
let_it_be(:group) { create(:group) }
let_it_be(:group_owner) { create(:user) }
let_it_be(:project) { create(:project, namespace: group) }
before do
allow(view).to receive(:current_user).and_return(group_owner)
allow(group_owner).to receive(:can?).and_return(true)
assign(:project, project)
stub_licensed_features(custom_compliance_frameworks: true)
end
context 'feature enabled' do
before do
stub_feature_flags(ff_custom_compliance_frameworks: true)
end
context 'group has compliance frameworks' do
let_it_be(:framework) { create(:compliance_framework, namespace: group, name: 'Custom framework 23') }
it 'includes a dropdown including that framework' do
render
expect(rendered).to match /Custom framework 23/
end
end
context 'group has no compliance frameworks' do
before do
group.compliance_management_frameworks.delete_all
end
it 'shows a notification' do
render
expect(rendered).to match /No compliance frameworks are in use. Create one using the GraphQL API./
end
end
end
context 'feature disabled' do
before do
stub_feature_flags(ff_custom_compliance_frameworks: false)
end
it 'includes a dropdown including only the hard-coded frameworks' do
render
expect(rendered).to match /GDPR/
expect(rendered).to match /HIPAA/
expect(rendered).to match /PCI-DSS/
expect(rendered).to match /SOC 2/
expect(rendered).to match /SOX/
end
end
end
...@@ -19303,6 +19303,9 @@ msgstr "" ...@@ -19303,6 +19303,9 @@ msgstr ""
msgid "No commits present here" msgid "No commits present here"
msgstr "" msgstr ""
msgid "No compliance frameworks are in use. Create one using the GraphQL API."
msgstr ""
msgid "No connection could be made to a Gitaly Server, please check your logs!" msgid "No connection could be made to a Gitaly Server, please check your logs!"
msgstr "" msgstr ""
......
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