Skip to content
Snippets Groups Projects
Commit d29ef222 authored by Jonathan Schafer's avatar Jonathan Schafer Committed by Thong Kuah
Browse files

Add top level securityReportFinding query

Refactors code that would have been duplicated

Changelog: added
EE: true
parent 20d58ca4
No related branches found
No related tags found
2 merge requests!103838Draft: Run test in MR with ce570f0 merging into fef465,!100871Add individual securityReportFinding GraphQL Query
......@@ -16301,6 +16301,18 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="pipelinejobssecurityreporttypes"></a>`securityReportTypes` | [`[SecurityReportTypeEnum!]`](#securityreporttypeenum) | Filter jobs by the type of security report they produce. |
| <a id="pipelinejobsstatuses"></a>`statuses` | [`[CiJobStatus!]`](#cijobstatus) | Filter jobs by status. |
 
##### `Pipeline.securityReportFinding`
Vulnerability finding reported on the pipeline.
Returns [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="pipelinesecurityreportfindinguuid"></a>`uuid` | [`String!`](#string) | UUID of the security report finding. |
##### `Pipeline.securityReportFindings`
 
Vulnerability findings reported on the pipeline.
......@@ -97,6 +97,7 @@ def all_security_findings
.latest
.page(page)
.per(per_page)
.then(&method(:by_uuid))
.then(&method(:by_confidence_levels))
.then(&method(:by_report_types))
.then(&method(:by_severity_levels))
......@@ -131,5 +132,11 @@ def by_severity_levels(relation)
relation.by_severity_levels(params[:severity])
end
def by_uuid(relation)
return relation unless params[:uuid]
relation.by_uuid(params[:uuid])
end
end
end
......@@ -20,6 +20,12 @@ module PipelineType
description: 'Vulnerability findings reported on the pipeline.',
resolver: ::Resolvers::PipelineSecurityReportFindingsResolver
field :security_report_finding,
::Types::PipelineSecurityReportFindingType,
null: true,
description: 'Vulnerability finding reported on the pipeline.',
resolver: ::Resolvers::SecurityReport::FindingResolver
field :code_quality_reports,
::Types::Ci::CodeQualityDegradationType.connection_type,
null: true,
......
# frozen_string_literal: true
module Resolvers
module SecurityReport
class FindingResolver < BaseResolver
type ::Types::PipelineSecurityReportFindingType, null: true
alias_method :pipeline, :object
argument :uuid, GraphQL::Types::String,
required: true,
description: 'UUID of the security report finding.'
def resolve(**args)
Security::FindingsFinder.new(pipeline, params: { uuid: args[:uuid] }).execute.findings.first
end
end
end
end
......@@ -19,5 +19,9 @@
latest { true }
status { :succeeded }
end
trait :purged do
status { :purged }
end
end
end
......@@ -207,6 +207,17 @@
it { is_expected.to match_array(expected_uuids) }
end
context 'when the uuid is provided' do
let(:uuid) { Security::Finding.pick(:uuid) }
let(:params) do
{
uuid: uuid
}
end
it { is_expected.to match_array([uuid]) }
end
context 'when the page is provided' do
let(:page) { 2 }
# Limit per_page to force pagination on smaller dataset
......
......@@ -2,47 +2,39 @@
require 'spec_helper'
RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportFindings' do
RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportFinding' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:build) { create(:ci_build, :success, name: 'sast', pipeline: pipeline) }
let_it_be(:artifact) { create(:ee_ci_job_artifact, :sast, job: build) }
let_it_be(:report) { create(:ci_reports_security_report, type: :sast) }
let_it_be(:user) { create(:user) }
before_all do
create(:ci_build, :success, name: 'dast_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :dast_large_scanned_resources_field, job: job, project: project)
end
create(:ci_build, :success, name: 'sast_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :sast, job: job, project: project)
end
end
let_it_be(:query) do
let_it_be(:scan) { create(:security_scan, :latest_successful, scan_type: :sast, build: build) }
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipeline(iid: "#{pipeline.iid}") {
securityReportFindings(reportType: ["sast", "dast"]) {
nodes {
confidence
severity
reportType
securityReportFinding(uuid: "#{security_finding.uuid}") {
confidence
severity
reportType
name
scanner {
name
}
projectFingerprint
identifiers {
name
scanner {
name
}
projectFingerprint
identifiers {
name
}
uuid
solution
description
project {
fullPath
visibility
}
}
uuid
solution
description
project {
fullPath
visibility
}
}
}
......@@ -51,7 +43,24 @@
)
end
let(:security_report_findings) { subject.dig('project', 'pipeline', 'securityReportFindings', 'nodes') }
let(:security_finding) { Security::Finding.first }
let(:security_report_finding) { subject.dig('project', 'pipeline', 'securityReportFinding') }
before_all do
content = File.read(artifact.file.path)
Gitlab::Ci::Parsers::Security::Sast.parse!(content, report)
report.merge!(report)
scan.report_findings.each do |finding|
create(:security_finding,
severity: finding.severity,
confidence: finding.confidence,
project_fingerprint: finding.project_fingerprint,
deduplicated: true,
scan: scan,
uuid: finding.uuid)
end
end
subject do
post_graphql(query, current_user: user)
......@@ -60,49 +69,46 @@
context 'when the required features are enabled' do
before do
stub_licensed_features(sast: true, dast: true, security_dashboard: true)
stub_licensed_features(sast: true, security_dashboard: true)
end
context 'when user is member of the project' do
before do
project.add_developer(user)
let(:expected_finding) do
security_finding.scan.report_findings.find { |f| f.uuid == security_finding.uuid }
end
it 'returns all the vulnerability findings' do
expect(security_report_findings.length).to eq(25)
before do
project.add_developer(user)
end
it 'returns all the queried fields', :aggregate_failures do
security_report_finding = security_report_findings.first
expect(security_report_finding.dig('project', 'fullPath')).to eq(project.full_path)
expect(security_report_finding.dig('project', 'visibility')).to eq(project.visibility)
expect(security_report_finding['identifiers'].length).to eq(3)
expect(security_report_finding['confidence']).not_to be_nil
expect(security_report_finding['severity']).not_to be_nil
expect(security_report_finding['reportType']).not_to be_nil
expect(security_report_finding['name']).not_to be_nil
expect(security_report_finding['projectFingerprint']).not_to be_nil
expect(security_report_finding['uuid']).not_to be_nil
expect(security_report_finding['solution']).not_to be_nil
expect(security_report_finding['description']).not_to be_nil
expect(security_report_finding['identifiers'].length).to eq(expected_finding.identifiers.length)
expect(security_report_finding['confidence']).to eq(expected_finding.confidence)
expect(security_report_finding['severity']).to eq(expected_finding.severity.upcase)
expect(security_report_finding['reportType']).to eq(expected_finding.report_type.upcase)
expect(security_report_finding['name']).to eq(expected_finding.name)
expect(security_report_finding['uuid']).to eq(expected_finding.uuid)
expect(security_report_finding['solution']).to eq(expected_finding.solution)
expect(security_report_finding['description']).to eq(expected_finding.description)
end
end
context 'when user is not a member of the project' do
it 'returns no vulnerability findings' do
expect(security_report_findings).to be_blank
expect(security_report_finding).to be_nil
end
end
end
context 'when the required features are disabled' do
before do
stub_licensed_features(sast: false, dast: false, security_dashboard: false)
stub_licensed_features(sast: false, security_dashboard: false)
end
it 'returns no vulnerability findings' do
expect(security_report_findings).to be_blank
expect(security_report_finding).to be_nil
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportFindings' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:user) { create(:user) }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipeline(iid: "#{pipeline.iid}") {
securityReportFindings(reportType: ["sast", "dast"]) {
nodes {
confidence
severity
reportType
name
scanner {
name
}
projectFingerprint
identifiers {
name
}
uuid
solution
description
project {
fullPath
visibility
}
}
}
}
}
}
)
end
let(:security_report_findings) { subject.dig('project', 'pipeline', 'securityReportFindings', 'nodes') }
before_all do
create(:ci_build, :success, name: 'dast_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :dast_large_scanned_resources_field, job: job, project: project)
end
create(:ci_build, :success, name: 'sast_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :sast, job: job, project: project)
end
end
subject do
post_graphql(query, current_user: user)
graphql_data
end
context 'when the required features are enabled' do
before do
stub_licensed_features(sast: true, dast: true, security_dashboard: true)
end
context 'when user is member of the project' do
before do
project.add_developer(user)
end
it 'returns all the vulnerability findings' do
expect(security_report_findings.length).to eq(25)
end
it 'returns all the queried fields', :aggregate_failures do
security_report_finding = security_report_findings.first
expect(security_report_finding.dig('project', 'fullPath')).to eq(project.full_path)
expect(security_report_finding.dig('project', 'visibility')).to eq(project.visibility)
expect(security_report_finding['identifiers'].length).to eq(3)
expect(security_report_finding['confidence']).not_to be_nil
expect(security_report_finding['severity']).not_to be_nil
expect(security_report_finding['reportType']).not_to be_nil
expect(security_report_finding['name']).not_to be_nil
expect(security_report_finding['projectFingerprint']).not_to be_nil
expect(security_report_finding['uuid']).not_to be_nil
expect(security_report_finding['solution']).not_to be_nil
expect(security_report_finding['description']).not_to be_nil
end
end
context 'when user is not a member of the project' do
it 'returns no vulnerability findings' do
expect(security_report_findings).to be_blank
end
end
end
context 'when the required features are disabled' do
before do
stub_licensed_features(sast: false, dast: false, security_dashboard: false)
end
it 'returns no vulnerability findings' do
expect(security_report_findings).to be_blank
end
end
end
......@@ -18,7 +18,10 @@
]
if Gitlab.ee?
expected_fields += %w[security_report_summary security_report_findings code_quality_reports dast_profile]
expected_fields += %w[
security_report_summary security_report_findings security_report_finding
code_quality_reports dast_profile
]
end
expect(described_class).to have_graphql_fields(*expected_fields)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment