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
push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false)
end
before_action only: [:auto_fix] do
check_feature_flag!
authorize_modify_auto_fix_setting!
end
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
private
def auto_fix_permission
def auto_fix_authorized?
can?(current_user, :modify_auto_fix_setting, project)
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
......@@ -4,4 +4,10 @@ class ProjectSecuritySetting < ApplicationRecord
self.primary_key = :project_id
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
......@@ -49,8 +49,8 @@ module Projects
help_page_path: help_page_path('user/application_security/index'),
latest_pipeline_path: latest_pipeline_path,
auto_fix_enabled: {
dependency_scanning: true,
container_scanning: true
dependency_scanning: project_settings.auto_fix_dependency_scanning,
container_scanning: project_settings.auto_fix_container_scanning
}.to_json,
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
......@@ -142,6 +142,10 @@ module Projects
def localized_scan_names
@localized_scan_names ||= self.class.localized_scan_names
end
def project_settings
ProjectSecuritySetting.safe_find_or_create_for(project)
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 @@
#js-security-configuration{ data: { **@configuration.to_h,
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'),
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
end
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
resources :vulnerability_findings, only: [:index] do
......
......@@ -3,67 +3,153 @@
require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationController do
let(:group) { create(:group) }
let(:group) { create(: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
let(:vulnerable) { project }
group.add_developer(user)
sign_in(user)
end
it "renders data on the project's security configuration" do
request
let(:security_dashboard_action) do
subject
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
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
describe 'GET #show' do
let(:user) { create(:user) }
describe 'POST #auto_fix' do
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
stub_licensed_features(security_dashboard: true)
group.add_developer(user)
project.add_maintainer(maintainer)
project.add_developer(developer)
sign_in(user)
end
it "renders data on the project's security configuration" do
subject
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 'with feature enabled' do
let(:feature) { :dependency_scanning }
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
)
before do
request
end
it 'reports that Auto DevOps is enabled' do
subject
context 'with sufficient permissions' do
let(:user) { maintainer }
let(:setting) { project.security_setting }
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)}\"]"
)
context 'with setup feature param' do
let(:feature) { :dependency_scanning }
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(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
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
......@@ -3,9 +3,33 @@
require 'spec_helper'
describe ProjectSecuritySetting do
subject { create(:project_security_setting) }
describe 'associations' do
subject { create(:project_security_setting) }
it { is_expected.to belong_to(:project) }
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
......@@ -21,6 +21,13 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
expect(subject[:help_page_path]).to eq(help_page_path('user/application_security/index'))
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
before do
create(
......@@ -52,12 +59,12 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
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
expect(subject[:auto_devops_enabled]).to be_falsy
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'))
end
......@@ -73,7 +80,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
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
create(
: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