Commit 92bc1bbd authored by Daniel Tian's avatar Daniel Tian

Add evidence and scanner fields to vuln details

Add evidence and scanner fields to vulnerability details on standalone
vulnerability page
parent 33ddf71a
...@@ -9,10 +9,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -9,10 +9,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13561) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13561) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
Each security vulnerability in the [Vulnerability List](../dependency_list/index.md) has its own standalone Each security vulnerability in the [Security Dashboard](../security_dashboard/index.md#project-security-dashboard) has its own standalone
page. page.
![Standalone vulnerability page](img/standalone_vulnerability_page_v12_10.png) ![Standalone vulnerability page](img/standalone_vulnerability_page_v13_1.png)
On the standalone vulnerability page, you can interact with the vulnerability in On the standalone vulnerability page, you can interact with the vulnerability in
several different ways: several different ways:
......
<script> <script>
import { GlLink } from '@gitlab/ui'; import { GlLink, GlSprintf } from '@gitlab/ui';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue'; import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import DetailItem from './detail_item.vue'; import DetailItem from './detail_item.vue';
export default { export default {
name: 'VulnerabilityDetails', name: 'VulnerabilityDetails',
components: { GlLink, SeverityBadge, DetailItem }, components: { GlLink, SeverityBadge, DetailItem, SafeLink, GlSprintf },
props: { props: {
vulnerability: { vulnerability: {
type: Object, type: Object,
...@@ -20,6 +21,9 @@ export default { ...@@ -20,6 +21,9 @@ export default {
location() { location() {
return this.finding.location || {}; return this.finding.location || {};
}, },
scanner() {
return this.finding.scanner || {};
},
fileText() { fileText() {
return (this.location.file || '') + (this.lineNumber ? `:${this.lineNumber}` : ''); return (this.location.file || '') + (this.lineNumber ? `:${this.lineNumber}` : '');
}, },
...@@ -30,6 +34,9 @@ export default { ...@@ -30,6 +34,9 @@ export default {
const { start_line: start, end_line: end } = this.location; const { start_line: start, end_line: end } = this.location;
return end > start ? `${start}-${end}` : start; return end > start ? `${start}-${end}` : start;
}, },
scannerUrl() {
return this.scanner.url || '';
},
}, },
}; };
</script> </script>
...@@ -44,12 +51,34 @@ export default { ...@@ -44,12 +51,34 @@ export default {
<detail-item :sprintf-message="__('%{labelStart}Severity:%{labelEnd} %{severity}')"> <detail-item :sprintf-message="__('%{labelStart}Severity:%{labelEnd} %{severity}')">
<severity-badge :severity="vulnerability.severity" class="gl-display-inline ml-1" /> <severity-badge :severity="vulnerability.severity" class="gl-display-inline ml-1" />
</detail-item> </detail-item>
<detail-item :sprintf-message="__('%{labelStart}Confidence:%{labelEnd} %{confidence}')" <detail-item
>{{ vulnerability.confidence }} v-if="finding.evidence"
:sprintf-message="__('%{labelStart}Evidence:%{labelEnd} %{evidence}')"
>{{ finding.evidence }}
</detail-item> </detail-item>
<detail-item :sprintf-message="__('%{labelStart}Report Type:%{labelEnd} %{reportType}')" <detail-item :sprintf-message="__('%{labelStart}Report Type:%{labelEnd} %{reportType}')"
>{{ vulnerability.report_type }} >{{ vulnerability.report_type }}
</detail-item> </detail-item>
<detail-item
v-if="scanner.name"
:sprintf-message="__('%{labelStart}Scanner:%{labelEnd} %{scanner}')"
>
<safe-link
:href="scannerUrl"
target="_blank"
rel="noopener noreferrer"
data-testid="scannerSafeLink"
>
<gl-sprintf
v-if="scanner.version"
:message="s__('Vulnerability|%{scannerName} (version %{scannerVersion})')"
>
<template #scannerName>{{ scanner.name }}</template>
<template #scannerVersion>{{ scanner.version }}</template>
</gl-sprintf>
<template v-else>{{ scanner.name }}</template>
</safe-link>
</detail-item>
<detail-item <detail-item
v-if="location.image" v-if="location.image"
:sprintf-message="__('%{labelStart}Image:%{labelEnd} %{image}')" :sprintf-message="__('%{labelStart}Image:%{labelEnd} %{image}')"
......
...@@ -43,7 +43,9 @@ module VulnerabilitiesHelper ...@@ -43,7 +43,9 @@ module VulnerabilitiesHelper
:issue_feedback, :issue_feedback,
:merge_request_feedback, :merge_request_feedback,
:project, :project,
:remediations :remediations,
:evidence,
:scanner
).merge( ).merge(
solution: remediation ? remediation['summary'] : finding[:solution] solution: remediation ? remediation['summary'] : finding[:solution]
) )
......
---
title: Add evidence and scanner fields to standalone vulnerability page
merge_request: 34498
author:
type: changed
...@@ -39,14 +39,15 @@ describe('Vulnerability Details', () => { ...@@ -39,14 +39,15 @@ describe('Vulnerability Details', () => {
expect(getText('title')).toBe(vulnerability.title); expect(getText('title')).toBe(vulnerability.title);
expect(getText('description')).toBe(finding.description); expect(getText('description')).toBe(finding.description);
expect(wrapper.find(SeverityBadge).props('severity')).toBe(vulnerability.severity); expect(wrapper.find(SeverityBadge).props('severity')).toBe(vulnerability.severity);
expect(getText('confidence')).toBe(`Confidence: ${vulnerability.confidence}`);
expect(getText('reportType')).toBe(`Report Type: ${vulnerability.report_type}`); expect(getText('reportType')).toBe(`Report Type: ${vulnerability.report_type}`);
expect(getById('image').exists()).toBeFalsy(); expect(getById('image').exists()).toBe(false);
expect(getById('os').exists()).toBeFalsy(); expect(getById('os').exists()).toBe(false);
expect(getById('file').exists()).toBeFalsy(); expect(getById('file').exists()).toBe(false);
expect(getById('class').exists()).toBeFalsy(); expect(getById('class').exists()).toBe(false);
expect(getById('method').exists()).toBeFalsy(); expect(getById('method').exists()).toBe(false);
expect(getById('evidence').exists()).toBe(false);
expect(getById('scanner').exists()).toBe(false);
expect(getAllById('link')).toHaveLength(0); expect(getAllById('link')).toHaveLength(0);
expect(getAllById('identifier')).toHaveLength(0); expect(getAllById('identifier')).toHaveLength(0);
}); });
...@@ -71,6 +72,11 @@ describe('Vulnerability Details', () => { ...@@ -71,6 +72,11 @@ describe('Vulnerability Details', () => {
expect(getText('method')).toBe(`Method: method name`); expect(getText('method')).toBe(`Method: method name`);
}); });
it('shows the evidence if it exists', () => {
createWrapper({ evidence: 'some evidence' });
expect(getText('evidence')).toBe(`Evidence: some evidence`);
});
it('shows the links if they exist', () => { it('shows the links if they exist', () => {
createWrapper({ links: [{ url: '0' }, { url: '1' }, { url: '2' }] }); createWrapper({ links: [{ url: '0' }, { url: '1' }, { url: '2' }] });
const links = getAllById('link'); const links = getAllById('link');
...@@ -138,4 +144,33 @@ describe('Vulnerability Details', () => { ...@@ -138,4 +144,33 @@ describe('Vulnerability Details', () => {
expect(file().text()).toBe('test.txt:24'); expect(file().text()).toBe('test.txt:24');
}); });
}); });
describe('scanner', () => {
const link = () => getById('scannerSafeLink');
const scannerText = () => getById('scanner').text();
it('shows the scanner name only but no link', () => {
createWrapper({ scanner: { name: 'some scanner' } });
expect(scannerText()).toBe('Scanner: some scanner');
expect(link().vm.$el instanceof HTMLSpanElement).toBe(true);
});
it('shows the scanner name and version but no link', () => {
createWrapper({ scanner: { name: 'some scanner', version: '1.2.3' } });
expect(scannerText()).toBe('Scanner: some scanner (version 1.2.3)');
expect(link().vm.$el instanceof HTMLSpanElement).toBe(true);
});
it('shows the scanner name only with a link', () => {
createWrapper({ scanner: { name: 'some scanner', url: '//link' } });
expect(scannerText()).toBe('Scanner: some scanner');
expect(link().props('href')).toBe('//link');
});
it('shows the scanner name and version with a link', () => {
createWrapper({ scanner: { name: 'some scanner', version: '1.2.3', url: '//link' } });
expect(scannerText()).toBe('Scanner: some scanner (version 1.2.3)');
expect(link().props('href')).toBe('//link');
});
});
}); });
...@@ -116,7 +116,9 @@ RSpec.describe VulnerabilitiesHelper do ...@@ -116,7 +116,9 @@ RSpec.describe VulnerabilitiesHelper do
name: finding.name, name: finding.name,
project: kind_of(Grape::Entity::Exposure::NestingExposure::OutputBuilder), project: kind_of(Grape::Entity::Exposure::NestingExposure::OutputBuilder),
remediations: finding.remediations, remediations: finding.remediations,
solution: kind_of(String) solution: kind_of(String),
evidence: kind_of(String),
scanner: kind_of(Grape::Entity::Exposure::NestingExposure::OutputBuilder)
) )
expect(subject[:location]['blob_path']).to match(kind_of(String)) expect(subject[:location]['blob_path']).to match(kind_of(String))
......
...@@ -386,7 +386,7 @@ msgstr "" ...@@ -386,7 +386,7 @@ msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}" msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr "" msgstr ""
msgid "%{labelStart}Confidence:%{labelEnd} %{confidence}" msgid "%{labelStart}Evidence:%{labelEnd} %{evidence}"
msgstr "" msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}" msgid "%{labelStart}File:%{labelEnd} %{file}"
...@@ -404,6 +404,9 @@ msgstr "" ...@@ -404,6 +404,9 @@ msgstr ""
msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}" msgid "%{labelStart}Report Type:%{labelEnd} %{reportType}"
msgstr "" msgstr ""
msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
msgstr ""
msgid "%{labelStart}Severity:%{labelEnd} %{severity}" msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr "" msgstr ""
......
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