Commit 2942255b authored by Tetiana Chupryna's avatar Tetiana Chupryna Committed by Igor Drozdov

Add endpoint for auto_fix setting

Save and show setting with configuration json
parent f6082c02
...@@ -11,15 +11,56 @@ module Projects ...@@ -11,15 +11,56 @@ module Projects
push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false) push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false)
end end
before_action only: [:auto_fix] do
check_feature_flag!
authorize_modify_auto_fix_setting!
end
def show def show
@configuration = ConfigurationPresenter.new(project, auto_fix_permission: auto_fix_permission) @configuration = ConfigurationPresenter.new(project, auto_fix_permission: auto_fix_authorized?)
end
def auto_fix
service = ::Security::Configuration::SaveAutoFixService.new(project, auto_fix_params[:feature])
return respond_422 unless service.execute(enabled: auto_fix_params[:enabled])
render status: :ok, json: auto_fix_settings
end end
private private
def auto_fix_permission def auto_fix_authorized?
can?(current_user, :modify_auto_fix_setting, project) can?(current_user, :modify_auto_fix_setting, project)
end end
def auto_fix_params
return @auto_fix_params if @auto_fix_params
@auto_fix_params = params.permit(:feature, :enabled)
feature = @auto_fix_params[:feature]
@auto_fix_params[:feature] = feature.blank? ? 'all' : feature.to_s
@auto_fix_params
end
def check_auto_fix_permissions!
render_403 unless auto_fix_authorized?
end
def check_feature_flag!
render_404 if Feature.disabled?(:security_auto_fix, project)
end
def auto_fix_settings
setting = project.security_setting
{
dependency_scanning: setting.auto_fix_dependency_scanning,
container_scanning: setting.auto_fix_container_scanning
}
end
end end
end end
end end
...@@ -4,4 +4,10 @@ class ProjectSecuritySetting < ApplicationRecord ...@@ -4,4 +4,10 @@ class ProjectSecuritySetting < ApplicationRecord
self.primary_key = :project_id self.primary_key = :project_id
belongs_to :project, inverse_of: :security_setting belongs_to :project, inverse_of: :security_setting
def self.safe_find_or_create_for(project)
project.security_setting || project.create_security_setting
rescue ActiveRecord::RecordNotUnique
retry
end
end end
...@@ -49,8 +49,8 @@ module Projects ...@@ -49,8 +49,8 @@ module Projects
help_page_path: help_page_path('user/application_security/index'), help_page_path: help_page_path('user/application_security/index'),
latest_pipeline_path: latest_pipeline_path, latest_pipeline_path: latest_pipeline_path,
auto_fix_enabled: { auto_fix_enabled: {
dependency_scanning: true, dependency_scanning: project_settings.auto_fix_dependency_scanning,
container_scanning: true container_scanning: project_settings.auto_fix_container_scanning
}.to_json, }.to_json,
can_toggle_auto_fix_settings: auto_fix_permission, can_toggle_auto_fix_settings: auto_fix_permission,
auto_fix_user_path: '/' # TODO: real link will be updated with https://gitlab.com/gitlab-org/gitlab/-/issues/215669 auto_fix_user_path: '/' # TODO: real link will be updated with https://gitlab.com/gitlab-org/gitlab/-/issues/215669
...@@ -142,6 +142,10 @@ module Projects ...@@ -142,6 +142,10 @@ module Projects
def localized_scan_names def localized_scan_names
@localized_scan_names ||= self.class.localized_scan_names @localized_scan_names ||= self.class.localized_scan_names
end end
def project_settings
ProjectSecuritySetting.safe_find_or_create_for(project)
end
end end
end end
end end
# frozen_string_literal: true
module Security
module Configuration
class SaveAutoFixService
SUPPORTED_SCANNERS = %w(container_scanning dependency_scanning all).freeze
# @param project [Project]
# @param ['dependency_scanning', 'container_scanning', 'all'] feature Type of scanner to apply auto_fix
def initialize(project, feature)
@project = project
@feature = feature
end
def execute(enabled:)
return unless valid?
project_settings.update(toggle_params(enabled))
end
private
attr_reader :enabled, :feature, :project
def project_settings
@project_settings ||= ProjectSecuritySetting.safe_find_or_create_for(project)
end
def toggle_params(enabled)
if feature == 'all'
{
auto_fix_container_scanning: enabled,
auto_fix_dast: enabled,
auto_fix_dependency_scanning: enabled,
auto_fix_sast: enabled
}
else
{
"auto_fix_#{feature}" => enabled
}
end
end
def valid?
SUPPORTED_SCANNERS.include?(feature)
end
end
end
end
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
#js-security-configuration{ data: { **@configuration.to_h, #js-security-configuration{ data: { **@configuration.to_h,
auto_fix_help_path: '/', auto_fix_help_path: '/',
toggle_autofix_setting_endpoint: 'auto_fix', toggle_autofix_setting_endpoint: 'configuration/auto_fix',
container_scanning_help_path: help_page_path('user/application_security/container_scanning/index'), container_scanning_help_path: help_page_path('user/application_security/container_scanning/index'),
dependency_scanning_help_path: help_page_path('user/application_security/dependency_scanning/index') } } dependency_scanning_help_path: help_page_path('user/application_security/dependency_scanning/index') } }
---
title: Save setting for auto-fix feature
merge_request: 32690
author:
type: added
...@@ -62,7 +62,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -62,7 +62,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
resources :dashboard, only: [:index], controller: :dashboard resources :dashboard, only: [:index], controller: :dashboard
resource :configuration, only: [:show], controller: :configuration
resource :configuration, only: [:show], controller: :configuration do
post :auto_fix, on: :collection
end
resource :discover, only: [:show], controller: :discover resource :discover, only: [:show], controller: :discover
resources :vulnerability_findings, only: [:index] do resources :vulnerability_findings, only: [:index] do
......
...@@ -3,67 +3,153 @@ ...@@ -3,67 +3,153 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationController do RSpec.describe Projects::Security::ConfigurationController do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) } let(:project) { create(:project, :repository, namespace: group) }
subject { get :show, params: { namespace_id: project.namespace, project_id: project } } describe 'GET #show' do
subject(:request) { get :show, params: { namespace_id: project.namespace, project_id: project } }
it_behaves_like SecurityDashboardsPermissions do
let(:vulnerable) { project }
let(:security_dashboard_action) { request }
end
context 'with user' do
let(:user) { create(:user) }
render_views
before do
stub_licensed_features(security_dashboard: true)
it_behaves_like SecurityDashboardsPermissions do group.add_developer(user)
let(:vulnerable) { project } sign_in(user)
end
it "renders data on the project's security configuration" do
request
let(:security_dashboard_action) do expect(response).to have_gitlab_http_status(:ok)
subject expect(response).to render_template(:show)
expect(response.body).to have_css(
'div#js-security-configuration'\
"[data-auto-devops-help-page-path=\"#{help_page_path('topics/autodevops/index')}\"]"\
"[data-help-page-path=\"#{help_page_path('user/application_security/index')}\"]"\
"[data-latest-pipeline-path=\"#{help_page_path('ci/pipelines')}\"]"
)
end
context 'when the latest pipeline used Auto DevOps' do
let!(:pipeline) do
create(
:ci_pipeline,
:auto_devops_source,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
end
it 'reports that Auto DevOps is enabled' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to have_css(
'div#js-security-configuration'\
'[data-auto-devops-enabled]'\
"[data-auto-devops-help-page-path=\"#{help_page_path('topics/autodevops/index')}\"]"\
"[data-help-page-path=\"#{help_page_path('user/application_security/index')}\"]"\
"[data-latest-pipeline-path=\"#{project_pipeline_path(project, pipeline)}\"]"
)
end
end
end end
end end
describe 'GET #show' do describe 'POST #auto_fix' do
let(:user) { create(:user) } subject(:request) { post :auto_fix, params: params }
render_views let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let(:params) do
{
namespace_id: project.namespace,
project_id: project,
feature: feature,
enabled: false
}
end
before do before do
stub_licensed_features(security_dashboard: true) stub_licensed_features(security_dashboard: true)
project.add_maintainer(maintainer)
group.add_developer(user) project.add_developer(developer)
sign_in(user) sign_in(user)
end end
it "renders data on the project's security configuration" do context 'with feature enabled' do
subject let(:feature) { :dependency_scanning }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
expect(response.body).to have_css(
'div#js-security-configuration'\
"[data-auto-devops-help-page-path=\"#{help_page_path('topics/autodevops/index')}\"]"\
"[data-help-page-path=\"#{help_page_path('user/application_security/index')}\"]"\
"[data-latest-pipeline-path=\"#{help_page_path('ci/pipelines')}\"]"
)
end
context 'when the latest pipeline used Auto DevOps' do before do
let!(:pipeline) do request
create(
:ci_pipeline,
:auto_devops_source,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
end end
it 'reports that Auto DevOps is enabled' do context 'with sufficient permissions' do
subject let(:user) { maintainer }
let(:setting) { project.security_setting }
expect(response).to have_gitlab_http_status(:ok) context 'with setup feature param' do
expect(response.body).to have_css( let(:feature) { :dependency_scanning }
'div#js-security-configuration'\
'[data-auto-devops-enabled]'\ it 'processes request and updates setting' do
"[data-auto-devops-help-page-path=\"#{help_page_path('topics/autodevops/index')}\"]"\ expect(response).to have_gitlab_http_status(:ok)
"[data-help-page-path=\"#{help_page_path('user/application_security/index')}\"]"\ expect(setting.auto_fix_dependency_scanning).to be_falsey
"[data-latest-pipeline-path=\"#{project_pipeline_path(project, pipeline)}\"]" expect(response[:dependency_scanning]).to be_falsey
) end
end
context 'without setup feature param' do
let(:feature) { '' }
it 'processes request and updates setting' do
expect(response).to have_gitlab_http_status(:ok)
expect(setting.auto_fix_dependency_scanning).to be_falsey
expect(setting.auto_fix_dast).to be_falsey
expect(response[:container_scanning]).to be_falsey
end
end
context 'without processable feature' do
let(:feature) { :dep_scan }
let(:setting) { project.create_security_setting }
it 'does not pass validation' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(setting.auto_fix_dependency_scanning).to be_truthy
end
end
end
context 'without sufficient permissions' do
let(:user) { developer }
let(:feature) { '' }
it { expect(response).to have_gitlab_http_status(:not_found) }
end end
end end
context 'with feature disabled' do
let(:user) { maintainer }
let(:feature) { :dependency_scanning }
before do
stub_feature_flags(security_auto_fix: false)
request
end
it { expect(response).to have_gitlab_http_status(:not_found) }
end
end end
end end
...@@ -3,9 +3,33 @@ ...@@ -3,9 +3,33 @@
require 'spec_helper' require 'spec_helper'
describe ProjectSecuritySetting do describe ProjectSecuritySetting do
subject { create(:project_security_setting) }
describe 'associations' do describe 'associations' do
subject { create(:project_security_setting) }
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
end end
describe '.safe_find_or_create_for' do
subject { described_class.safe_find_or_create_for(project) }
let_it_be(:project) { create :project }
context 'without existing setting' do
it 'creates a new entry' do
expect { subject }.to change { ProjectSecuritySetting.count }.by(1)
expect(subject).to be_a_kind_of(ProjectSecuritySetting)
end
end
context 'with existing setting' do
before do
project.create_security_setting
end
it 'reuses existing entry' do
expect { subject }.not_to change { ProjectSecuritySetting.count }
expect(subject).to be_a_kind_of(ProjectSecuritySetting)
end
end
end
end end
...@@ -21,6 +21,13 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -21,6 +21,13 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
expect(subject[:help_page_path]).to eq(help_page_path('user/application_security/index')) expect(subject[:help_page_path]).to eq(help_page_path('user/application_security/index'))
end end
it 'includes settings for auto_fix feature' do
auto_fix = Gitlab::Json.parse(subject[:auto_fix_enabled])
expect(auto_fix['dependency_scanning']).to be_truthy
expect(auto_fix['container_scanning']).to be_truthy
end
context "when the latest default branch pipeline's source is auto devops" do context "when the latest default branch pipeline's source is auto devops" do
before do before do
create( create(
...@@ -52,12 +59,12 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -52,12 +59,12 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
end end
end end
context "when the project has no default branch pipeline" do context 'when the project has no default branch pipeline' do
it 'reports that auto devops is disabled' do it 'reports that auto devops is disabled' do
expect(subject[:auto_devops_enabled]).to be_falsy expect(subject[:auto_devops_enabled]).to be_falsy
end end
it "includes a link to CI pipeline docs" do it 'includes a link to CI pipeline docs' do
expect(subject[:latest_pipeline_path]).to eq(help_page_path('ci/pipelines')) expect(subject[:latest_pipeline_path]).to eq(help_page_path('ci/pipelines'))
end end
...@@ -73,7 +80,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -73,7 +80,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
end end
end end
context "when latest default branch pipeline's source is not auto devops" do context 'when latest default branch pipeline`s source is not auto devops' do
let(:pipeline) do let(:pipeline) do
create( create(
:ci_pipeline, :ci_pipeline,
......
# frozen_string_literal: true
require 'spec_helper'
describe Security::Configuration::SaveAutoFixService do
describe '#execute' do
let_it_be(:project) { create(:project) }
subject(:service) { described_class.new(project, feature) }
before do
service.execute(enabled: false)
end
context 'with supported scanner type' do
let(:feature) { 'dependency_scanning' }
it 'changes setting' do
expect(project.security_setting.auto_fix_dependency_scanning).to be_falsey
end
end
context 'with all scanners' do
let(:feature) { 'all' }
it 'changes setting' do
expect(project.security_setting.auto_fix_dependency_scanning).to be_falsey
expect(project.security_setting.auto_fix_container_scanning).to be_falsey
end
end
context 'with not supported scanner type' do
let(:feature) { :dep_scan }
before do
project.create_security_setting
end
it 'does not change setting' do
expect(project.security_setting.auto_fix_dependency_scanning).to be_truthy
end
end
end
end
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