Commit 0f705fae authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Rémy Coutable

Add scanner name and identifiers to Vulnerability Type

This change adds new fields to Vulnerability GraphQL API. It adds
fields: identifiers, primaryIdentifier and scanner to Vulnerability.
parent ecfc5247
......@@ -13490,6 +13490,11 @@ type Vulnerability {
"""
id: ID!
"""
Identifiers of the vulnerability.
"""
identifiers: [VulnerabilityIdentifier!]!
"""
List of issue links related to the vulnerability
"""
......@@ -13525,6 +13530,11 @@ type Vulnerability {
"""
location: VulnerabilityLocation
"""
Primary identifier of the vulnerability.
"""
primaryIdentifier: VulnerabilityIdentifier
"""
The project on which the vulnerability was found
"""
......@@ -13536,6 +13546,11 @@ type Vulnerability {
"""
reportType: VulnerabilityReportType
"""
Scanner metadata for the vulnerability.
"""
scanner: VulnerabilityScanner
"""
Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)
"""
......@@ -13602,6 +13617,31 @@ type VulnerabilityEdge {
node: Vulnerability
}
"""
Represents a vulnerability identifier.
"""
type VulnerabilityIdentifier {
"""
External ID of the vulnerability identifier
"""
externalId: String
"""
External type of the vulnerability identifier
"""
externalType: String
"""
Name of the vulnerability identifier
"""
name: String
"""
URL of the vulnerability identifier
"""
url: String
}
"""
Represents an issue link of a vulnerability.
"""
......@@ -13846,6 +13886,21 @@ enum VulnerabilityReportType {
SECRET_DETECTION
}
"""
Represents a vulnerability scanner.
"""
type VulnerabilityScanner {
"""
External ID of the vulnerability scanner
"""
externalId: String
"""
Name of the vulnerability scanner
"""
name: String
}
"""
Represents vulnerability counts by severity
"""
......
......@@ -39667,6 +39667,32 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "identifiers",
"description": "Identifiers of the vulnerability.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "VulnerabilityIdentifier",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issueLinks",
"description": "List of issue links related to the vulnerability",
......@@ -39748,6 +39774,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "primaryIdentifier",
"description": "Primary identifier of the vulnerability.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityIdentifier",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "project",
"description": "The project on which the vulnerability was found",
......@@ -39776,6 +39816,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "scanner",
"description": "Scanner metadata for the vulnerability.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityScanner",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "severity",
"description": "Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)",
......@@ -39988,6 +40042,75 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityIdentifier",
"description": "Represents a vulnerability identifier.",
"fields": [
{
"name": "externalId",
"description": "External ID of the vulnerability identifier",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "externalType",
"description": "External type of the vulnerability identifier",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Name of the vulnerability identifier",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "url",
"description": "URL of the vulnerability identifier",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityIssueLink",
......@@ -40755,6 +40878,47 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityScanner",
"description": "Represents a vulnerability scanner.",
"fields": [
{
"name": "externalId",
"description": "External ID of the vulnerability scanner",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Name of the vulnerability scanner",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilitySeveritiesCount",
......@@ -2002,9 +2002,12 @@ Represents a vulnerability.
| --- | ---- | ---------- |
| `description` | String | Description of the vulnerability |
| `id` | ID! | GraphQL ID of the vulnerability |
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability |
| `primaryIdentifier` | VulnerabilityIdentifier | Primary identifier of the vulnerability. |
| `project` | Project | The project on which the vulnerability was found |
| `reportType` | VulnerabilityReportType | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION) |
| `scanner` | VulnerabilityScanner | Scanner metadata for the vulnerability. |
| `severity` | VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) |
| `state` | VulnerabilityState | State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED) |
| `title` | String | Title of the vulnerability |
......@@ -2012,6 +2015,17 @@ Represents a vulnerability.
| `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource |
| `vulnerabilityPath` | String | URL to the vulnerability's details page |
## VulnerabilityIdentifier
Represents a vulnerability identifier.
| Name | Type | Description |
| --- | ---- | ---------- |
| `externalId` | String | External ID of the vulnerability identifier |
| `externalType` | String | External type of the vulnerability identifier |
| `name` | String | Name of the vulnerability identifier |
| `url` | String | URL of the vulnerability identifier |
## VulnerabilityIssueLink
Represents an issue link of a vulnerability.
......@@ -2091,6 +2105,15 @@ Check permissions for the current user on a vulnerability
| `readVulnerabilityFeedback` | Boolean! | Indicates the user can perform `read_vulnerability_feedback` on this resource |
| `updateVulnerabilityFeedback` | Boolean! | Indicates the user can perform `update_vulnerability_feedback` on this resource |
## VulnerabilityScanner
Represents a vulnerability scanner.
| Name | Type | Description |
| --- | ---- | ---------- |
| `externalId` | String | External ID of the vulnerability scanner |
| `name` | String | Name of the vulnerability scanner |
## VulnerabilitySeveritiesCount
Represents vulnerability counts by severity
......
# frozen_string_literal: true
module Types
# rubocop: disable Graphql/AuthorizeTypes
class VulnerabilityIdentifierType < BaseObject
graphql_name 'VulnerabilityIdentifier'
description 'Represents a vulnerability identifier.'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the vulnerability identifier'
field :url, GraphQL::STRING_TYPE, null: true,
description: 'URL of the vulnerability identifier'
field :external_type, GraphQL::STRING_TYPE, null: true,
description: 'External type of the vulnerability identifier'
field :external_id, GraphQL::STRING_TYPE, null: true,
description: 'External ID of the vulnerability identifier'
end
# rubocop: enable Graphql/AuthorizeTypes
end
# frozen_string_literal: true
module Types
# rubocop: disable Graphql/AuthorizeTypes
class VulnerabilityScannerType < BaseObject
graphql_name 'VulnerabilityScanner'
description 'Represents a vulnerability scanner.'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the vulnerability scanner'
field :external_id, GraphQL::STRING_TYPE, null: true,
description: 'External ID of the vulnerability scanner'
end
# rubocop: enable Graphql/AuthorizeTypes
end
......@@ -42,6 +42,18 @@ module Types
description: 'Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability',
resolve: -> (obj, _args, _ctx) { obj.finding&.location&.merge(report_type: obj.report_type) }
field :scanner, VulnerabilityScannerType, null: true,
description: 'Scanner metadata for the vulnerability.',
resolve: -> (obj, _args, _ctx) { obj.finding&.scanner }
field :primary_identifier, VulnerabilityIdentifierType, null: true,
description: 'Primary identifier of the vulnerability.',
resolve: -> (obj, _args, _ctx) { obj.finding&.primary_identifier }
field :identifiers, [VulnerabilityIdentifierType], null: false,
description: 'Identifiers of the vulnerability.',
resolve: -> (obj, _args, _ctx) { obj.finding&.identifiers }
field :project, ::Types::ProjectType, null: true,
description: 'The project on which the vulnerability was found',
authorize: :read_project,
......
---
title: Add scanner name and identifiers to VulnerabilityType
merge_request: 34766
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['VulnerabilityIdentifier'] do
it { expect(described_class).to have_graphql_fields(:name, :url, :external_type, :external_id) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['VulnerabilityScanner'] do
it { expect(described_class).to have_graphql_fields(:name, :external_id) }
end
......@@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['Vulnerability'] do
let_it_be(:vulnerability) { create(:vulnerability, project: project) }
let(:fields) do
%i[userPermissions id title description user_notes_count state severity report_type vulnerability_path location project issueLinks]
%i[userPermissions id title description user_notes_count state severity report_type vulnerability_path location scanner primary_identifier identifiers project issueLinks]
end
before do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.vulnerabilities.identifiers' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let_it_be(:fields) do
<<~QUERY
identifiers {
name
externalType
externalId
url
}
QUERY
end
let_it_be(:query) do
graphql_query_for('vulnerabilities', {}, query_graphql_field('nodes', {}, fields))
end
let_it_be(:vulnerability) { create(:vulnerability, project: project, report_type: :container_scanning) }
let_it_be(:occurrence_identifier) do
create(
:vulnerabilities_identifier,
external_type: 'CVE',
external_id: 'CVE-2020-1211',
name: 'CVE-2020-1211',
url: 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=2020-1211'
)
end
let_it_be(:finding) do
create(
:vulnerabilities_occurrence,
vulnerability: vulnerability
)
end
let_it_be(:vulnerabilities_occurrence_identifier) do
create(:vulnerabilities_occurrence_identifier, identifier: occurrence_identifier, occurrence: finding)
end
subject { graphql_data.dig('vulnerabilities', 'nodes') }
before do
project.add_developer(user)
stub_licensed_features(security_dashboard: true)
post_graphql(query, current_user: user)
end
it 'returns a vulnerability identifiers' do
identifier = subject.first['identifiers'].first
expect(identifier['name']).to eq(occurrence_identifier.name)
expect(identifier['externalType']).to eq(occurrence_identifier.external_type)
expect(identifier['externalId']).to eq(occurrence_identifier.external_id)
expect(identifier['url']).to eq(occurrence_identifier.url)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.vulnerabilities.primaryIdentifier' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let_it_be(:fields) do
<<~QUERY
primaryIdentifier {
name
externalType
externalId
url
}
QUERY
end
let_it_be(:query) do
graphql_query_for('vulnerabilities', {}, query_graphql_field('nodes', {}, fields))
end
let_it_be(:vulnerability) { create(:vulnerability, project: project, report_type: :container_scanning) }
let_it_be(:primary_identifier) do
create(
:vulnerabilities_identifier,
external_type: 'CVE',
external_id: 'CVE-2020-1211',
name: 'CVE-2020-1211',
url: 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=2020-1211'
)
end
let_it_be(:finding) do
create(
:vulnerabilities_occurrence,
vulnerability: vulnerability,
primary_identifier: primary_identifier
)
end
subject { graphql_data.dig('vulnerabilities', 'nodes') }
before do
project.add_developer(user)
stub_licensed_features(security_dashboard: true)
post_graphql(query, current_user: user)
end
it 'returns a vulnerability identifiers' do
identifier = subject.first['primaryIdentifier']
expect(identifier['name']).to eq(primary_identifier.name)
expect(identifier['externalType']).to eq(primary_identifier.external_type)
expect(identifier['externalId']).to eq(primary_identifier.external_id)
expect(identifier['url']).to eq(primary_identifier.url)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.vulnerabilities.scanner' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let_it_be(:fields) do
<<~QUERY
scanner {
name
externalId
}
QUERY
end
let_it_be(:query) do
graphql_query_for('vulnerabilities', {}, query_graphql_field('nodes', {}, fields))
end
let_it_be(:vulnerability) { create(:vulnerability, project: project, report_type: :container_scanning) }
let_it_be(:vulnerabilities_scanner) do
create(
:vulnerabilities_scanner,
name: 'Vulnerability Scanner',
external_id: 'vulnerabilities_scanner',
project: project
)
end
let_it_be(:finding) do
create(
:vulnerabilities_occurrence,
vulnerability: vulnerability,
scanner: vulnerabilities_scanner
)
end
subject { graphql_data.dig('vulnerabilities', 'nodes') }
before do
project.add_developer(user)
stub_licensed_features(security_dashboard: true)
post_graphql(query, current_user: user)
end
it 'returns a vulnerability scanner' do
scanner = subject.first['scanner']
expect(scanner['name']).to eq(vulnerabilities_scanner.name)
expect(scanner['externalId']).to eq(vulnerabilities_scanner.external_id)
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