Skip to content
Snippets Groups Projects
Commit 7d5fa988 authored by Mehmet Emin INAC's avatar Mehmet Emin INAC :two: Committed by Eugenia Grieff
Browse files

Add `findingReportsComparer` field to MR GraphQL type

Changelog: added
EE: true
parent a11eea88
No related branches found
No related tags found
1 merge request!122339Add `findingReportsComparer` field to MR GraphQL type
Showing
with 447 additions and 1 deletion
......@@ -13244,6 +13244,35 @@ Returns [`CommitParentNames`](#commitparentnames).
| ---- | ---- | ----------- |
| <a id="commitreferencestippingtagslimit"></a>`limit` | [`Int!`](#int) | Number of ref names to return. |
 
### `ComparedSecurityReport`
Represents compared security report.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="comparedsecurityreportadded"></a>`added` **{warning-solid}** | [`[ComparedSecurityReportFinding!]`](#comparedsecurityreportfinding) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. New vulnerability findings. |
| <a id="comparedsecurityreportbasereportcreatedat"></a>`baseReportCreatedAt` | [`Time`](#time) | Time of the base report creation. |
| <a id="comparedsecurityreportbasereportoutofdate"></a>`baseReportOutOfDate` | [`Boolean`](#boolean) | Indicates whether the base report out of date. |
| <a id="comparedsecurityreportfixed"></a>`fixed` **{warning-solid}** | [`[ComparedSecurityReportFinding!]`](#comparedsecurityreportfinding) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Fixed vulnerability findings. |
| <a id="comparedsecurityreportheadreportcreatedat"></a>`headReportCreatedAt` | [`Time`](#time) | Time of the base report creation. |
### `ComparedSecurityReportFinding`
Represents finding.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="comparedsecurityreportfindingdescription"></a>`description` | [`String`](#string) | Description of the vulnerability finding. |
| <a id="comparedsecurityreportfindingfoundbypipelineiid"></a>`foundByPipelineIid` | [`String`](#string) | IID of the pipeline. |
| <a id="comparedsecurityreportfindingseverity"></a>`severity` | [`VulnerabilitySeverity`](#vulnerabilityseverity) | Severity of the vulnerability finding. |
| <a id="comparedsecurityreportfindingstate"></a>`state` | [`VulnerabilityState`](#vulnerabilitystate) | Finding status. |
| <a id="comparedsecurityreportfindingtitle"></a>`title` | [`String`](#string) | Title of the vulnerability finding. |
| <a id="comparedsecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | UUIDv5 digest based on the vulnerability's report type, primary identifier, location, fingerprint, project identifier. |
### `ComplianceFramework`
 
Represents a ComplianceFramework associated with a Project.
......@@ -15054,6 +15083,18 @@ Describes an external status check.
| <a id="fileuploadpath"></a>`path` | [`String!`](#string) | Path of the upload. |
| <a id="fileuploadsize"></a>`size` | [`Int!`](#int) | Size of the upload in bytes. |
 
### `FindingReportsComparer`
Represents security reports comparison for vulnerability findings.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="findingreportscomparerreport"></a>`report` **{warning-solid}** | [`ComparedSecurityReport`](#comparedsecurityreport) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Compared security report. |
| <a id="findingreportscomparerstatus"></a>`status` | [`FindingReportsComparerStatus`](#findingreportscomparerstatus) | Comparison status. |
| <a id="findingreportscomparerstatusreason"></a>`statusReason` | [`String`](#string) | Text explaining the status. |
### `Forecast`
 
Information about specific forecast created.
......@@ -17314,6 +17355,22 @@ Returns [`[DiffStats!]`](#diffstats).
| ---- | ---- | ----------- |
| <a id="mergerequestdiffstatspath"></a>`path` | [`String`](#string) | Specific file path. |
 
##### `MergeRequest.findingReportsComparer`
Vulnerability finding reports comparison reported on the merge request.
WARNING:
**Introduced** in 16.1.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`FindingReportsComparer`](#findingreportscomparer).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestfindingreportscomparerreporttype"></a>`reportType` | [`ComparableSecurityReportType!`](#comparablesecurityreporttype) | Filter vulnerability findings by report type. |
##### `MergeRequest.pipelines`
 
Pipelines for the merge request. Note: for performance reasons, no more than the most recent 500 pipelines will be returned.
......@@ -24450,6 +24507,20 @@ Mode of a commit action.
| <a id="commitencodingbase64"></a>`BASE64` | Base64 encoding. |
| <a id="commitencodingtext"></a>`TEXT` | Text encoding. |
 
### `ComparableSecurityReportType`
Comparable security report type.
| Value | Description |
| ----- | ----------- |
| <a id="comparablesecurityreporttypeapi_fuzzing"></a>`API_FUZZING` | API Fuzzing report. |
| <a id="comparablesecurityreporttypecontainer_scanning"></a>`CONTAINER_SCANNING` | Container Scanning report. |
| <a id="comparablesecurityreporttypecoverage_fuzzing"></a>`COVERAGE_FUZZING` | Coverage Fuzzing report. |
| <a id="comparablesecurityreporttypedast"></a>`DAST` | DAST report. |
| <a id="comparablesecurityreporttypedependency_scanning"></a>`DEPENDENCY_SCANNING` | Dependency Scanning report. |
| <a id="comparablesecurityreporttypesast"></a>`SAST` | SAST report. |
| <a id="comparablesecurityreporttypesecret_detection"></a>`SECRET_DETECTION` | Secret Detection report. |
### `ComplianceFrameworkPresenceFilter`
 
ComplianceFramework of a project for filtering.
......@@ -24965,6 +25036,16 @@ Event action.
| <a id="eventactionreopened"></a>`REOPENED` | Reopened action. |
| <a id="eventactionupdated"></a>`UPDATED` | Updated action. |
 
### `FindingReportsComparerStatus`
Report comparison status.
| Value | Description |
| ----- | ----------- |
| <a id="findingreportscomparerstatuserror"></a>`ERROR` | An error happened while generating the report. |
| <a id="findingreportscomparerstatusparsed"></a>`PARSED` | Report is generated. |
| <a id="findingreportscomparerstatusparsing"></a>`PARSING` | Report is being generated. |
### `ForecastStatus`
 
List of statuses for forecasting model.
......@@ -49,6 +49,13 @@ module MergeRequestType
null: true,
alpha: { milestone: '16.1' },
description: 'Diff summaries generated by AI'
field :finding_reports_comparer,
type: ::Types::Security::FindingReportsComparerType,
null: true,
alpha: { milestone: '16.1' },
description: 'Vulnerability finding reports comparison reported on the merge request.',
resolver: ::Resolvers::SecurityReport::FindingReportsComparerResolver
end
def merge_trains_count
......
# frozen_string_literal: true
module Resolvers
module SecurityReport
class FindingReportsComparerResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
type ::Types::Security::FindingReportsComparerType, null: true
authorize :read_security_resource
authorizes_object!
argument :report_type, Types::Security::ComparableReportTypeEnum,
required: true,
description: 'Filter vulnerability findings by report type.'
def resolve(report_type:)
::Security::MergeRequestSecurityReportGenerationService.execute(object, report_type)
end
end
end
end
# frozen_string_literal: true
module Types
module Security
class ComparableReportTypeEnum < BaseEnum
graphql_name 'ComparableSecurityReportType'
description 'Comparable security report type'
(::Enums::Vulnerability.report_types.keys - %w[generic cluster_image_scanning]).each do |report_type|
human_type = case report_type.to_sym
when :dast, :sast then report_type.upcase
when :api_fuzzing then 'API Fuzzing'
else report_type.titleize
end
value report_type.upcase, value: report_type, description: "#{human_type} report"
end
end
end
end
# frozen_string_literal: true
module Types
module Security
module FindingReportsComparer
# rubocop: disable Graphql/AuthorizeTypes (Parent node applies authorization)
class FindingType < BaseObject
graphql_name 'ComparedSecurityReportFinding'
description 'Represents finding.'
field :uuid,
type: GraphQL::Types::String,
null: true,
description: 'UUIDv5 digest based on the vulnerability\'s report type, primary identifier, location, ' \
'fingerprint, project identifier.'
field :title,
type: GraphQL::Types::String,
null: true,
description: 'Title of the vulnerability finding.',
hash_key: 'name'
field :description,
type: GraphQL::Types::String,
null: true,
description: 'Description of the vulnerability finding.'
field :state,
type: VulnerabilityStateEnum,
null: true,
description: 'Finding status.'
field :severity,
type: VulnerabilitySeverityEnum,
null: true,
description: 'Severity of the vulnerability finding.'
field :found_by_pipeline_iid,
type: GraphQL::Types::String,
null: true,
description: 'IID of the pipeline.'
def found_by_pipeline_iid
object.dig('found_by_pipeline', 'iid')
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
end
# frozen_string_literal: true
module Types
module Security
module FindingReportsComparer
# rubocop: disable Graphql/AuthorizeTypes (Parent node applies authorization)
class ReportType < BaseObject
graphql_name 'ComparedSecurityReport'
description 'Represents compared security report.'
field :base_report_created_at,
type: Types::TimeType,
null: true,
description: 'Time of the base report creation.'
field :base_report_out_of_date,
type: GraphQL::Types::Boolean,
null: true,
description: 'Indicates whether the base report out of date.'
field :head_report_created_at,
type: Types::TimeType,
null: true,
description: 'Time of the base report creation.'
field :added,
type: [FindingType],
null: true,
alpha: { milestone: '16.1' },
description: 'New vulnerability findings.'
field :fixed,
type: [FindingType],
null: true,
alpha: { milestone: '16.1' },
description: 'Fixed vulnerability findings.'
def base_report_created_at
Time.parse(object['base_report_created_at']) if object['base_report_created_at']
end
def head_report_created_at
Time.parse(object['head_report_created_at']) if object['head_report_created_at']
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
end
# frozen_string_literal: true
module Types
module Security
module FindingReportsComparer
class StatusEnum < BaseEnum
graphql_name 'FindingReportsComparerStatus'
description 'Report comparison status'
value 'PARSED', value: :parsed, description: "Report is generated."
value 'PARSING', value: :parsing, description: "Report is being generated."
value 'ERROR', value: :error, description: "An error happened while generating the report."
end
end
end
end
# frozen_string_literal: true
module Types
module Security
# rubocop: disable Graphql/AuthorizeTypes (The resolver authorizes the request)
class FindingReportsComparerType < BaseObject
graphql_name 'FindingReportsComparer'
description 'Represents security reports comparison for vulnerability findings.'
field :status,
type: FindingReportsComparer::StatusEnum,
null: true,
description: 'Comparison status.'
field :status_reason,
type: GraphQL::Types::String,
null: true,
description: 'Text explaining the status.'
field :report,
type: FindingReportsComparer::ReportType,
null: true,
alpha: { milestone: '16.1' },
hash_key: 'data',
description: 'Compared security report.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['MergeRequest'], feature_category: :code_review_workflow do
it { expect(described_class).to have_graphql_fields(:approvals_required, :merge_trains_count, :approval_state).at_least }
it { expect(described_class).to have_graphql_fields(:approvals_required, :merge_trains_count, :approval_state, :finding_reports_comparer).at_least }
it { expect(described_class).to have_graphql_field(:approved, complexity: 2, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:approvals_left, complexity: 2, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:has_security_reports, calls_gitaly?: true) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ComparableSecurityReportType'], feature_category: :vulnerability_management do
let(:expected_enum_keys) do
%w[
SAST
SECRET_DETECTION
DAST
CONTAINER_SCANNING
DEPENDENCY_SCANNING
COVERAGE_FUZZING
API_FUZZING
]
end
it 'exposes all vulnerability report types' do
expect(described_class.values.keys).to match_array(expected_enum_keys)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ComparedSecurityReportFinding'], feature_category: :vulnerability_management do
let(:expected_fields) { %i[uuid title description state severity found_by_pipeline_iid] }
it { expect(described_class).to have_graphql_fields(expected_fields) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ComparedSecurityReport'], feature_category: :vulnerability_management do
let(:expected_fields) { %i[base_report_created_at base_report_out_of_date head_report_created_at added fixed] }
it { expect(described_class).to have_graphql_fields(expected_fields) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['FindingReportsComparer'], feature_category: :vulnerability_management do
it { expect(described_class).to have_graphql_fields(:status, :status_reason, :report) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project.mergeRequest.findingReportsComparer', feature_category: :vulnerability_management do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let(:mock_report) do
{
status: :parsed,
status_reason: 'An example reason',
data: {
base_report_out_of_date: false,
base_report_created_at: nil,
head_report_created_at: Time.now.to_s,
added: [
{
uuid: SecureRandom.uuid,
name: 'Test Vulnerability',
description: 'Test description',
severity: 'critical',
state: 'confirmed',
found_by_pipeline: {
iid: 1
}
}
],
fixed: []
}.deep_stringify_keys
}
end
let(:finding_reports_comparer_fields) do
<<~QUERY
findingReportsComparer(reportType: SAST) {
status
statusReason
report {
baseReportCreatedAt
headReportCreatedAt
baseReportOutOfDate
added {
uuid
title
description
severity
state
foundByPipelineIid
}
fixed {
uuid
title
description
severity
state
foundByPipelineIid
}
}
}
QUERY
end
let(:merge_request_fields) do
query_graphql_field(
:merge_request,
{ iid: merge_request.iid.to_s },
finding_reports_comparer_fields)
end
let(:query) { graphql_query_for(:project, { full_path: project.full_path }, merge_request_fields) }
subject(:result) { graphql_data_at(:project, :merge_request, :finding_reports_comparer) }
before do
allow(::Security::MergeRequestSecurityReportGenerationService).to receive(:execute).and_return(mock_report)
end
context 'when the user is not authorized to read the field' do
before do
post_graphql(query, current_user: user)
end
it { is_expected.to be_nil }
end
context 'when the user is authorized to read the field' do
before do
stub_licensed_features(security_dashboard: true)
project.add_developer(user)
post_graphql(query, current_user: user)
end
it 'returns expected data' do
expect(result).to match(a_hash_including(
{
status: 'PARSED',
statusReason: 'An example reason',
report: {
baseReportOutOfDate: false,
baseReportCreatedAt: nil,
headReportCreatedAt: an_instance_of(String),
added: [
{
uuid: an_instance_of(String),
title: 'Test Vulnerability',
description: 'Test description',
severity: 'CRITICAL',
state: 'CONFIRMED',
foundByPipelineIid: '1'
}
],
fixed: []
}
}.deep_stringify_keys))
end
end
end
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