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 { ...@@ -13490,6 +13490,11 @@ type Vulnerability {
""" """
id: ID! id: ID!
"""
Identifiers of the vulnerability.
"""
identifiers: [VulnerabilityIdentifier!]!
""" """
List of issue links related to the vulnerability List of issue links related to the vulnerability
""" """
...@@ -13525,6 +13530,11 @@ type Vulnerability { ...@@ -13525,6 +13530,11 @@ type Vulnerability {
""" """
location: VulnerabilityLocation location: VulnerabilityLocation
"""
Primary identifier of the vulnerability.
"""
primaryIdentifier: VulnerabilityIdentifier
""" """
The project on which the vulnerability was found The project on which the vulnerability was found
""" """
...@@ -13536,6 +13546,11 @@ type Vulnerability { ...@@ -13536,6 +13546,11 @@ type Vulnerability {
""" """
reportType: VulnerabilityReportType reportType: VulnerabilityReportType
"""
Scanner metadata for the vulnerability.
"""
scanner: VulnerabilityScanner
""" """
Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)
""" """
...@@ -13602,6 +13617,31 @@ type VulnerabilityEdge { ...@@ -13602,6 +13617,31 @@ type VulnerabilityEdge {
node: Vulnerability 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. Represents an issue link of a vulnerability.
""" """
...@@ -13846,6 +13886,21 @@ enum VulnerabilityReportType { ...@@ -13846,6 +13886,21 @@ enum VulnerabilityReportType {
SECRET_DETECTION 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 Represents vulnerability counts by severity
""" """
......
...@@ -39667,6 +39667,32 @@ ...@@ -39667,6 +39667,32 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "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", "name": "issueLinks",
"description": "List of issue links related to the vulnerability", "description": "List of issue links related to the vulnerability",
...@@ -39748,6 +39774,20 @@ ...@@ -39748,6 +39774,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "primaryIdentifier",
"description": "Primary identifier of the vulnerability.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityIdentifier",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "project", "name": "project",
"description": "The project on which the vulnerability was found", "description": "The project on which the vulnerability was found",
...@@ -39776,6 +39816,20 @@ ...@@ -39776,6 +39816,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "scanner",
"description": "Scanner metadata for the vulnerability.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityScanner",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "severity", "name": "severity",
"description": "Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)", "description": "Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL)",
...@@ -39988,6 +40042,75 @@ ...@@ -39988,6 +40042,75 @@
"enumValues": null, "enumValues": null,
"possibleTypes": 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", "kind": "OBJECT",
"name": "VulnerabilityIssueLink", "name": "VulnerabilityIssueLink",
...@@ -40755,6 +40878,47 @@ ...@@ -40755,6 +40878,47 @@
], ],
"possibleTypes": null "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", "kind": "OBJECT",
"name": "VulnerabilitySeveritiesCount", "name": "VulnerabilitySeveritiesCount",
...@@ -2002,9 +2002,12 @@ Represents a vulnerability. ...@@ -2002,9 +2002,12 @@ Represents a vulnerability.
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `description` | String | Description of the vulnerability | | `description` | String | Description of the vulnerability |
| `id` | ID! | GraphQL ID 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 | | `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 | | `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) | | `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) | | `severity` | VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) |
| `state` | VulnerabilityState | State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED) | | `state` | VulnerabilityState | State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED) |
| `title` | String | Title of the vulnerability | | `title` | String | Title of the vulnerability |
...@@ -2012,6 +2015,17 @@ Represents a vulnerability. ...@@ -2012,6 +2015,17 @@ Represents a vulnerability.
| `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource | | `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource |
| `vulnerabilityPath` | String | URL to the vulnerability's details page | | `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 ## VulnerabilityIssueLink
Represents an issue link of a vulnerability. Represents an issue link of a vulnerability.
...@@ -2091,6 +2105,15 @@ Check permissions for the current user on 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 | | `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 | | `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 ## VulnerabilitySeveritiesCount
Represents vulnerability counts by severity 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 ...@@ -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', 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) } 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, field :project, ::Types::ProjectType, null: true,
description: 'The project on which the vulnerability was found', description: 'The project on which the vulnerability was found',
authorize: :read_project, 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 ...@@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['Vulnerability'] do
let_it_be(:vulnerability) { create(:vulnerability, project: project) } let_it_be(:vulnerability) { create(:vulnerability, project: project) }
let(:fields) do 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 end
before do 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