Commit efceaf81 authored by James Lopez's avatar James Lopez

Merge branch '262112_synch_dismissal_information_always' into 'master'

Synch dismissal information between vulnerabilities and findings

See merge request gitlab-org/gitlab!46141
parents d99701be adea7553
...@@ -90,5 +90,13 @@ module Vulnerabilities ...@@ -90,5 +90,13 @@ module Vulnerabilities
def touch_pipeline def touch_pipeline
pipeline&.touch pipeline&.touch
end end
def finding
Finding.find_by(
project_id: project_id,
report_type: category,
project_fingerprint: project_fingerprint
)
end
end end
end end
...@@ -7,9 +7,13 @@ module Vulnerabilities ...@@ -7,9 +7,13 @@ module Vulnerabilities
def execute def execute
raise Gitlab::Access::AccessDeniedError unless authorized? raise Gitlab::Access::AccessDeniedError unless authorized?
@vulnerability.tap do |vulnerability| @vulnerability.transaction do
update_with_note(vulnerability, state: Vulnerability.states[:confirmed], confirmed_by: @user, confirmed_at: Time.current) DestroyDismissalFeedbackService.new(@user, @vulnerability).execute
update_with_note(@vulnerability, state: Vulnerability.states[:confirmed], confirmed_by: @user, confirmed_at: Time.current)
end end
@vulnerability
end end
end end
end end
# frozen_string_literal: true
require_dependency 'vulnerabilities/base_service'
# This service class removes all the dismissal feedback
# associated with a vulnerability through it's findings.
module Vulnerabilities
class DestroyDismissalFeedbackService < BaseService
def execute
@vulnerability.dismissed_findings.each do |finding|
unless destroy_feedback_for(finding)
handle_finding_revert_error(finding)
raise ActiveRecord::Rollback
end
end
end
private
def destroy_feedback_for(finding)
VulnerabilityFeedback::DestroyService
.new(@project, @user, finding.dismissal_feedback, revert_vulnerability_state: false)
.execute
end
def handle_finding_revert_error(finding)
@vulnerability.errors.add(
:base,
:finding_revert_to_detected_error,
message: _("failed to revert associated finding(id=%{finding_id}) to detected") %
{
finding_id: finding.id
})
end
end
end
...@@ -6,21 +6,24 @@ module Vulnerabilities ...@@ -6,21 +6,24 @@ module Vulnerabilities
class DismissService < BaseService class DismissService < BaseService
FindingsDismissResult = Struct.new(:ok?, :finding, :message) FindingsDismissResult = Struct.new(:ok?, :finding, :message)
def initialize(current_user, vulnerability, comment = nil) def initialize(current_user, vulnerability, comment = nil, dismiss_findings: true)
super(current_user, vulnerability) super(current_user, vulnerability)
@comment = comment @comment = comment
@dismiss_findings = dismiss_findings
end end
def execute def execute
raise Gitlab::Access::AccessDeniedError unless authorized? raise Gitlab::Access::AccessDeniedError unless authorized?
@vulnerability.transaction do @vulnerability.transaction do
result = dismiss_findings if dismiss_findings
result = dismiss_vulnerability_findings
unless result.ok? unless result.ok?
handle_finding_dismissal_error(result.finding, result.message) handle_finding_dismissal_error(result.finding, result.message)
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
end
update_with_note(@vulnerability, state: Vulnerability.states[:dismissed], dismissed_by: @user, dismissed_at: Time.current) update_with_note(@vulnerability, state: Vulnerability.states[:dismissed], dismissed_by: @user, dismissed_at: Time.current)
end end
...@@ -30,6 +33,8 @@ module Vulnerabilities ...@@ -30,6 +33,8 @@ module Vulnerabilities
private private
attr_reader :dismiss_findings
def feedback_service_for(finding) def feedback_service_for(finding)
VulnerabilityFeedback::CreateService.new(@project, @user, feedback_params_for(finding)) VulnerabilityFeedback::CreateService.new(@project, @user, feedback_params_for(finding))
end end
...@@ -40,11 +45,12 @@ module Vulnerabilities ...@@ -40,11 +45,12 @@ module Vulnerabilities
feedback_type: 'dismissal', feedback_type: 'dismissal',
project_fingerprint: finding.project_fingerprint, project_fingerprint: finding.project_fingerprint,
comment: @comment, comment: @comment,
pipeline: @project.latest_pipeline_with_security_reports(only_successful: true) pipeline: @project.latest_pipeline_with_security_reports(only_successful: true),
dismiss_vulnerability: false
} }
end end
def dismiss_findings def dismiss_vulnerability_findings
@vulnerability.findings.each do |finding| @vulnerability.findings.each do |finding|
result = feedback_service_for(finding).execute result = feedback_service_for(finding).execute
......
...@@ -7,9 +7,13 @@ module Vulnerabilities ...@@ -7,9 +7,13 @@ module Vulnerabilities
def execute def execute
raise Gitlab::Access::AccessDeniedError unless authorized? raise Gitlab::Access::AccessDeniedError unless authorized?
@vulnerability.tap do |vulnerability| @vulnerability.transaction do
update_with_note(vulnerability, state: Vulnerability.states[:resolved], resolved_by: @user, resolved_at: Time.current) DestroyDismissalFeedbackService.new(@user, @vulnerability).execute
update_with_note(@vulnerability, state: Vulnerability.states[:resolved], resolved_by: @user, resolved_at: Time.current)
end end
@vulnerability
end end
end end
end end
...@@ -10,46 +10,12 @@ module Vulnerabilities ...@@ -10,46 +10,12 @@ module Vulnerabilities
raise Gitlab::Access::AccessDeniedError unless authorized? raise Gitlab::Access::AccessDeniedError unless authorized?
@vulnerability.transaction do @vulnerability.transaction do
revert_result = revert_findings_to_detected_state DestroyDismissalFeedbackService.new(@user, @vulnerability).execute
raise ActiveRecord::Rollback unless revert_result
update_with_note(@vulnerability, state: Vulnerability.states[:detected], **REVERT_PARAMS) update_with_note(@vulnerability, state: Vulnerability.states[:detected], **REVERT_PARAMS)
end end
@vulnerability @vulnerability
end end
private
def destroy_feedback_for(finding)
VulnerabilityFeedback::DestroyService
.new(@project, @user, finding.dismissal_feedback)
.execute
end
def revert_findings_to_detected_state
@vulnerability
.dismissed_findings
.each do |finding|
result = destroy_feedback_for(finding)
unless result
handle_finding_revert_error(finding)
return false
end
end
true
end
def handle_finding_revert_error(finding)
@vulnerability.errors.add(
:base,
:finding_revert_to_detected_error,
message: _("failed to revert associated finding(id=%{finding_id}) to detected") %
{
finding_id: finding.id
})
end
end end
end end
...@@ -2,21 +2,17 @@ ...@@ -2,21 +2,17 @@
module VulnerabilityFeedback module VulnerabilityFeedback
class CreateService < ::BaseService class CreateService < ::BaseService
def execute include Gitlab::Utils::StrongMemoize
vulnerability_feedback = @project.vulnerability_feedback.find_or_init_for(create_params)
def execute
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :create_vulnerability_feedback, vulnerability_feedback) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :create_vulnerability_feedback, vulnerability_feedback)
if vulnerability_feedback.for_issue? && if vulnerability_feedback.for_issue? && !vulnerability_feedback.vulnerability_data.blank?
!vulnerability_feedback.vulnerability_data.blank? create_issue
elsif vulnerability_feedback.for_merge_request? && !vulnerability_feedback.vulnerability_data.blank?
create_issue_for(vulnerability_feedback) create_merge_request
elsif vulnerability_feedback.for_merge_request? &&
!vulnerability_feedback.vulnerability_data.blank?
create_merge_request_for(vulnerability_feedback)
else else
vulnerability_feedback.save dismiss_existing_vulnerability
end end
if vulnerability_feedback.persisted? && vulnerability_feedback.valid? if vulnerability_feedback.persisted? && vulnerability_feedback.valid?
...@@ -34,9 +30,15 @@ module VulnerabilityFeedback ...@@ -34,9 +30,15 @@ module VulnerabilityFeedback
private private
attr_reader :params
def vulnerability_feedback
@vulnerability_feedback ||= @project.vulnerability_feedback.find_or_init_for(create_params)
end
def create_params def create_params
@params[:author] = @current_user @params[:author] = @current_user
@params.merge(comment_params) @params.merge(comment_params).except(:dismiss_vulnerability)
end end
def comment_params def comment_params
...@@ -52,7 +54,7 @@ module VulnerabilityFeedback ...@@ -52,7 +54,7 @@ module VulnerabilityFeedback
super().merge(vulnerability_feedback: vulnerability_feedback) super().merge(vulnerability_feedback: vulnerability_feedback)
end end
def create_issue_for(vulnerability_feedback) def create_issue
# Wrap Feedback and Issue creation in the same transaction # Wrap Feedback and Issue creation in the same transaction
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
result = Issues::CreateFromVulnerabilityDataService result = Issues::CreateFromVulnerabilityDataService
...@@ -80,7 +82,7 @@ module VulnerabilityFeedback ...@@ -80,7 +82,7 @@ module VulnerabilityFeedback
end end
end end
def create_merge_request_for(vulnerability_feedback) def create_merge_request
result = MergeRequests::CreateFromVulnerabilityDataService result = MergeRequests::CreateFromVulnerabilityDataService
.new(@project, @current_user, vulnerability_feedback.vulnerability_data) .new(@project, @current_user, vulnerability_feedback.vulnerability_data)
.execute .execute
...@@ -115,5 +117,26 @@ module VulnerabilityFeedback ...@@ -115,5 +117,26 @@ module VulnerabilityFeedback
.new(current_user, vulnerability, issue, link_type: Vulnerabilities::IssueLink.link_types[:created]) .new(current_user, vulnerability, issue, link_type: Vulnerabilities::IssueLink.link_types[:created])
.execute .execute
end end
def dismiss_existing_vulnerability
ActiveRecord::Base.transaction do
if dismiss_vulnerability? && existing_vulnerability
Vulnerabilities::DismissService.new(current_user,
existing_vulnerability,
params[:comment],
dismiss_findings: false).execute
end
raise ActiveRecord::Rollback unless vulnerability_feedback.save
end
end
def existing_vulnerability
strong_memoize(:existing_vulnerability) { vulnerability_feedback.finding&.vulnerability }
end
def dismiss_vulnerability?
params[:dismiss_vulnerability] != false && can?(current_user, :admin_vulnerability, project)
end
end end
end end
...@@ -2,15 +2,34 @@ ...@@ -2,15 +2,34 @@
module VulnerabilityFeedback module VulnerabilityFeedback
class DestroyService < ::BaseService class DestroyService < ::BaseService
def initialize(project, user, vulnerability_feedback) include Gitlab::Utils::StrongMemoize
@project, @current_user, @vulnerability_feedback = project, user, vulnerability_feedback
def initialize(project, user, vulnerability_feedback, revert_vulnerability_state: true)
@project, @current_user, @vulnerability_feedback, @revert_vulnerability_state = project, user, vulnerability_feedback, revert_vulnerability_state
end end
def execute def execute
# TODO: Add system note when destroying a dismissal feedback # TODO: Add system note when destroying a dismissal feedback
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_vulnerability_feedback, @vulnerability_feedback) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_vulnerability_feedback, vulnerability_feedback)
revert_vulnerability if revert_vulnerability_state?
vulnerability_feedback.destroy
end
private
attr_reader :vulnerability_feedback, :revert_vulnerability_state
def revert_vulnerability
Vulnerabilities::RevertToDetectedService.new(current_user, existing_vulnerability).execute
end
def revert_vulnerability_state?
revert_vulnerability_state && existing_vulnerability
end
@vulnerability_feedback.destroy def existing_vulnerability
strong_memoize(:existing_vulnerability) { vulnerability_feedback.finding&.vulnerability }
end end
end end
end end
---
title: Synchronize dismissal information between `finding` and `vulnerability` entries
merge_request: 46141
author:
type: fixed
...@@ -235,4 +235,25 @@ RSpec.describe Vulnerabilities::Feedback do ...@@ -235,4 +235,25 @@ RSpec.describe Vulnerabilities::Feedback do
it { is_expected.to eq({ project_id: project.id, category: category, project_fingerprint: project_fingerprint }) } it { is_expected.to eq({ project_id: project.id, category: category, project_fingerprint: project_fingerprint }) }
end end
describe '#finding' do
let_it_be(:feedback) { create(:vulnerability_feedback) }
subject { feedback.finding }
context 'when the is no finding persisted' do
it { is_expected.to be_nil }
end
context 'when there is a persisted finding' do
let!(:finding) do
create(:vulnerabilities_finding,
project: feedback.project,
report_type: feedback.category,
project_fingerprint: feedback.project_fingerprint)
end
it { is_expected.to eq(finding) }
end
end
end end
...@@ -22,6 +22,7 @@ RSpec.describe Vulnerabilities::ConfirmService do ...@@ -22,6 +22,7 @@ RSpec.describe Vulnerabilities::ConfirmService do
end end
it_behaves_like 'calls vulnerability statistics utility services in order' it_behaves_like 'calls vulnerability statistics utility services in order'
it_behaves_like 'removes dismissal feedback from associated findings'
it 'confirms a vulnerability' do it 'confirms a vulnerability' do
Timecop.freeze do Timecop.freeze do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Vulnerabilities::DestroyDismissalFeedbackService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:vulnerability) { create(:vulnerability, project: project) }
before(:all) do
finding_1 = create(:vulnerabilities_finding, project: project)
finding_2 = create(:vulnerabilities_finding, project: project)
create(:vulnerability_feedback, project: project, category: finding_1.report_type, project_fingerprint: finding_1.project_fingerprint)
create(:vulnerability_feedback, project: project, category: finding_2.report_type, project_fingerprint: finding_2.project_fingerprint)
create(:vulnerability_feedback)
vulnerability.findings << finding_1
vulnerability.findings << finding_2
end
describe '#execute' do
subject(:destroy_feedback) { described_class.new(user, vulnerability).execute }
context 'without necessary permissions' do
it 'raises `Gitlab::Access::AccessDeniedError` error' do
expect { destroy_feedback }.to raise_error(Gitlab::Access::AccessDeniedError)
.and not_change { Vulnerabilities::Feedback.count }
end
end
context 'with necessary permissions' do
before do
project.add_developer(user)
end
it 'destroys the feedback records associated with the findings of the given vulnerability' do
expect { destroy_feedback }.to change { Vulnerabilities::Feedback.count }.from(3).to(1)
end
end
end
end
...@@ -14,7 +14,8 @@ RSpec.describe Vulnerabilities::DismissService do ...@@ -14,7 +14,8 @@ RSpec.describe Vulnerabilities::DismissService do
let!(:pipeline) { create(:ci_pipeline, :success, project: project) } let!(:pipeline) { create(:ci_pipeline, :success, project: project) }
let!(:build) { create(:ee_ci_build, :sast, pipeline: pipeline) } let!(:build) { create(:ee_ci_build, :sast, pipeline: pipeline) }
let(:vulnerability) { create(:vulnerability, :with_findings, project: project) } let(:vulnerability) { create(:vulnerability, :with_findings, project: project) }
let(:service) { described_class.new(user, vulnerability) } let(:dismiss_findings) { true }
let(:service) { described_class.new(user, vulnerability, dismiss_findings: dismiss_findings) }
subject(:dismiss_vulnerability) { service.execute } subject(:dismiss_vulnerability) { service.execute }
...@@ -25,6 +26,21 @@ RSpec.describe Vulnerabilities::DismissService do ...@@ -25,6 +26,21 @@ RSpec.describe Vulnerabilities::DismissService do
it_behaves_like 'calls vulnerability statistics utility services in order' it_behaves_like 'calls vulnerability statistics utility services in order'
context 'when the `dismiss_findings` argument is false' do
let(:dismiss_findings) { false }
it 'dismisses only vulnerability' do
Timecop.freeze do
dismiss_vulnerability
expect(vulnerability.reload).to(
have_attributes(state: 'dismissed', dismissed_by: user, dismissed_at: be_like_time(Time.current)))
expect(vulnerability.findings).not_to include have_vulnerability_dismissal_feedback
end
end
end
context 'when the `dismiss_findings` argument is not false' do
it 'dismisses a vulnerability and its associated findings' do it 'dismisses a vulnerability and its associated findings' do
Timecop.freeze do Timecop.freeze do
dismiss_vulnerability dismiss_vulnerability
...@@ -34,6 +50,7 @@ RSpec.describe Vulnerabilities::DismissService do ...@@ -34,6 +50,7 @@ RSpec.describe Vulnerabilities::DismissService do
expect(vulnerability.findings).to all have_vulnerability_dismissal_feedback expect(vulnerability.findings).to all have_vulnerability_dismissal_feedback
end end
end end
end
context 'when comment is added' do context 'when comment is added' do
let(:comment) { 'Dismissal Comment' } let(:comment) { 'Dismissal Comment' }
...@@ -62,7 +79,7 @@ RSpec.describe Vulnerabilities::DismissService do ...@@ -62,7 +79,7 @@ RSpec.describe Vulnerabilities::DismissService do
context 'when there is a finding dismissal error' do context 'when there is a finding dismissal error' do
before do before do
allow(service).to receive(:dismiss_findings).and_return( allow(service).to receive(:dismiss_vulnerability_findings).and_return(
described_class::FindingsDismissResult.new(false, broken_finding, 'something went wrong')) described_class::FindingsDismissResult.new(false, broken_finding, 'something went wrong'))
end end
......
...@@ -22,6 +22,7 @@ RSpec.describe Vulnerabilities::ResolveService do ...@@ -22,6 +22,7 @@ RSpec.describe Vulnerabilities::ResolveService do
end end
it_behaves_like 'calls vulnerability statistics utility services in order' it_behaves_like 'calls vulnerability statistics utility services in order'
it_behaves_like 'removes dismissal feedback from associated findings'
it 'resolves a vulnerability' do it 'resolves a vulnerability' do
Timecop.freeze do Timecop.freeze do
......
...@@ -23,7 +23,6 @@ RSpec.describe Vulnerabilities::RevertToDetectedService do ...@@ -23,7 +23,6 @@ RSpec.describe Vulnerabilities::RevertToDetectedService do
expect(vulnerability.reload).to( expect(vulnerability.reload).to(
have_attributes(state: 'detected', dismissed_by: nil, dismissed_at: nil, resolved_by: nil, resolved_at: nil, confirmed_by: nil, confirmed_at: nil)) have_attributes(state: 'detected', dismissed_by: nil, dismissed_at: nil, resolved_by: nil, resolved_at: nil, confirmed_by: nil, confirmed_at: nil))
expect(vulnerability.findings).to all not_have_vulnerability_dismissal_feedback
end end
end end
...@@ -34,6 +33,7 @@ RSpec.describe Vulnerabilities::RevertToDetectedService do ...@@ -34,6 +33,7 @@ RSpec.describe Vulnerabilities::RevertToDetectedService do
end end
it_behaves_like 'calls vulnerability statistics utility services in order' it_behaves_like 'calls vulnerability statistics utility services in order'
it_behaves_like 'removes dismissal feedback from associated findings'
end end
context 'with an authorized user with proper permissions' do context 'with an authorized user with proper permissions' do
...@@ -59,23 +59,6 @@ RSpec.describe Vulnerabilities::RevertToDetectedService do ...@@ -59,23 +59,6 @@ RSpec.describe Vulnerabilities::RevertToDetectedService do
include_examples 'reverts vulnerability' include_examples 'reverts vulnerability'
end end
context 'when there is an error' do
let(:broken_finding) { vulnerability.findings.first }
let!(:dismissal_feedback) do
create(:vulnerability_feedback, :dismissal, project: broken_finding.project, project_fingerprint: broken_finding.project_fingerprint)
end
before do
allow(service).to receive(:destroy_feedback_for).and_return(false)
end
it 'responds with error' do
expect(revert_vulnerability_to_detected.errors.messages).to eq(
base: ["failed to revert associated finding(id=#{broken_finding.id}) to detected"])
end
end
context 'when security dashboard feature is disabled' do context 'when security dashboard feature is disabled' do
before do before do
stub_licensed_features(security_dashboard: false) stub_licensed_features(security_dashboard: false)
......
...@@ -7,20 +7,24 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do ...@@ -7,20 +7,24 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do
let(:project) { create(:project, :public, :repository, namespace: group) } let(:project) { create(:project, :public, :repository, namespace: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:dismiss_vulnerability) { true }
before do before do
group.add_developer(user) group.add_developer(user)
end end
context 'when params are valid' do context 'when params are valid' do
let(:category) { 'sast' }
let(:project_fingerprint) { '418291a26024a1445b23fe64de9380cdcdfd1fa8' }
let(:feedback_params) do let(:feedback_params) do
{ {
feedback_type: 'dismissal', pipeline_id: pipeline.id, category: 'sast', feedback_type: 'dismissal', pipeline_id: pipeline.id, category: category,
project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1fa8', project_fingerprint: project_fingerprint,
comment: 'a dismissal comment', comment: 'a dismissal comment',
dismiss_vulnerability: dismiss_vulnerability,
vulnerability_data: { vulnerability_data: {
blob_path: '/path/to/blob', blob_path: '/path/to/blob',
category: 'sast', category: category,
priority: 'Low', line: '41', priority: 'Low', line: '41',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java', file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM', cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
...@@ -42,6 +46,13 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do ...@@ -42,6 +46,13 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do
context 'when feedback_type is dismissal' do context 'when feedback_type is dismissal' do
let(:result) { described_class.new(project, user, feedback_params).execute } let(:result) { described_class.new(project, user, feedback_params).execute }
let!(:finding) do
create(:vulnerabilities_finding,
:detected,
project: project,
report_type: category,
project_fingerprint: project_fingerprint)
end
it 'creates the feedback with the given params' do it 'creates the feedback with the given params' do
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
...@@ -51,8 +62,8 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do ...@@ -51,8 +62,8 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do
expect(feedback.author).to eq(user) expect(feedback.author).to eq(user)
expect(feedback.feedback_type).to eq('dismissal') expect(feedback.feedback_type).to eq('dismissal')
expect(feedback.pipeline_id).to eq(pipeline.id) expect(feedback.pipeline_id).to eq(pipeline.id)
expect(feedback.category).to eq('sast') expect(feedback.category).to eq(category)
expect(feedback.project_fingerprint).to eq('418291a26024a1445b23fe64de9380cdcdfd1fa8') expect(feedback.project_fingerprint).to eq(project_fingerprint)
expect(feedback.for_dismissal?).to eq(true) expect(feedback.for_dismissal?).to eq(true)
expect(feedback.for_issue?).to eq(false) expect(feedback.for_issue?).to eq(false)
expect(feedback.issue).to be_nil expect(feedback.issue).to be_nil
...@@ -87,6 +98,44 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do ...@@ -87,6 +98,44 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do
expect(feedback.comment_timestamp).to be_nil expect(feedback.comment_timestamp).to be_nil
end end
end end
context 'when the `dismiss_vulnerability` argument is true' do
context 'when the security_dashboard is not enabled' do
it 'does not dismiss the existing vulnerability' do
expect { result }.not_to change { finding.vulnerability.reload.state }.from('detected')
end
end
context 'when the security_dashboard is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
it 'dismisses the existing vulnerability' do
expect { result }.to change { finding.vulnerability.reload.state }.from('detected').to('dismissed')
end
end
end
context 'when the `dismiss_vulnerability` argument is false' do
let(:dismiss_vulnerability) { false }
context 'when the security_dashboard is not enabled' do
it 'does not dismiss the existing vulnerability' do
expect { result }.not_to change { finding.vulnerability.reload.state }.from('detected')
end
end
context 'when the security_dashboard is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
it 'dismisses the existing vulnerability' do
expect { result }.not_to change { finding.vulnerability.reload.state }.from('detected')
end
end
end
end end
context 'when feedback_type is issue' do context 'when feedback_type is issue' do
...@@ -145,7 +194,7 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do ...@@ -145,7 +194,7 @@ RSpec.describe VulnerabilityFeedback::CreateService, '#execute' do
feedback_params.deep_merge( feedback_params.deep_merge(
feedback_type: 'issue', feedback_type: 'issue',
vulnerability_data: { vulnerability_id: vulnerability_id } vulnerability_data: { vulnerability_id: vulnerability_id }
) ).except(:dismiss_vulnerability)
end end
subject(:result) { described_class.new(project, user, feedback_params_with_vulnerability_id).execute } subject(:result) { described_class.new(project, user, feedback_params_with_vulnerability_id).execute }
......
...@@ -3,32 +3,67 @@ ...@@ -3,32 +3,67 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe VulnerabilityFeedback::DestroyService, '#execute' do RSpec.describe VulnerabilityFeedback::DestroyService, '#execute' do
let(:group) { create(:group) } let(:project) { create(:project, :public, :repository) }
let(:project) { create(:project, :public, :repository, namespace: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:vulnerability_feedback) { create(:vulnerability_feedback, feedback_type, project: project)} let(:vulnerability_feedback) { create(:vulnerability_feedback, feedback_type, project: project)}
let(:revert_vulnerability_state) { true }
let(:service_object) { described_class.new(project, user, vulnerability_feedback, revert_vulnerability_state: revert_vulnerability_state) }
before do before do
group.add_developer(user) project.add_developer(user)
stub_licensed_features(security_dashboard: true)
end end
subject { described_class.new(project, user, vulnerability_feedback).execute } subject(:destroy_feedback) { service_object.execute }
context 'when feedback_type is dismissal' do context 'when feedback_type is dismissal' do
let(:feedback_type) { :dismissal } let(:feedback_type) { :dismissal }
context 'when the user is authorized' do
context 'when the `revert_vulnerability_state` argument is set as true' do
context 'when the finding is not associated with a vulnerability' do
it 'destroys the feedback' do it 'destroys the feedback' do
subject expect { destroy_feedback }.to change { vulnerability_feedback.destroyed? }.to(true)
end
end
context 'when the finding is associated with a vulnerability' do
let(:finding) { create(:vulnerabilities_finding, :dismissed, project: project) }
let(:vulnerability_feedback) { finding.dismissal_feedback }
it 'changes the state of the vulnerability to `detected`' do
expect { destroy_feedback }.to change { finding.vulnerability.reload.state }.from('dismissed').to('detected')
end
end
end
context 'when the `revert_vulnerability_state` argument is set as false' do
let(:revert_vulnerability_state) { false }
context 'when the finding is not associated with a vulnerability' do
it 'destroys the feedback' do
expect { destroy_feedback }.to change { vulnerability_feedback.destroyed? }.to(true)
end
end
expect { vulnerability_feedback.reload }.to raise_error ActiveRecord::RecordNotFound context 'when the finding is associated with a vulnerability' do
let(:finding) { create(:vulnerabilities_finding, :dismissed, project: project) }
let(:vulnerability_feedback) { finding.dismissal_feedback }
it 'does not change the state of the vulnerability to `detected`' do
expect { destroy_feedback }.not_to change { finding.vulnerability.reload.state }
end
end
end
end end
context 'when user is not authorized' do context 'when user is not authorized' do
let(:unauthorized_user) { create(:user) } before do
project.add_guest(user)
end
it 'raise error if permission is denied' do it 'raise error if permission is denied' do
expect { described_class.new(project, unauthorized_user, vulnerability_feedback).execute } expect { destroy_feedback }.to raise_error(Gitlab::Access::AccessDeniedError)
.to raise_error(Gitlab::Access::AccessDeniedError)
end end
end end
end end
...@@ -37,8 +72,7 @@ RSpec.describe VulnerabilityFeedback::DestroyService, '#execute' do ...@@ -37,8 +72,7 @@ RSpec.describe VulnerabilityFeedback::DestroyService, '#execute' do
let(:feedback_type) { :issue } let(:feedback_type) { :issue }
it 'raise error as this type of feedback can not be destroyed' do it 'raise error as this type of feedback can not be destroyed' do
expect { described_class.new(project, user, vulnerability_feedback).execute } expect { destroy_feedback }.to raise_error(Gitlab::Access::AccessDeniedError)
.to raise_error(Gitlab::Access::AccessDeniedError)
end end
end end
...@@ -46,8 +80,7 @@ RSpec.describe VulnerabilityFeedback::DestroyService, '#execute' do ...@@ -46,8 +80,7 @@ RSpec.describe VulnerabilityFeedback::DestroyService, '#execute' do
let(:feedback_type) { :merge_request } let(:feedback_type) { :merge_request }
it 'raise error as this type of feedback can not be destroyed' do it 'raise error as this type of feedback can not be destroyed' do
expect { described_class.new(project, user, vulnerability_feedback).execute } expect { destroy_feedback }.to raise_error(Gitlab::Access::AccessDeniedError)
.to raise_error(Gitlab::Access::AccessDeniedError)
end end
end end
end end
# frozen_string_literal: true
RSpec.shared_examples 'removes dismissal feedback from associated findings' do
let(:finding) { create(:vulnerabilities_finding, vulnerability: vulnerability, project: vulnerability.project) }
before do
create(:vulnerability_feedback,
:dismissal,
project: finding.project,
category: finding.report_type,
project_fingerprint: finding.project_fingerprint)
end
context 'when there is no error' do
it 'removes dismissal feedback from associated findings' do
expect { subject }.to change { Vulnerabilities::Feedback.count }.by(-1)
end
end
context 'when there is an error' do
before do
allow_next_instance_of(VulnerabilityFeedback::DestroyService) do |destroy_service_object|
allow(destroy_service_object).to receive(:execute).and_return(false)
end
end
it 'does not remove any feedback' do
expect { subject }.not_to change { Vulnerabilities::Feedback.count }
end
it 'responds with error' do
expect(subject.errors.messages).to eq(
base: ["failed to revert associated finding(id=#{finding.id}) to detected"])
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