Commit 050a96c0 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'add-filters-to-vulnerability-resolver' into 'master'

Add filters to VulnerabilityResolver

See merge request gitlab-org/gitlab!26956
parents d9b8ac54 ca5bb6f4
...@@ -6401,6 +6401,26 @@ type Project { ...@@ -6401,6 +6401,26 @@ type Project {
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
last: Int 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 ): VulnerabilityConnection
""" """
......
...@@ -19018,6 +19018,78 @@ ...@@ -19018,6 +19018,78 @@
"name": "vulnerabilities", "name": "vulnerabilities",
"description": "Vulnerabilities reported on the project. Available only when feature flag `first_class_vulnerabilities` is enabled", "description": "Vulnerabilities reported on the project. Available only when feature flag `first_class_vulnerabilities` is enabled",
"args": [ "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", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
......
...@@ -34,26 +34,26 @@ module Security ...@@ -34,26 +34,26 @@ module Security
attr_reader :filters, :vulnerabilities attr_reader :filters, :vulnerabilities
def filter_by_projects def filter_by_projects
if filters[:project_ids].present? if filters[:project_id].present?
@vulnerabilities = vulnerabilities.for_projects(filters[:project_ids]) @vulnerabilities = vulnerabilities.for_projects(filters[:project_id])
end end
end end
def filter_by_report_types def filter_by_report_types
if filters[:report_types].present? if filters[:report_type].present?
@vulnerabilities = vulnerabilities.with_report_types(filters[:report_types]) @vulnerabilities = vulnerabilities.with_report_types(filters[:report_type])
end end
end end
def filter_by_severities def filter_by_severities
if filters[:severities].present? if filters[:severity].present?
@vulnerabilities = vulnerabilities.with_severities(filters[:severities]) @vulnerabilities = vulnerabilities.with_severities(filters[:severity])
end end
end end
def filter_by_states def filter_by_states
if filters[:states].present? if filters[:state].present?
@vulnerabilities = vulnerabilities.with_states(filters[:states]) @vulnerabilities = vulnerabilities.with_states(filters[:state])
end end
end end
end end
......
...@@ -6,10 +6,28 @@ module Resolvers ...@@ -6,10 +6,28 @@ module Resolvers
type Types::VulnerabilityType, null: true 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) def resolve(**args)
return Vulnerability.none unless vulnerable 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 end
private private
...@@ -24,5 +42,9 @@ module Resolvers ...@@ -24,5 +42,9 @@ module Resolvers
object.respond_to?(:sync) ? object.sync : object object.respond_to?(:sync) ? object.sync : object
end end
end end
def vulnerabilities(filters)
Security::VulnerabilitiesFinder.new(vulnerable, filters).execute
end
end end
end end
...@@ -35,7 +35,7 @@ describe Security::VulnerabilitiesFinder do ...@@ -35,7 +35,7 @@ describe Security::VulnerabilitiesFinder do
end end
context 'when filtered by report type' do 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 it 'only returns vulnerabilities matching the given report types' do
is_expected.to contain_exactly(vulnerability1, vulnerability2) is_expected.to contain_exactly(vulnerability1, vulnerability2)
...@@ -43,7 +43,7 @@ describe Security::VulnerabilitiesFinder do ...@@ -43,7 +43,7 @@ describe Security::VulnerabilitiesFinder do
end end
context 'when filtered by severity' do 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 it 'only returns vulnerabilities matching the given severities' do
is_expected.to contain_exactly(vulnerability2, vulnerability3) is_expected.to contain_exactly(vulnerability2, vulnerability3)
...@@ -51,7 +51,7 @@ describe Security::VulnerabilitiesFinder do ...@@ -51,7 +51,7 @@ describe Security::VulnerabilitiesFinder do
end end
context 'when filtered by state' do 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 it 'only returns vulnerabilities matching the given states' do
is_expected.to contain_exactly(vulnerability1, vulnerability3) is_expected.to contain_exactly(vulnerability1, vulnerability3)
...@@ -62,7 +62,7 @@ describe Security::VulnerabilitiesFinder do ...@@ -62,7 +62,7 @@ describe Security::VulnerabilitiesFinder do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:another_project) { create(:project, namespace: group) } let(:another_project) { create(:project, namespace: group) }
let!(:another_vulnerability) { create(:vulnerability, project: another_project) } 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 } let(:vulnerable) { group }
before do before do
...@@ -79,7 +79,7 @@ describe Security::VulnerabilitiesFinder do ...@@ -79,7 +79,7 @@ describe Security::VulnerabilitiesFinder do
create(:vulnerability, severity: :medium, report_type: :sast, state: :detected, project: project) create(:vulnerability, severity: :medium, report_type: :sast, state: :detected, project: project)
end 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 it 'only returns vulnerabilities matching all of the given filters' do
is_expected.to contain_exactly(vulnerability4) is_expected.to contain_exactly(vulnerability4)
......
...@@ -7,11 +7,23 @@ describe Resolvers::VulnerabilitiesResolver do ...@@ -7,11 +7,23 @@ describe Resolvers::VulnerabilitiesResolver do
describe '#resolve' do describe '#resolve' do
let_it_be(:project) { create(:project) } 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 it "returns the project's vulnerabilities" do
is_expected.to contain_exactly(critical_vulnerability, high_vulnerability, low_vulnerability) is_expected.to contain_exactly(critical_vulnerability, high_vulnerability, low_vulnerability)
...@@ -22,5 +34,46 @@ describe Resolvers::VulnerabilitiesResolver do ...@@ -22,5 +34,46 @@ describe Resolvers::VulnerabilitiesResolver do
expect(subject.second).to eq(high_vulnerability) expect(subject.second).to eq(high_vulnerability)
expect(subject.third).to eq(low_vulnerability) expect(subject.third).to eq(low_vulnerability)
end 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
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