Commit fb620bd6 authored by Adam Hegyi's avatar Adam Hegyi

Merge branch 'sk/341393-create-vuln-reads-trigger' into 'master'

Add triggers to sync vulnerability_reads

See merge request gitlab-org/gitlab!75230
parents 1a7122b4 b9bf81f1
# frozen_string_literal: true
class AddInsertOrUpdateVulnerabilityReadsTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
FUNCTION_NAME = 'insert_or_update_vulnerability_reads'
TRIGGER_NAME = 'trigger_insert_or_update_vulnerability_reads_from_occurrences'
def up
execute(<<~SQL)
CREATE OR REPLACE FUNCTION #{FUNCTION_NAME}()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
severity smallint;
state smallint;
report_type smallint;
resolved_on_default_branch boolean;
BEGIN
IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN
RETURN NULL;
END IF;
IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN
RETURN NULL;
END IF;
SELECT
vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch
INTO
severity, state, report_type, resolved_on_default_branch
FROM
vulnerabilities
WHERE
vulnerabilities.id = NEW.vulnerability_id;
INSERT INTO vulnerability_reads (vulnerability_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id)
VALUES (NEW.vulnerability_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id')
ON CONFLICT(vulnerability_id) DO NOTHING;
RETURN NULL;
END
$$;
SQL
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_NAME}
AFTER INSERT OR UPDATE ON vulnerability_occurrences
FOR EACH ROW
EXECUTE PROCEDURE #{FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerability_occurrences, TRIGGER_NAME)
drop_function(FUNCTION_NAME)
end
end
# frozen_string_literal: true
class AddUpdateVulnerabilityReadsTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
TRIGGER_NAME = 'trigger_update_vulnerability_reads_on_vulnerability_update'
FUNCTION_NAME = 'update_vulnerability_reads_from_vulnerability'
def up
create_trigger_function(FUNCTION_NAME, replace: true) do
<<~SQL
UPDATE
vulnerability_reads
SET
severity = NEW.severity,
state = NEW.state,
resolved_on_default_branch = NEW.resolved_on_default_branch
WHERE vulnerability_id = NEW.id;
RETURN NULL;
SQL
end
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_NAME}
AFTER UPDATE ON vulnerabilities
FOR EACH ROW
WHEN (
OLD.severity IS DISTINCT FROM NEW.severity OR
OLD.state IS DISTINCT FROM NEW.state OR
OLD.resolved_on_default_branch IS DISTINCT FROM NEW.resolved_on_default_branch
)
EXECUTE PROCEDURE #{FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerabilities, TRIGGER_NAME)
drop_function(FUNCTION_NAME)
end
end
# frozen_string_literal: true
class AddUpdateVulnerabilityReadsLocationTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
TRIGGER_NAME = 'trigger_update_location_on_vulnerability_occurrences_update'
FUNCTION_NAME = 'update_location_from_vulnerability_occurrences'
def up
create_trigger_function(FUNCTION_NAME, replace: true) do
<<~SQL
UPDATE
vulnerability_reads
SET
location_image = NEW.location->>'image',
cluster_agent_id = NEW.location->'kubernetes_resource'->>'agent_id'
WHERE
vulnerability_id = NEW.vulnerability_id;
RETURN NULL;
SQL
end
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_NAME}
AFTER UPDATE ON vulnerability_occurrences
FOR EACH ROW
WHEN (
NEW.report_type IN (2, 7) AND (
OLD.location->>'image' IS DISTINCT FROM NEW.location->>'image' OR
OLD.location->'kubernetes_resource'->>'agent_id' IS DISTINCT FROM NEW.location->'kubernetes_resource'->>'agent_id'
)
)
EXECUTE PROCEDURE #{FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerability_occurrences, TRIGGER_NAME)
drop_function(FUNCTION_NAME)
end
end
# frozen_string_literal: true
class AddHasIssuesOnVulnerabilityReadsTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
TRIGGER_ON_INSERT = 'trigger_update_has_issues_on_vulnerability_issue_links_update'
INSERT_FUNCTION_NAME = 'set_has_issues_on_vulnerability_reads'
TRIGGER_ON_DELETE = 'trigger_update_has_issues_on_vulnerability_issue_links_delete'
DELETE_FUNCTION_NAME = 'unset_has_issues_on_vulnerability_reads'
def up
create_trigger_function(INSERT_FUNCTION_NAME, replace: true) do
<<~SQL
UPDATE
vulnerability_reads
SET
has_issues = true
WHERE
vulnerability_id = NEW.vulnerability_id AND has_issues IS FALSE;
RETURN NULL;
SQL
end
execute(<<~SQL)
CREATE OR REPLACE FUNCTION #{DELETE_FUNCTION_NAME}()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
has_issue_links integer;
BEGIN
PERFORM 1
FROM
vulnerability_reads
WHERE
vulnerability_id = OLD.vulnerability_id
FOR UPDATE;
SELECT 1 INTO has_issue_links FROM vulnerability_issue_links WHERE vulnerability_id = OLD.vulnerability_id LIMIT 1;
IF (has_issue_links = 1) THEN
RETURN NULL;
END IF;
UPDATE
vulnerability_reads
SET
has_issues = false
WHERE
vulnerability_id = OLD.vulnerability_id;
RETURN NULL;
END
$$;
SQL
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_ON_INSERT}
AFTER INSERT ON vulnerability_issue_links
FOR EACH ROW
EXECUTE FUNCTION #{INSERT_FUNCTION_NAME}();
SQL
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_ON_DELETE}
AFTER DELETE ON vulnerability_issue_links
FOR EACH ROW
EXECUTE FUNCTION #{DELETE_FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerability_issue_links, TRIGGER_ON_INSERT)
drop_function(INSERT_FUNCTION_NAME)
drop_trigger(:vulnerability_issue_links, TRIGGER_ON_DELETE)
drop_function(DELETE_FUNCTION_NAME)
end
end
c1af9546bdfa0f32c3c2faf362062cd300800514e5b1efd1fa8a1770753d00e5
\ No newline at end of file
8b51ae2b13066a56d2131efb7ea746335513031e751fb231e43121552d6f2b1d
\ No newline at end of file
f385631d0317630661d487011a228501a6cbc71de25ca457d75e6a815c598045
\ No newline at end of file
4726d84ff42e64b1c47c5ba454ff5be05f434a86bb2af4bbe27dd00e5e3da5cb
\ No newline at end of file
......@@ -45,6 +45,39 @@ RETURN NULL;
END
$$;
CREATE FUNCTION insert_or_update_vulnerability_reads() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
severity smallint;
state smallint;
report_type smallint;
resolved_on_default_branch boolean;
BEGIN
IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN
RETURN NULL;
END IF;
IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN
RETURN NULL;
END IF;
SELECT
vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch
INTO
severity, state, report_type, resolved_on_default_branch
FROM
vulnerabilities
WHERE
vulnerabilities.id = NEW.vulnerability_id;
INSERT INTO vulnerability_reads (vulnerability_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id)
VALUES (NEW.vulnerability_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id')
ON CONFLICT(vulnerability_id) DO NOTHING;
RETURN NULL;
END
$$;
CREATE FUNCTION insert_projects_sync_event() RETURNS trigger
LANGUAGE plpgsql
AS $$
......@@ -107,6 +140,83 @@ RETURN NULL;
END
$$;
CREATE FUNCTION set_has_issues_on_vulnerability_reads() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
vulnerability_reads
SET
has_issues = true
WHERE
vulnerability_id = NEW.vulnerability_id AND has_issues IS FALSE;
RETURN NULL;
END
$$;
CREATE FUNCTION unset_has_issues_on_vulnerability_reads() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
has_issue_links integer;
BEGIN
PERFORM 1
FROM
vulnerability_reads
WHERE
vulnerability_id = OLD.vulnerability_id
FOR UPDATE;
SELECT 1 INTO has_issue_links FROM vulnerability_issue_links WHERE vulnerability_id = OLD.vulnerability_id LIMIT 1;
IF (has_issue_links = 1) THEN
RETURN NULL;
END IF;
UPDATE
vulnerability_reads
SET
has_issues = false
WHERE
vulnerability_id = OLD.vulnerability_id;
RETURN NULL;
END
$$;
CREATE FUNCTION update_location_from_vulnerability_occurrences() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
vulnerability_reads
SET
location_image = NEW.location->>'image',
cluster_agent_id = NEW.location->'kubernetes_resource'->>'agent_id'
WHERE
vulnerability_id = NEW.vulnerability_id;
RETURN NULL;
END
$$;
CREATE FUNCTION update_vulnerability_reads_from_vulnerability() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
vulnerability_reads
SET
severity = NEW.severity,
state = NEW.state,
resolved_on_default_branch = NEW.resolved_on_default_branch
WHERE vulnerability_id = NEW.id;
RETURN NULL;
END
$$;
CREATE TABLE audit_events (
id bigint NOT NULL,
author_id integer NOT NULL,
......@@ -29163,6 +29273,8 @@ CREATE TRIGGER trigger_has_external_wiki_on_type_new_updated AFTER UPDATE OF typ
CREATE TRIGGER trigger_has_external_wiki_on_update AFTER UPDATE ON integrations FOR EACH ROW WHEN (((new.type_new = 'Integrations::ExternalWiki'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_insert_or_update_vulnerability_reads_from_occurrences AFTER INSERT OR UPDATE ON vulnerability_occurrences FOR EACH ROW EXECUTE FUNCTION insert_or_update_vulnerability_reads();
CREATE TRIGGER trigger_namespaces_parent_id_on_insert AFTER INSERT ON namespaces FOR EACH ROW EXECUTE FUNCTION insert_namespaces_sync_event();
CREATE TRIGGER trigger_namespaces_parent_id_on_update AFTER UPDATE ON namespaces FOR EACH ROW WHEN ((old.parent_id IS DISTINCT FROM new.parent_id)) EXECUTE FUNCTION insert_namespaces_sync_event();
......@@ -29173,6 +29285,14 @@ CREATE TRIGGER trigger_projects_parent_id_on_update AFTER UPDATE ON projects FOR
CREATE TRIGGER trigger_type_new_on_insert AFTER INSERT ON integrations FOR EACH ROW EXECUTE FUNCTION integrations_set_type_new();
CREATE TRIGGER trigger_update_has_issues_on_vulnerability_issue_links_delete AFTER DELETE ON vulnerability_issue_links FOR EACH ROW EXECUTE FUNCTION unset_has_issues_on_vulnerability_reads();
CREATE TRIGGER trigger_update_has_issues_on_vulnerability_issue_links_update AFTER INSERT ON vulnerability_issue_links FOR EACH ROW EXECUTE FUNCTION set_has_issues_on_vulnerability_reads();
CREATE TRIGGER trigger_update_location_on_vulnerability_occurrences_update AFTER UPDATE ON vulnerability_occurrences FOR EACH ROW WHEN (((new.report_type = ANY (ARRAY[2, 7])) AND (((old.location ->> 'image'::text) IS DISTINCT FROM (new.location ->> 'image'::text)) OR (((old.location -> 'kubernetes_resource'::text) ->> 'agent_id'::text) IS DISTINCT FROM ((new.location -> 'kubernetes_resource'::text) ->> 'agent_id'::text))))) EXECUTE FUNCTION update_location_from_vulnerability_occurrences();
CREATE TRIGGER trigger_update_vulnerability_reads_on_vulnerability_update AFTER UPDATE ON vulnerabilities FOR EACH ROW WHEN (((old.severity IS DISTINCT FROM new.severity) OR (old.state IS DISTINCT FROM new.state) OR (old.resolved_on_default_branch IS DISTINCT FROM new.resolved_on_default_branch))) EXECUTE FUNCTION update_vulnerability_reads_from_vulnerability();
CREATE TRIGGER users_loose_fk_trigger AFTER DELETE ON users REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
ALTER TABLE ONLY chat_names
......@@ -32,4 +32,188 @@ RSpec.describe Vulnerabilities::Read, type: :model do
it { is_expected.to allow_value(false).for(:resolved_on_default_branch) }
it { is_expected.not_to allow_value(nil).for(:resolved_on_default_branch) }
end
describe 'triggers' do
let(:namespace) { create(:namespace) }
let(:user) { create(:user) }
let(:project) { create(:project, namespace: namespace) }
let(:issue) { create(:issue, project: project) }
let(:scanner) { create(:vulnerabilities_scanner, project: project) }
let(:identifier) { create(:vulnerabilities_identifier, project: project) }
let(:vulnerability) { create_vulnerability }
let(:vulnerability2) { create_vulnerability }
let(:finding) { create_finding(primary_identifier: identifier) }
describe 'trigger on vulnerability_occurrences insert' do
context 'when vulnerability_id is set' do
it 'creates a new vulnerability_reads row' do
expect do
create_finding(vulnerability: vulnerability2)
end.to change { Vulnerabilities::Read.count }.from(0).to(1)
end
end
context 'when vulnerability_id is not set' do
it 'does not create a new vulnerability_reads row' do
expect do
create_finding
end.not_to change { Vulnerabilities::Read.count }
end
end
end
describe 'trigger on vulnerability_occurrences update' do
context 'when vulnerability_id is updated' do
it 'creates a new vulnerability_reads row' do
expect do
finding.update!(vulnerability_id: vulnerability.id)
end.to change { Vulnerabilities::Read.count }.from(0).to(1)
end
end
context 'when vulnerability_id is not updated' do
it 'does not create a new vulnerability_reads row' do
finding.update!(vulnerability_id: nil)
expect do
finding.update!(location: '')
end.not_to change { Vulnerabilities::Read.count }
end
end
end
describe 'trigger on vulnerability_occurrences location update' do
context 'when image is updated' do
it 'updates location_image in vulnerability_reads' do
finding = create_finding(vulnerability: vulnerability, report_type: 7, location: { "image" => "alpine:3.4" })
expect do
finding.update!(location: { "image" => "alpine:4" })
end.to change { Vulnerabilities::Read.first.location_image }.from("alpine:3.4").to("alpine:4")
end
end
context 'when agent_id is updated' do
it 'updates cluster_agent_id in vulnerability_reads' do
finding = create_finding(vulnerability: vulnerability, report_type: 7, location: { "image" => "alpine:3.4" })
expect do
finding.update!(location: { "kubernetes_resource" => { "agent_id" => "1234" } })
end.to change { Vulnerabilities::Read.first.cluster_agent_id }.from(nil).to("1234")
end
end
context 'when image or agent_id is not updated' do
it 'does not update location_image or cluster_agent_id in vulnerability_reads' do
finding = create_finding(
vulnerability: vulnerability,
report_type: 7,
location: { "image" => "alpine:3.4", "kubernetes_resource" => { "agent_id" => "1234" } }
)
expect do
finding.update!(project_fingerprint: "123qweasdzx")
end.not_to change { Vulnerabilities::Read.first.location_image }
end
end
end
describe 'trigger on vulnerabilities update' do
before do
create_finding(vulnerability: vulnerability, report_type: 7)
end
context 'when vulnerability attributes are updated' do
it 'updates vulnerability attributes in vulnerability_reads' do
expect do
vulnerability.update!(severity: 6)
end.to change { Vulnerabilities::Read.first.severity }.from("critical").to("high")
end
end
context 'when vulnerability attributes are not updated' do
it 'does not update vulnerability attributes in vulnerability_reads' do
expect do
vulnerability.update!(title: "New vulnerability")
end.not_to change { Vulnerabilities::Read.first }
end
end
end
describe 'trigger on vulnerabilities_issue_link' do
context 'on insert' do
before do
create_finding(vulnerability: vulnerability, report_type: 7)
end
it 'updates has_issues in vulnerability_reads' do
expect do
create(:vulnerabilities_issue_link, vulnerability: vulnerability, issue: issue)
end.to change { Vulnerabilities::Read.first.has_issues }.from(false).to(true)
end
end
context 'on delete' do
before do
create_finding(vulnerability: vulnerability, report_type: 7)
end
let(:issue2) { create(:issue, project: project) }
it 'does not change has_issues when there exists another issue' do
issue_link1 = create(:vulnerabilities_issue_link, vulnerability: vulnerability, issue: issue)
create(:vulnerabilities_issue_link, vulnerability: vulnerability, issue: issue2)
expect do
issue_link1.delete
end.not_to change { Vulnerabilities::Read.first.has_issues }
end
it 'unsets has_issues when all issues are deleted' do
issue_link1 = create(:vulnerabilities_issue_link, vulnerability: vulnerability, issue: issue)
issue_link2 = create(:vulnerabilities_issue_link, vulnerability: vulnerability, issue: issue2)
expect do
issue_link1.delete
issue_link2.delete
end.to change { Vulnerabilities::Read.first.has_issues }.from(true).to(false)
end
end
end
end
private
def create_vulnerability(severity: 7, confidence: 7, report_type: 0)
create(:vulnerability,
project: project,
author: user,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding(
vulnerability: nil, primary_identifier: identifier, severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
create(:vulnerabilities_finding,
vulnerability: vulnerability,
project: project,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner: scanner,
primary_identifier: primary_identifier,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end
......@@ -87,7 +87,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveDuplicateVulnerabilitiesFindin
let!(:unrelated_finding) do
create_finding!(
id: 9999999,
uuid: "unreleated_finding",
uuid: Gitlab::UUID.v5(SecureRandom.hex),
vulnerability_id: nil,
report_type: 1,
location_fingerprint: 'random_location_fingerprint',
......
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddInsertOrUpdateVulnerabilityReadsTrigger do
let(:migration) { described_class.new }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
author_id: user.id
)
end
let(:vulnerability2) do
create_vulnerability!(
project_id: project.id,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
let(:finding) do
create_finding!(
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
end
describe '#up' do
before do
migrate!
end
describe 'UPDATE trigger' do
context 'when vulnerability_id is updated' do
it 'creates a new vulnerability_reads row' do
expect do
finding.update!(vulnerability_id: vulnerability.id)
end.to change { vulnerability_reads.count }.from(0).to(1)
end
end
context 'when vulnerability_id is not updated' do
it 'does not create a new vulnerability_reads row' do
finding.update!(vulnerability_id: nil)
expect do
finding.update!(location: '')
end.not_to change { vulnerability_reads.count }
end
end
end
describe 'INSERT trigger' do
context 'when vulnerability_id is set' do
it 'creates a new vulnerability_reads row' do
expect do
create_finding!(
vulnerability_id: vulnerability2.id,
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
end.to change { vulnerability_reads.count }.from(0).to(1)
end
end
context 'when vulnerability_id is not set' do
it 'does not create a new vulnerability_reads row' do
expect do
create_finding!(
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
end.not_to change { vulnerability_reads.count }
end
end
end
end
describe '#down' do
before do
migration.up
migration.down
end
it 'drops the trigger' do
expect do
finding.update!(vulnerability_id: vulnerability.id)
end.not_to change { vulnerability_reads.count }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddUpdateVulnerabilityReadsTrigger do
let(:migration) { described_class.new }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:issue_links) { table(:vulnerability_issue_links) }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
report_type: 7,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
describe '#up' do
before do
migrate!
end
describe 'UPDATE trigger' do
before do
create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
primary_identifier_id: identifier.id
)
end
context 'when vulnerability attributes are updated' do
it 'updates vulnerability attributes in vulnerability_reads' do
expect do
vulnerability.update!(severity: 6)
end.to change { vulnerability_reads.first.severity }.from(7).to(6)
end
end
context 'when vulnerability attributes are not updated' do
it 'does not update vulnerability attributes in vulnerability_reads' do
expect do
vulnerability.update!(title: "New vulnerability")
end.not_to change { vulnerability_reads.first }
end
end
end
end
describe '#down' do
before do
migration.up
migration.down
create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
primary_identifier_id: identifier.id
)
end
it 'drops the trigger' do
expect do
vulnerability.update!(severity: 6)
end.not_to change { vulnerability_reads.first.severity }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddUpdateVulnerabilityReadsLocationTrigger do
let(:migration) { described_class.new }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:issue_links) { table(:vulnerability_issue_links) }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
report_type: 7,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
describe '#up' do
before do
migrate!
end
describe 'UPDATE trigger' do
context 'when image is updated' do
it 'updates location_image in vulnerability_reads' do
finding = create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
location: { "image" => "alpine:3.4" },
primary_identifier_id: identifier.id
)
expect do
finding.update!(location: { "image" => "alpine:4", "kubernetes_resource" => { "agent_id" => "1234" } })
end.to change { vulnerability_reads.first.location_image }.from("alpine:3.4").to("alpine:4")
end
end
context 'when image is not updated' do
it 'updates location_image in vulnerability_reads' do
finding = create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
location: { "image" => "alpine:3.4", "kubernetes_resource" => { "agent_id" => "1234" } },
primary_identifier_id: identifier.id
)
expect do
finding.update!(project_fingerprint: "123qweasdzx")
end.not_to change { vulnerability_reads.first.location_image }
end
end
end
end
describe '#down' do
before do
migration.up
migration.down
end
it 'drops the trigger' do
finding = create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
expect do
finding.update!(location: '{"image":"alpine:4"}')
end.not_to change { vulnerability_reads.first.location_image }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddHasIssuesOnVulnerabilityReadsTrigger do
let(:migration) { described_class.new }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:issue_links) { table(:vulnerability_issue_links) }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
before do
create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
@vulnerability_read = vulnerability_reads.first
end
describe '#up' do
before do
migrate!
end
describe 'INSERT trigger' do
it 'updates has_issues in vulnerability_reads' do
expect do
issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
end.to change { @vulnerability_read.reload.has_issues }.from(false).to(true)
end
end
describe 'DELETE trigger' do
let(:issue2) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
it 'does not change has_issues when there exists another issue' do
issue_link1 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue2.id)
expect do
issue_link1.delete
end.not_to change { @vulnerability_read.reload.has_issues }
end
it 'unsets has_issues when all issues are deleted' do
issue_link1 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
issue_link2 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue2.id)
expect do
issue_link1.delete
issue_link2.delete
end.to change { @vulnerability_read.reload.has_issues }.from(true).to(false)
end
end
end
describe '#down' do
before do
migration.up
migration.down
end
it 'drops the trigger' do
expect do
issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
end.not_to change { @vulnerability_read.has_issues }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
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