Commit 8e2844e4 authored by Victor Zagorodny's avatar Victor Zagorodny

Add VulnerabilitiesFinder for new Vulnerability

Rename old VulnerabilitiesFinder to
VulnerabilityFindingsFinder because it finds
Occurrences (Findings in new terminology) and
update its usages. Add new VulnerabilitiesFinder
that finds Vulnerabilities of a Project and
respects the pagination of results.
parent a8482a92
......@@ -40,6 +40,6 @@ module VulnerabilityFindingsActions
end
def vulnerability_findings(collection = :latest)
::Security::VulnerabilitiesFinder.new(vulnerable, params: filter_params).execute(collection)
::Security::VulnerabilityFindingsFinder.new(vulnerable, params: filter_params).execute(collection)
end
end
......@@ -2,75 +2,27 @@
# Security::VulnerabilitiesFinder
#
# Used to filter Vulnerabilities::Occurrences by set of params for Security Dashboard
# Used to filter Vulnerability record by set of params for Vulnerabilities API
#
# Arguments:
# vulnerable - object to filter vulnerabilities
# project: a Project to query for Vulnerabilities
# params:
# severity: Array<String>
# confidence: Array<String>
# project: Array<String>
# report_type: Array<String>
# page: Integer
# per_page: Integer
module Security
class VulnerabilitiesFinder
attr_accessor :params
attr_reader :vulnerable
attr_reader :project
attr_reader :page, :per_page
def initialize(vulnerable, params: {})
@vulnerable = vulnerable
@params = params
def initialize(project, params = {})
@project = project
@page = params[:page] || 1
@per_page = params[:per_page] || 20
end
def execute(scope = :latest)
collection = init_collection(scope)
collection = by_report_type(collection)
collection = by_project(collection)
collection = by_severity(collection)
collection = by_confidence(collection)
collection
end
private
def by_report_type(items)
return items unless params[:report_type].present?
items.by_report_types(
Vulnerabilities::Occurrence::REPORT_TYPES.values_at(
*params[:report_type]).compact)
end
def by_project(items)
return items unless params[:project_id].present?
items.by_projects(params[:project_id])
end
def by_severity(items)
return items unless params[:severity].present?
items.by_severities(
Vulnerabilities::Occurrence::SEVERITY_LEVELS.values_at(
*params[:severity]).compact)
end
def by_confidence(items)
return items unless params[:confidence].present?
items.by_confidences(
Vulnerabilities::Occurrence::CONFIDENCE_LEVELS.values_at(
*params[:confidence]).compact)
end
def init_collection(scope)
if scope == :all
vulnerable.all_vulnerabilities
elsif scope == :with_sha
vulnerable.latest_vulnerabilities_with_sha
else
vulnerable.latest_vulnerabilities
end
def execute
project.vulnerabilities.page(page).per(per_page)
end
end
end
# frozen_string_literal: true
# Security::VulnerabilityFindingsFinder
#
# Used to filter Vulnerabilities::Occurrences by set of params for Security Dashboard
#
# Arguments:
# vulnerable - object to filter vulnerabilities
# params:
# severity: Array<String>
# confidence: Array<String>
# project: Array<String>
# report_type: Array<String>
module Security
# TODO: check usages and rename `vulnerable` associations to mention the term "finding";
# probably, add new changes to https://gitlab.com/gitlab-org/gitlab/merge_requests/17351
class VulnerabilityFindingsFinder
attr_accessor :params
attr_reader :vulnerable
def initialize(vulnerable, params: {})
@vulnerable = vulnerable
@params = params
end
def execute(scope = :latest)
collection = init_collection(scope)
collection = by_report_type(collection)
collection = by_project(collection)
collection = by_severity(collection)
collection = by_confidence(collection)
collection
end
private
def by_report_type(items)
return items unless params[:report_type].present?
items.by_report_types(
Vulnerabilities::Occurrence::REPORT_TYPES.values_at(
*params[:report_type]).compact)
end
def by_project(items)
return items unless params[:project_id].present?
items.by_projects(params[:project_id])
end
def by_severity(items)
return items unless params[:severity].present?
items.by_severities(
Vulnerabilities::Occurrence::SEVERITY_LEVELS.values_at(
*params[:severity]).compact)
end
def by_confidence(items)
return items unless params[:confidence].present?
items.by_confidences(
Vulnerabilities::Occurrence::CONFIDENCE_LEVELS.values_at(
*params[:confidence]).compact)
end
def init_collection(scope)
if scope == :all
vulnerable.all_vulnerabilities
elsif scope == :with_sha
vulnerable.latest_vulnerabilities_with_sha
else
vulnerable.latest_vulnerabilities
end
end
end
end
......@@ -24,7 +24,7 @@ module Gitlab
private
def found_vulnerabilities
::Security::VulnerabilitiesFinder.new(group, params: filters).execute(:all)
::Security::VulnerabilityFindingsFinder.new(group, params: filters).execute(:all)
end
def cached_vulnerability_history
......
......@@ -12,7 +12,7 @@ module Gitlab
def fetch(range, force: false)
Rails.cache.fetch(cache_key, force: force, expires_in: 1.day) do
vulnerabilities = ::Security::VulnerabilitiesFinder
vulnerabilities = ::Security::VulnerabilityFindingsFinder
.new(group, params: { project_id: [project_id] })
.execute(:all)
.count_by_day_and_severity(range)
......
......@@ -89,5 +89,11 @@ FactoryBot.modify do
trait :github_imported do
import_type { 'github' }
end
trait :with_vulnerabilities do
after(:create) do |project|
create_list(:vulnerability, 3, :opened, project: project)
end
end
end
end
......@@ -3,132 +3,12 @@
require 'spec_helper'
describe Security::VulnerabilitiesFinder do
describe '#execute' do
set(:group) { create(:group) }
set(:project1) { create(:project, :private, :repository, group: group) }
set(:project2) { create(:project, :private, :repository, group: group) }
set(:pipeline1) { create(:ci_pipeline, :success, project: project1) }
set(:pipeline2) { create(:ci_pipeline, :success, project: project2) }
let(:project) { create(:project, :with_vulnerabilities) }
let(:params) { { page: 2, per_page: 1 } }
set(:vulnerability1) { create(:vulnerabilities_occurrence, report_type: :sast, severity: :high, confidence: :high, pipelines: [pipeline1], project: project1) }
set(:vulnerability2) { create(:vulnerabilities_occurrence, report_type: :dependency_scanning, severity: :medium, confidence: :low, pipelines: [pipeline2], project: project2) }
set(:vulnerability3) { create(:vulnerabilities_occurrence, report_type: :sast, severity: :low, pipelines: [pipeline2], project: project2) }
set(:vulnerability4) { create(:vulnerabilities_occurrence, report_type: :dast, severity: :medium, pipelines: [pipeline1], project: project1) }
subject { described_class.new(project, params).execute }
subject { described_class.new(group, params: params).execute }
context 'by report type' do
context 'when sast' do
let(:params) { { report_type: %w[sast] } }
it 'includes only sast' do
is_expected.to contain_exactly(vulnerability1, vulnerability3)
end
end
context 'when dependency_scanning' do
let(:params) { { report_type: %w[dependency_scanning] } }
it 'includes only depscan' do
is_expected.to contain_exactly(vulnerability2)
end
end
end
context 'by severity' do
context 'when high' do
let(:params) { { severity: %w[high] } }
it 'includes only high' do
is_expected.to contain_exactly(vulnerability1)
end
end
context 'when medium' do
let(:params) { { severity: %w[medium] } }
it 'includes only medium' do
is_expected.to contain_exactly(vulnerability2, vulnerability4)
end
end
end
context 'by confidence' do
context 'when high' do
let(:params) { { confidence: %w[high] } }
it 'includes only high confidence vulnerabilities' do
is_expected.to contain_exactly(vulnerability1)
end
end
context 'when low' do
let(:params) { { confidence: %w[low] } }
it 'includes only low confidence vulnerabilities' do
is_expected.to contain_exactly(vulnerability2)
end
end
end
context 'by project' do
let(:params) { { project_id: [project2.id] } }
it 'includes only vulnerabilities for one project' do
is_expected.to contain_exactly(vulnerability2, vulnerability3)
end
end
# FIXME: unskip when this filter is implemented
context 'by dismissals' do
let!(:dismissal) do
create(:vulnerability_feedback, :sast, :dismissal,
pipeline: pipeline1,
project: project1,
project_fingerprint: vulnerability1.project_fingerprint)
end
let(:params) { { hide_dismissed: true } }
skip 'exclude dismissal' do
is_expected.to contain_exactly(vulnerability2, vulnerability3, vulnerability4)
end
end
context 'by all filters' do
context 'with found entity' do
let(:params) { { severity: %w[high medium low], project_id: [project1.id, project2.id], report_type: %w[sast dast] } }
it 'filters by all params' do
is_expected.to contain_exactly(vulnerability1, vulnerability3, vulnerability4)
end
end
context 'without found entity' do
let(:params) { { severity: %w[low], project_id: [project1.id], report_type: %w[sast] } }
it 'did not find anything' do
is_expected.to be_empty
end
end
end
context 'by some filters' do
context 'with found entity' do
let(:params) { { project_id: [project2.id], severity: %w[medium low] } }
it 'filters by all params' do
is_expected.to contain_exactly(vulnerability2, vulnerability3)
end
end
context 'without found entity' do
let(:params) { { project_id: project1.id, severity: %w[low] } }
it 'did not find anything' do
is_expected.to be_empty
end
end
end
it 'returns vulnerabilities of a project and respects pagination params' do
expect(subject).to contain_exactly(project.vulnerabilities.drop(1).take(1).first)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Security::VulnerabilityFindingsFinder do
describe '#execute' do
set(:group) { create(:group) }
set(:project1) { create(:project, :private, :repository, group: group) }
set(:project2) { create(:project, :private, :repository, group: group) }
set(:pipeline1) { create(:ci_pipeline, :success, project: project1) }
set(:pipeline2) { create(:ci_pipeline, :success, project: project2) }
set(:vulnerability1) { create(:vulnerabilities_occurrence, report_type: :sast, severity: :high, confidence: :high, pipelines: [pipeline1], project: project1) }
set(:vulnerability2) { create(:vulnerabilities_occurrence, report_type: :dependency_scanning, severity: :medium, confidence: :low, pipelines: [pipeline2], project: project2) }
set(:vulnerability3) { create(:vulnerabilities_occurrence, report_type: :sast, severity: :low, pipelines: [pipeline2], project: project2) }
set(:vulnerability4) { create(:vulnerabilities_occurrence, report_type: :dast, severity: :medium, pipelines: [pipeline1], project: project1) }
subject { described_class.new(group, params: params).execute }
context 'by report type' do
context 'when sast' do
let(:params) { { report_type: %w[sast] } }
it 'includes only sast' do
is_expected.to contain_exactly(vulnerability1, vulnerability3)
end
end
context 'when dependency_scanning' do
let(:params) { { report_type: %w[dependency_scanning] } }
it 'includes only depscan' do
is_expected.to contain_exactly(vulnerability2)
end
end
end
context 'by severity' do
context 'when high' do
let(:params) { { severity: %w[high] } }
it 'includes only high' do
is_expected.to contain_exactly(vulnerability1)
end
end
context 'when medium' do
let(:params) { { severity: %w[medium] } }
it 'includes only medium' do
is_expected.to contain_exactly(vulnerability2, vulnerability4)
end
end
end
context 'by confidence' do
context 'when high' do
let(:params) { { confidence: %w[high] } }
it 'includes only high confidence vulnerabilities' do
is_expected.to contain_exactly(vulnerability1)
end
end
context 'when low' do
let(:params) { { confidence: %w[low] } }
it 'includes only low confidence vulnerabilities' do
is_expected.to contain_exactly(vulnerability2)
end
end
end
context 'by project' do
let(:params) { { project_id: [project2.id] } }
it 'includes only vulnerabilities for one project' do
is_expected.to contain_exactly(vulnerability2, vulnerability3)
end
end
# FIXME: unskip when this filter is implemented
context 'by dismissals' do
let!(:dismissal) do
create(:vulnerability_feedback, :sast, :dismissal,
pipeline: pipeline1,
project: project1,
project_fingerprint: vulnerability1.project_fingerprint)
end
let(:params) { { hide_dismissed: true } }
skip 'exclude dismissal' do
is_expected.to contain_exactly(vulnerability2, vulnerability3, vulnerability4)
end
end
context 'by all filters' do
context 'with found entity' do
let(:params) { { severity: %w[high medium low], project_id: [project1.id, project2.id], report_type: %w[sast dast] } }
it 'filters by all params' do
is_expected.to contain_exactly(vulnerability1, vulnerability3, vulnerability4)
end
end
context 'without found entity' do
let(:params) { { severity: %w[low], project_id: [project1.id], report_type: %w[sast] } }
it 'did not find anything' do
is_expected.to be_empty
end
end
end
context 'by some filters' do
context 'with found entity' do
let(:params) { { project_id: [project2.id], severity: %w[medium low] } }
it 'filters by all params' do
is_expected.to contain_exactly(vulnerability2, vulnerability3)
end
end
context 'without found entity' do
let(:params) { { project_id: project1.id, severity: %w[low] } }
it 'did not find anything' do
is_expected.to be_empty
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