Commit 3ad1f9dc authored by Craig Smith's avatar Craig Smith

Add scan field to MR DAST report endpoint

We’d like to display the number of URLs scanned
by DAST for a MR. This commit takes the
scanned_resources_count field from Security::Scan
and shows it on OccurrenceReportsComparerEntity
parent 015889ef
...@@ -8,7 +8,8 @@ module Ci ...@@ -8,7 +8,8 @@ module Ci
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224 # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
class CompareReportsBaseService < ::BaseService class CompareReportsBaseService < ::BaseService
def execute(base_pipeline, head_pipeline) def execute(base_pipeline, head_pipeline)
comparer = comparer_class.new(get_report(base_pipeline), get_report(head_pipeline)) comparer = build_comparer(base_pipeline, head_pipeline)
{ {
status: :parsed, status: :parsed,
key: key(base_pipeline, head_pipeline), key: key(base_pipeline, head_pipeline),
...@@ -28,6 +29,12 @@ module Ci ...@@ -28,6 +29,12 @@ module Ci
data&.fetch(:key, nil) == key(base_pipeline, head_pipeline) data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
end end
protected
def build_comparer(base_pipeline, head_pipeline)
comparer_class.new(get_report(base_pipeline), get_report(head_pipeline))
end
private private
def key(base_pipeline, head_pipeline) def key(base_pipeline, head_pipeline)
......
# frozen_string_literal: true # frozen_string_literal: true
class Vulnerabilities::OccurrenceReportsComparerEntity < Grape::Entity class Vulnerabilities::OccurrenceReportsComparerEntity < Grape::Entity
include RequestAwareEntity
expose :base_report_created_at expose :base_report_created_at
expose :base_report_out_of_date expose :base_report_out_of_date
expose :head_report_created_at expose :head_report_created_at
expose :added, using: Vulnerabilities::OccurrenceEntity expose :added, using: Vulnerabilities::OccurrenceEntity
expose :fixed, using: Vulnerabilities::OccurrenceEntity expose :fixed, using: Vulnerabilities::OccurrenceEntity
expose :existing, using: Vulnerabilities::OccurrenceEntity expose :existing, using: Vulnerabilities::OccurrenceEntity
expose :scans, using: Vulnerabilities::ScanEntity
end end
# frozen_string_literal: true
class Vulnerabilities::ScanEntity < Grape::Entity
include RequestAwareEntity
expose :scanned_resources_count do |scan|
scan.scanned_resources_count || 0
end
expose :job_path do |scan|
project_job_path(scan.build.project, scan.build)
end
end
...@@ -13,5 +13,9 @@ module Ci ...@@ -13,5 +13,9 @@ module Ci
def get_report(pipeline) def get_report(pipeline)
Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[container_scanning], scope: 'all' }).execute Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[container_scanning], scope: 'all' }).execute
end end
def build_comparer(base_pipeline, head_pipeline)
comparer_class.new(get_report(base_pipeline), get_report(head_pipeline), head_security_scans: head_pipeline.security_scans)
end
end end
end end
...@@ -13,5 +13,9 @@ module Ci ...@@ -13,5 +13,9 @@ module Ci
def get_report(pipeline) def get_report(pipeline)
Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[dast] }).execute Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[dast] }).execute
end end
def build_comparer(base_pipeline, head_pipeline)
comparer_class.new(get_report(base_pipeline), get_report(head_pipeline), head_security_scans: head_pipeline.security_scans)
end
end end
end end
...@@ -13,5 +13,9 @@ module Ci ...@@ -13,5 +13,9 @@ module Ci
def get_report(pipeline) def get_report(pipeline)
Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[dependency_scanning], scope: 'all' }).execute Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[dependency_scanning], scope: 'all' }).execute
end end
def build_comparer(base_pipeline, head_pipeline)
comparer_class.new(get_report(base_pipeline), get_report(head_pipeline), head_security_scans: head_pipeline.security_scans)
end
end end
end end
...@@ -13,5 +13,9 @@ module Ci ...@@ -13,5 +13,9 @@ module Ci
def get_report(pipeline) def get_report(pipeline)
Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[sast], scope: 'all' }).execute Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[sast], scope: 'all' }).execute
end end
def build_comparer(base_pipeline, head_pipeline)
comparer_class.new(get_report(base_pipeline), get_report(head_pipeline), head_security_scans: head_pipeline.security_scans)
end
end end
end end
---
title: Show the number of scanned resources in the DAST vulnerability report
merge_request: 28718
author:
type: added
...@@ -7,13 +7,14 @@ module Gitlab ...@@ -7,13 +7,14 @@ module Gitlab
class VulnerabilityReportsComparer class VulnerabilityReportsComparer
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
attr_reader :base_report, :head_report attr_reader :base_report, :head_report, :security_scans
ACCEPTABLE_REPORT_AGE = 1.week ACCEPTABLE_REPORT_AGE = 1.week
def initialize(base_report, head_report) def initialize(base_report, head_report, **extra_metadata)
@base_report = base_report @base_report = base_report
@head_report = head_report @head_report = head_report
@security_scans = extra_metadata[:head_security_scans]
end end
def base_report_created_at def base_report_created_at
...@@ -48,6 +49,10 @@ module Gitlab ...@@ -48,6 +49,10 @@ module Gitlab
head_report.occurrences & base_report.occurrences head_report.occurrences & base_report.occurrences
end end
end end
def scans
@security_scans
end
end end
end end
end end
......
...@@ -16,7 +16,7 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do ...@@ -16,7 +16,7 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
allow(head_vulnerability).to receive(:location).and_return({}) allow(head_vulnerability).to receive(:location).and_return({})
end end
subject { described_class.new(base_report, head_report) } subject { described_class.new(base_report, head_report, head_security_scans: []) }
describe '#base_report_out_of_date' do describe '#base_report_out_of_date' do
context 'no base report' do context 'no base report' do
...@@ -140,7 +140,7 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do ...@@ -140,7 +140,7 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
let(:empty_report) { build(:ci_reports_security_aggregated_reports, reports: [], occurrences: [])} let(:empty_report) { build(:ci_reports_security_aggregated_reports, reports: [], occurrences: [])}
it 'returns empty array when reports are not present' do it 'returns empty array when reports are not present' do
comparer = described_class.new(empty_report, empty_report) comparer = described_class.new(empty_report, empty_report, head_security_scans: [])
expect(comparer.existing).to eq([]) expect(comparer.existing).to eq([])
expect(comparer.fixed).to eq([]) expect(comparer.fixed).to eq([])
...@@ -148,7 +148,7 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do ...@@ -148,7 +148,7 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
end end
it 'returns added vulnerability when base is empty and head is not empty' do it 'returns added vulnerability when base is empty and head is not empty' do
comparer = described_class.new(empty_report, head_report) comparer = described_class.new(empty_report, head_report, head_security_scans: [])
expect(comparer.existing).to eq([]) expect(comparer.existing).to eq([])
expect(comparer.fixed).to eq([]) expect(comparer.fixed).to eq([])
...@@ -156,11 +156,21 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do ...@@ -156,11 +156,21 @@ describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
end end
it 'returns fixed vulnerability when head is empty and base is not empty' do it 'returns fixed vulnerability when head is empty and base is not empty' do
comparer = described_class.new(base_report, empty_report) comparer = described_class.new(base_report, empty_report, head_security_scans: [])
expect(comparer.existing).to eq([]) expect(comparer.existing).to eq([])
expect(comparer.fixed).to eq([base_vulnerability]) expect(comparer.fixed).to eq([base_vulnerability])
expect(comparer.added).to eq([]) expect(comparer.added).to eq([])
end end
end end
describe 'security_scans' do
let(:scan) { build(:security_scan, scanned_resources_count: 10) }
let(:security_scans) { [scan] }
it 'sets the security scans' do
comparer = described_class.new(base_report, head_report, head_security_scans: security_scans)
expect(comparer.security_scans).to be(security_scans)
end
end
end end
...@@ -14,7 +14,10 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do ...@@ -14,7 +14,10 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do
let(:head_combined_reports) { build_list(:ci_reports_security_report, 1, created_at: 2.days.ago) } let(:head_combined_reports) { build_list(:ci_reports_security_report, 1, created_at: 2.days.ago) }
let(:head_report) { build(:ci_reports_security_aggregated_reports, reports: head_combined_reports, occurrences: head_occurrences)} let(:head_report) { build(:ci_reports_security_aggregated_reports, reports: head_combined_reports, occurrences: head_occurrences)}
let(:comparer) { Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(base_report, head_report) } let(:scan) { create(:security_scan, scanned_resources_count: 10) }
let(:security_scans) { [scan] }
let(:comparer) { Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(base_report, head_report, head_security_scans: security_scans) }
let(:request) { double('request') } let(:request) { double('request') }
...@@ -31,6 +34,17 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do ...@@ -31,6 +34,17 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do
allow(request).to receive(:current_user).and_return(user) allow(request).to receive(:current_user).and_return(user)
end end
it 'avoids N+1 database queries' do
comparer = Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(base_report, head_report, head_security_scans: [])
entity = described_class.new(comparer, request: request)
control_count = ActiveRecord::QueryRecorder.new { entity.as_json }.count
scans = create_list(:security_scan, 5)
comparer = Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(base_report, head_report, head_security_scans: scans)
entity = described_class.new(comparer, request: request)
expect { entity.as_json }.not_to exceed_query_limit(control_count)
end
it 'contains the added existing and fixed vulnerabilities for container scanning' do it 'contains the added existing and fixed vulnerabilities for container scanning' do
expect(subject.keys).to include(:added) expect(subject.keys).to include(:added)
expect(subject.keys).to include(:existing) expect(subject.keys).to include(:existing)
...@@ -42,6 +56,23 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do ...@@ -42,6 +56,23 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do
expect(subject.keys).to include(:base_report_out_of_date) expect(subject.keys).to include(:base_report_out_of_date)
expect(subject.keys).to include(:head_report_created_at) expect(subject.keys).to include(:head_report_created_at)
end end
it 'contains the scan fields' do
expect(subject.keys).to include(:scans)
expect(subject[:scans].length).to be(1)
expect(subject[:scans].first[:scanned_resources_count]).to be(10)
project = scan.build.project
expect(subject[:scans].first[:job_path]).to eq("/#{project.namespace.path}/#{project.path}/-/jobs/#{scan.build.id}")
end
context 'scanned_resources_count is nil' do
let(:scan) { create(:security_scan, scanned_resources_count: nil) }
let(:security_scans) { [scan] }
it 'shows the scanned_resources_count is 0' do
expect(subject[:scans].first[:scanned_resources_count]).to be(0)
end
end
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Vulnerabilities::ScanEntity do
let(:scan) { build(:security_scan, scanned_resources_count: 10) }
let(:request) { double('request') }
let(:entity) do
described_class.represent(scan, request: request)
end
describe '#as_json' do
subject { entity.as_json }
it 'contains required fields' do
expect(subject).to include(:scanned_resources_count)
expect(subject).to include(:job_path)
end
describe 'job_path' do
it 'returns path to the job log' do
project = scan.build.project
expect(subject[:job_path]).to eq("/#{project.namespace.path}/#{project.path}/-/jobs/#{scan.build.id}")
end
end
describe 'scanned_resources_count' do
context 'is nil' do
let(:scan) { build(:security_scan, scanned_resources_count: nil) }
it 'shows a count of 0' do
expect(subject[:scanned_resources_count]).to be(0)
end
end
context 'has a value' do
it 'shows the count' do
expect(subject[:scanned_resources_count]).to be(10)
end
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