Commit ca5bb6f4 authored by Avielle Wolfe's avatar Avielle Wolfe Committed by Jan Provaznik

Add filter by severities

And use VulnerabilitiesFinder in VulnerabilitiesResolver
parent 09762c21
......@@ -6401,6 +6401,26 @@ type Project {
Returns the last _n_ elements from the list.
"""
last: Int
"""
Filter vulnerabilities by project
"""
projectId: [ID!]
"""
Filter vulnerabilities by report type
"""
reportType: [VulnerabilityReportType!]
"""
Filter vulnerabilities by severity
"""
severity: [VulnerabilitySeverity!]
"""
Filter vulnerabilities by state
"""
state: [VulnerabilityState!]
): VulnerabilityConnection
"""
......
......@@ -19018,6 +19018,78 @@
"name": "vulnerabilities",
"description": "Vulnerabilities reported on the project. Available only when feature flag `first_class_vulnerabilities` is enabled",
"args": [
{
"name": "projectId",
"description": "Filter vulnerabilities by project",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "reportType",
"description": "Filter vulnerabilities by report type",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "VulnerabilityReportType",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "severity",
"description": "Filter vulnerabilities by severity",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "VulnerabilitySeverity",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "state",
"description": "Filter vulnerabilities by state",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "VulnerabilityState",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......
......@@ -34,26 +34,26 @@ module Security
attr_reader :filters, :vulnerabilities
def filter_by_projects
if filters[:project_ids].present?
@vulnerabilities = vulnerabilities.for_projects(filters[:project_ids])
if filters[:project_id].present?
@vulnerabilities = vulnerabilities.for_projects(filters[:project_id])
end
end
def filter_by_report_types
if filters[:report_types].present?
@vulnerabilities = vulnerabilities.with_report_types(filters[:report_types])
if filters[:report_type].present?
@vulnerabilities = vulnerabilities.with_report_types(filters[:report_type])
end
end
def filter_by_severities
if filters[:severities].present?
@vulnerabilities = vulnerabilities.with_severities(filters[:severities])
if filters[:severity].present?
@vulnerabilities = vulnerabilities.with_severities(filters[:severity])
end
end
def filter_by_states
if filters[:states].present?
@vulnerabilities = vulnerabilities.with_states(filters[:states])
if filters[:state].present?
@vulnerabilities = vulnerabilities.with_states(filters[:state])
end
end
end
......
......@@ -6,10 +6,28 @@ module Resolvers
type Types::VulnerabilityType, null: true
argument :project_id, [GraphQL::ID_TYPE],
required: false,
description: 'Filter vulnerabilities by project'
argument :report_type, [Types::VulnerabilityReportTypeEnum],
required: false,
description: 'Filter vulnerabilities by report type'
argument :severity, [Types::VulnerabilitySeverityEnum],
required: false,
description: 'Filter vulnerabilities by severity'
argument :state, [Types::VulnerabilityStateEnum],
required: false,
description: 'Filter vulnerabilities by state'
def resolve(**args)
return Vulnerability.none unless vulnerable
vulnerable.vulnerabilities.with_findings.ordered
filters = args.slice(:project_id, :report_type, :severity, :state)
vulnerabilities(filters).with_findings.ordered
end
private
......@@ -24,5 +42,9 @@ module Resolvers
object.respond_to?(:sync) ? object.sync : object
end
end
def vulnerabilities(filters)
Security::VulnerabilitiesFinder.new(vulnerable, filters).execute
end
end
end
......@@ -35,7 +35,7 @@ describe Security::VulnerabilitiesFinder do
end
context 'when filtered by report type' do
let(:filters) { { report_types: %w[sast dast] } }
let(:filters) { { report_type: %w[sast dast] } }
it 'only returns vulnerabilities matching the given report types' do
is_expected.to contain_exactly(vulnerability1, vulnerability2)
......@@ -43,7 +43,7 @@ describe Security::VulnerabilitiesFinder do
end
context 'when filtered by severity' do
let(:filters) { { severities: %w[medium high] } }
let(:filters) { { severity: %w[medium high] } }
it 'only returns vulnerabilities matching the given severities' do
is_expected.to contain_exactly(vulnerability2, vulnerability3)
......@@ -51,7 +51,7 @@ describe Security::VulnerabilitiesFinder do
end
context 'when filtered by state' do
let(:filters) { { states: %w[detected confirmed] } }
let(:filters) { { state: %w[detected confirmed] } }
it 'only returns vulnerabilities matching the given states' do
is_expected.to contain_exactly(vulnerability1, vulnerability3)
......@@ -62,7 +62,7 @@ describe Security::VulnerabilitiesFinder do
let(:group) { create(:group) }
let(:another_project) { create(:project, namespace: group) }
let!(:another_vulnerability) { create(:vulnerability, project: another_project) }
let(:filters) { { project_ids: [another_project.id] } }
let(:filters) { { project_id: [another_project.id] } }
let(:vulnerable) { group }
before do
......@@ -79,7 +79,7 @@ describe Security::VulnerabilitiesFinder do
create(:vulnerability, severity: :medium, report_type: :sast, state: :detected, project: project)
end
let(:filters) { { report_types: %w[sast], severities: %w[medium] } }
let(:filters) { { report_type: %w[sast], severity: %w[medium] } }
it 'only returns vulnerabilities matching all of the given filters' do
is_expected.to contain_exactly(vulnerability4)
......
......@@ -7,11 +7,23 @@ describe Resolvers::VulnerabilitiesResolver do
describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:low_vulnerability) { create(:vulnerability, :low, project: project) }
let_it_be(:critical_vulnerability) { create(:vulnerability, :critical, project: project) }
let_it_be(:high_vulnerability) { create(:vulnerability, :high, project: project) }
subject { resolve(described_class, obj: project) }
let_it_be(:low_vulnerability) do
create(:vulnerability, :detected, :low, project: project, report_type: :dast)
end
let_it_be(:critical_vulnerability) do
create(:vulnerability, :detected, :critical, project: project, report_type: :sast)
end
let_it_be(:high_vulnerability) do
create(:vulnerability, :dismissed, :high, project: project, report_type: :container_scanning)
end
let(:filters) { {} }
let(:vulnerable) { project }
subject { resolve(described_class, obj: vulnerable, args: filters) }
it "returns the project's vulnerabilities" do
is_expected.to contain_exactly(critical_vulnerability, high_vulnerability, low_vulnerability)
......@@ -22,5 +34,46 @@ describe Resolvers::VulnerabilitiesResolver do
expect(subject.second).to eq(high_vulnerability)
expect(subject.third).to eq(low_vulnerability)
end
context 'when given severities' do
let(:filters) { { severity: ['low'] } }
it 'only returns vulnerabilities of the given severities' do
is_expected.to contain_exactly(low_vulnerability)
end
end
context 'when given states' do
let(:filters) { { state: ['dismissed'] } }
it 'only returns vulnerabilities of the given states' do
is_expected.to contain_exactly(high_vulnerability)
end
end
context 'when given report types' do
let(:filters) { { report_type: %i[dast sast] } }
it 'only returns vulnerabilities of the given report types' do
is_expected.to contain_exactly(critical_vulnerability, low_vulnerability)
end
end
context 'when given project IDs' do
let_it_be(:group) { create(:group) }
let_it_be(:project2) { create(:project, namespace: group) }
let_it_be(:project2_vulnerability) { create(:vulnerability, project: project2) }
let(:filters) { { project_id: [project2.id] } }
let(:vulnerable) { group }
before do
project.update(namespace: group)
end
it 'only returns vulnerabilities belonging to the given projects' do
is_expected.to contain_exactly(project2_vulnerability)
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