Skip to content
Snippets Groups Projects
Verified Commit 6e35bd6c authored by Gal Katz's avatar Gal Katz Committed by GitLab
Browse files

Add vulnerability_severity_overrides table

Changelog: added
MR: !176952
EE: true
parent de536df6
No related branches found
No related tags found
2 merge requests!180727Resolve "Extend job archival mechanism to the whole pipeline",!178577Add system notes to vulnerability severity overrides
......@@ -22,6 +22,7 @@ module SystemNoteHelper
'vulnerability_confirmed' => 'shield',
'vulnerability_dismissed' => 'cancel',
'vulnerability_resolved' => 'status_closed',
'vulnerability_severity_changed' => 'file-modified',
'published' => 'bullhorn',
'paging_started' => 'mobile',
'progress' => 'progress',
......
......@@ -10,7 +10,7 @@ module SystemNoteMetadata
epic_issue_moved issue_changed_epic epic_date_changed relate_epic unrelate_epic
vulnerability_confirmed vulnerability_dismissed vulnerability_resolved vulnerability_detected
iteration paging_started progress checkin_reminder approvals_reset
notify_service
notify_service vulnerability_severity_changed
].freeze
EE_TYPES_WITH_CROSS_REFERENCES = %w[
......
......@@ -14,23 +14,25 @@ def change_vulnerability_state(body = nil)
end
class << self
def formatted_note(from_state, to_state, dismissal_reason, comment)
def formatted_note(transition, to_value, reason, comment, attribute = "status", from_value = nil)
format(
"%{from_state} vulnerability status to %{to_state}%{reason}%{comment}",
from_state: from_state,
to_state: to_state.to_s.titleize,
"%{transition} vulnerability %{attribute}%{from} to %{to_value}%{reason}%{comment}",
transition: transition,
attribute: attribute,
from: formatted_from(from_value),
to_value: to_value.to_s.titleize,
comment: formatted_comment(comment),
reason: formatted_reason(dismissal_reason, to_state)
reason: formatted_reason(reason, to_value)
)
end
private
def formatted_reason(dismissal_reason, to_state)
return if to_state.to_sym != :dismissed
return if dismissal_reason.blank?
def formatted_reason(reason, to_value)
return if to_value.to_sym != :dismissed
return if reason.blank?
": #{dismissal_reason.titleize}"
": #{reason.titleize}"
end
def formatted_comment(comment)
......@@ -38,6 +40,12 @@ def formatted_comment(comment)
format(' with the following comment: "%{comment}"', comment: comment)
end
def formatted_from(from_value)
return unless from_value.present?
format(' from %{from_value}', from_value: from_value.to_s.titleize)
end
end
private
......@@ -45,7 +53,7 @@ def formatted_comment(comment)
def state_change_body
if state_transition.present?
self.class.formatted_note(
from_status,
transition_name,
to_state,
state_transition.dismissal_reason,
state_transition.comment
......@@ -55,7 +63,7 @@ def state_change_body
end
end
def from_status
def transition_name
state_transition.to_state_detected? ? 'reverted' : 'changed'
end
......
......@@ -20,6 +20,11 @@ def update(vulnerabilities_ids)
update_support_tables(vulnerabilities, db_attributes)
vulnerabilities.update_all(db_attributes[:vulnerabilities])
end
Note.transaction do
notes_ids = Note.insert_all!(db_attributes[:system_notes], returning: %w[id])
SystemNoteMetadata.insert_all!(system_note_metadata_attributes_for(notes_ids))
end
end
def authorized_for_project(project)
......@@ -43,7 +48,8 @@ def severity_overrides_attributes_for(vulnerability_attrs)
def db_attributes_for(vulnerability_attrs)
{
vulnerabilities: vulnerabilities_update_attributes,
severity_overrides: severity_overrides_attributes_for(vulnerability_attrs)
severity_overrides: severity_overrides_attributes_for(vulnerability_attrs),
system_notes: system_note_attributes_for(vulnerability_attrs)
}
end
......@@ -70,5 +76,48 @@ def vulnerabilities_update_attributes
updated_at: now
}
end
def system_note_metadata_action
"vulnerability_severity_changed"
end
def system_note_attributes_for(vulnerability_attrs)
vulnerability_attrs.map do |id, severity, project_id, namespace_id|
{
noteable_type: "Vulnerability",
noteable_id: id,
project_id: project_id,
namespace_id: namespace_id,
system: true,
note: ::SystemNotes::VulnerabilitiesService.formatted_note(
'changed',
@new_severity,
nil,
comment,
'severity',
severity
),
author_id: user.id,
created_at: now,
updated_at: now,
discussion_id: Discussion.discussion_id(Note.new({
noteable_id: id,
noteable_type: "Vulnerability"
}))
}
end
end
def system_note_metadata_attributes_for(results)
results.map do |row|
id = row['id']
{
note_id: id,
action: system_note_metadata_action,
created_at: now,
updated_at: now
}
end
end
end
end
......@@ -105,4 +105,30 @@
end
end
end
describe '#formatted_note for severity override' do
subject(:formatted_note) do
described_class.formatted_note('changed', to_severity, nil, comment, 'severity', from_severity)
end
let(:from_severity) { 'low' }
let(:to_severity) { 'critical' }
let(:comment) { nil }
context 'when no comment is passed' do
it 'returns the note text correctly' do
expect(formatted_note).to eq("changed vulnerability severity from #{from_severity.titleize} " \
"to #{to_severity.titleize}")
end
end
context 'when comment is passed' do
let(:comment) { 'Test comment' }
it 'returns the note text correctly' do
expect(formatted_note).to eq("changed vulnerability severity from #{from_severity.titleize} " \
"to #{to_severity.titleize} with the following comment: \"Test comment\"")
end
end
end
end
......@@ -68,6 +68,10 @@
"changed vulnerability status to Dismissed: Used In Tests with the following comment: \"#{comment}\""
)
expect(last_note).to be_system
last_system_note_metadata = SystemNoteMetadata.last
expect(last_system_note_metadata.note_id).to eq(last_note.id)
expect(last_system_note_metadata.action).to eq("vulnerability_dismissed")
end
it 'updates the dismissal reason for each vulnerability read record' do
......
......@@ -58,7 +58,7 @@
end
it 'inserts a severity override record for each vulnerability' do
service.execute
expect { service.execute }.to change { Vulnerabilities::SeverityOverride.count }.by(vulnerability_ids.count)
vulnerability.reload
last_override = Vulnerabilities::SeverityOverride.last
......@@ -68,6 +68,24 @@
expect(last_override.author).to eq(user)
end
it 'inserts a system note for each vulnerability' do
expect { service.execute }.to change { Note.count }.by(vulnerability_ids.count)
last_note = Note.last
expect(last_note.noteable).to eq(vulnerability)
expect(last_note.author).to eq(user)
expect(last_note.project).to eq(project)
expect(last_note.namespace_id).to eq(project.project_namespace_id)
expect(last_note.note).to eq(
"changed vulnerability severity from High to #{new_severity.titleize} " \
"with the following comment: \"#{comment}\"")
expect(last_note).to be_system
last_system_note_metadata = SystemNoteMetadata.last
expect(last_system_note_metadata.note_id).to eq(last_note.id)
expect(last_system_note_metadata.action).to eq("vulnerability_severity_changed")
end
it 'returns a service response' do
result = service.execute
......@@ -97,6 +115,14 @@
end
end
it 'inserts a severity override record for each vulnerability' do
expect { service.execute }.to change { Vulnerabilities::SeverityOverride.count }.by(vulnerability_ids.count)
end
it 'inserts a system note for the vulnerability' do
expect { service.execute }.to change { Note.count }.by(vulnerability_ids.count)
end
it 'does not introduce N+1 queries' do
control = ActiveRecord::QueryRecorder.new do
described_class.new(user, vulnerability_ids, comment, new_severity).execute
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment