Commit 8d17d7c3 authored by James Lopez's avatar James Lopez

Merge branch 'create-instance-security-dashboard-type' into 'master'

Add vulnerabilities field to QueryType

See merge request gitlab-org/gitlab!26348
parents bd7e94f1 ce9d7f25
......@@ -7174,6 +7174,51 @@ type Query {
"""
visibility: VisibilityScopesEnum
): SnippetConnection
"""
Vulnerabilities reported on projects on the current user's instance security dashboard
"""
vulnerabilities(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
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
}
"""
......
......@@ -21480,6 +21480,131 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerabilities",
"description": "Vulnerabilities reported on projects on the current user's instance security dashboard",
"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.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
......
......@@ -9,6 +9,12 @@ module EE
DesignManagementObject = Struct.new(:issue)
prepended do
field :vulnerabilities,
::Types::VulnerabilityType.connection_type,
null: true,
description: "Vulnerabilities reported on projects on the current user's instance security dashboard",
resolver: Resolvers::VulnerabilitiesResolver
field :design_management, ::Types::DesignManagementType,
null: false,
description: 'Fields related to design management'
......
......@@ -25,9 +25,7 @@ module Resolvers
def resolve(**args)
return Vulnerability.none unless vulnerable
filters = args.slice(:project_id, :report_type, :severity, :state)
vulnerabilities(filters).with_findings.ordered
vulnerabilities(args).with_findings.ordered
end
private
......@@ -38,13 +36,26 @@ module Resolvers
# At this point we need the `id` of the project or group to query for vulnerabilities, so
# make sure it's loaded and not `nil` before continuing.
strong_memoize(:vuln) do
object.respond_to?(:sync) ? object.sync : object
strong_memoize(:vulnerable) do
if resolve_vulnerabilities_for_instance_security_dashboard?
InstanceSecurityDashboard.new(current_user)
elsif object.respond_to?(:sync)
object.sync
else
object
end
end
end
def vulnerabilities(filters)
Security::VulnerabilitiesFinder.new(vulnerable, filters).execute
end
def resolve_vulnerabilities_for_instance_security_dashboard?
# object will be nil when we're fetching vulnerabilities from QueryType,
# which is the source of vulnerability data for the instance security
# dashboard
object.nil? && current_user.present?
end
end
end
......@@ -24,6 +24,12 @@ class InstanceSecurityDashboard
Project.where(id: visible_users_security_dashboard_projects)
end
def vulnerabilities
return Vulnerability.none if projects.empty?
Vulnerability.for_projects(projects)
end
private
attr_reader :project_ids, :user
......
---
title: Add vulnerabilities field to QueryType
merge_request: 26348
author:
type: added
......@@ -6,29 +6,27 @@ describe Resolvers::VulnerabilitiesResolver do
include GraphqlHelpers
describe '#resolve' do
subject { resolve(described_class, obj: vulnerable, args: filters, ctx: { current_user: current_user }) }
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let_it_be(:low_vulnerability) do
create(:vulnerability, :detected, :low, project: project, report_type: :dast)
create(:vulnerability, :detected, :low, :dast, project: project)
end
let_it_be(:critical_vulnerability) do
create(:vulnerability, :detected, :critical, project: project, report_type: :sast)
create(:vulnerability, :detected, :critical, :sast, project: project)
end
let_it_be(:high_vulnerability) do
create(:vulnerability, :dismissed, :high, project: project, report_type: :container_scanning)
create(:vulnerability, :dismissed, :high, :container_scanning, project: project)
end
let(:current_user) { user }
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)
end
it 'orders results by severity' do
expect(subject.first).to eq(critical_vulnerability)
expect(subject.second).to eq(high_vulnerability)
......@@ -75,5 +73,33 @@ describe Resolvers::VulnerabilitiesResolver do
is_expected.to contain_exactly(project2_vulnerability)
end
end
context 'when resolving vulnerabilities for a project' do
it "returns the project's vulnerabilities" do
is_expected.to contain_exactly(critical_vulnerability, high_vulnerability, low_vulnerability)
end
end
context 'when resolving vulnerabilities for an instance security dashboard' do
before do
project.add_developer(user)
end
let(:vulnerable) { nil }
context 'when there is a current user' do
it "returns vulnerabilities for all projects on the current user's instance security dashboard" do
is_expected.to contain_exactly(critical_vulnerability, high_vulnerability, low_vulnerability)
end
end
context 'and there is no current user' do
let(:current_user) { nil }
it 'returns no vulnerabilities' do
is_expected.to be_empty
end
end
end
end
end
......@@ -3,7 +3,11 @@
require 'spec_helper'
describe GitlabSchema.types['Query'] do
it do
expect(described_class).to have_graphql_fields(:design_management, :geo_node).at_least
specify do
expect(described_class).to have_graphql_fields(
:design_management,
:geo_node,
:vulnerabilities
).at_least
end
end
......@@ -3,10 +3,10 @@
require 'spec_helper'
describe InstanceSecurityDashboard do
let(:pipeline1) { create(:ci_pipeline, project: project1) }
let(:pipeline2) { create(:ci_pipeline, project: project2) }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
let_it_be(:project1) { create(:project) }
let_it_be(:project2) { create(:project) }
let_it_be(:pipeline1) { create(:ci_pipeline, project: project1) }
let_it_be(:pipeline2) { create(:ci_pipeline, project: project2) }
let(:project_ids) { [project1.id] }
let(:user) { create(:user) }
......@@ -96,4 +96,23 @@ describe InstanceSecurityDashboard do
end
end
end
describe '#vulnerabilities' do
let_it_be(:vulnerability1) { create(:vulnerability, project: project1) }
let_it_be(:vulnerability2) { create(:vulnerability, project: project2) }
context 'when the user cannot read all resources' do
it 'returns only vulnerabilities from projects on their dashboard that they can read' do
expect(subject.vulnerabilities).to contain_exactly(vulnerability1)
end
end
context 'when the user can read all resources' do
let(:user) { create(:auditor) }
it "returns vulnerabilities from all projects on the user's dashboard" do
expect(subject.vulnerabilities).to contain_exactly(vulnerability1, vulnerability2)
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