Commit 0094a007 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '5656-add-fe-store-support-for-remediations-ee' into 'master'

Support remediations in Dependency Scanning report

See merge request gitlab-org/gitlab-ee!9009
parents 080c604e 2b8dadaf
......@@ -11,6 +11,32 @@ import { n__, s__, sprintf } from '~/locale';
export const findIssueIndex = (issues, issue) =>
issues.findIndex(el => el.project_fingerprint === issue.project_fingerprint);
/**
*
* Returns whether a vulnerability has a match in an array of fixes
*
* @param fixes {Array} Array of fixes (vulnerability identifiers) of a remediation
* @param vulnerability {Object} Vulnerability
* @returns {boolean}
*/
const hasMatchingFix = (fixes, vulnerability) =>
Array.isArray(fixes) ? fixes.some(fix => _.isMatch(vulnerability, fix)) : false;
/**
*
* Returns the first remediation that fixes the given vulnerability or null
*
* @param {Array} remediations
* @param {Object} vulnerability
* @returns {Object|null}
*/
export const findMatchingRemediation = (remediations, vulnerability) => {
if (!Array.isArray(remediations)) {
return null;
}
return remediations.find(rem => hasMatchingFix(rem.fixes, vulnerability)) || null;
};
/**
* Returns given vulnerability enriched with the corresponding
* feedback (`dismissal` or `issue` type)
......@@ -101,6 +127,7 @@ function adaptDeprecatedReportFormat(report) {
if (Array.isArray(report)) {
return {
vulnerabilities: report,
remediations: [],
};
}
......@@ -140,8 +167,9 @@ export const parseSastIssues = (report = [], feedback = [], path = '') =>
* @param {String} path
* @returns {Array}
*/
export const parseDependencyScanningIssues = (report = [], feedback = [], path = '') =>
adaptDeprecatedReportFormat(report).vulnerabilities.map(issue => {
export const parseDependencyScanningIssues = (report = [], feedback = [], path = '') => {
const { vulnerabilities, remediations } = adaptDeprecatedReportFormat(report);
return vulnerabilities.map(issue => {
const parsed = {
...adaptDeprecatedIssueFormat(issue),
category: 'dependency_scanning',
......@@ -149,6 +177,12 @@ export const parseDependencyScanningIssues = (report = [], feedback = [], path =
title: issue.message,
};
const remediation = findMatchingRemediation(remediations, parsed);
if (remediation) {
parsed.remediation = remediation;
}
return {
...parsed,
path: parsed.location.file,
......@@ -156,6 +190,7 @@ export const parseDependencyScanningIssues = (report = [], feedback = [], path =
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
};
/**
* Parses Container Scanning results into a common format to allow to use the same Vue component.
......@@ -164,7 +199,6 @@ export const parseDependencyScanningIssues = (report = [], feedback = [], path =
*
* @param {Array} issues
* @param {Array} feedback
* @param {String} path
* @returns {Array}
*/
export const parseSastContainer = (issues = [], feedback = []) =>
......
......@@ -497,6 +497,12 @@ export const dependencyScanningIssues = [
export const dependencyScanningIssuesMajor2 = {
version: '2.0',
vulnerabilities: dependencyScanningIssues,
remediations: [
{
fixes: [{ cve: dependencyScanningIssues[0].cve }],
summary: 'Fixes the first dependency Scanning issue',
},
],
};
export const dependencyScanningIssuesBase = [
......
import sha1 from 'sha1';
import {
findIssueIndex,
findMatchingRemediation,
parseSastIssues,
parseDependencyScanningIssues,
parseSastContainer,
......@@ -55,6 +56,50 @@ describe('security reports utils', () => {
});
});
describe('findMatchingRemediation', () => {
const remediation1 = {
fixes: [
{
cve: '123',
},
{
foobar: 'baz',
},
],
summary: 'Update to x.y.z',
};
const remediation2 = { ...remediation1, summary: 'Remediation2' };
const impossibleRemediation = {
fixes: [],
summary: 'Impossible',
};
const remediations = [impossibleRemediation, remediation1, remediation2];
it('returns null for empty vulnerability', () => {
expect(findMatchingRemediation(remediations, {})).toBeNull();
expect(findMatchingRemediation(remediations, null)).toBeNull();
expect(findMatchingRemediation(remediations, undefined)).toBeNull();
});
it('returns null for empty remediations', () => {
expect(findMatchingRemediation([], { cve: '123' })).toBeNull();
expect(findMatchingRemediation(null, { cve: '123' })).toBeNull();
expect(findMatchingRemediation(undefined, { cve: '123' })).toBeNull();
});
it('returns null for vulnerabilities without remediation', () => {
expect(findMatchingRemediation(remediations, { cve: 'NOT_FOUND' })).toBeNull();
});
it('returns first matching remediation for a vulnerability', () => {
expect(findMatchingRemediation(remediations, { cve: '123' })).toEqual(remediation1);
expect(findMatchingRemediation(remediations, { foobar: 'baz' })).toEqual(remediation1);
});
});
describe('parseSastIssues', () => {
it('should parse the received issues with old JSON format', () => {
const parsed = parseSastIssues(oldSastIssues, [], 'path')[0];
......@@ -140,6 +185,7 @@ describe('security reports utils', () => {
expect(parsed.location.end_line).toBeUndefined();
expect(parsed.urlPath).toEqual(`path/${raw.location.file}`);
expect(parsed.project_fingerprint).toEqual(sha1(raw.cve));
expect(parsed.remediation).toEqual(dependencyScanningIssuesMajor2.remediations[0]);
});
it('generate correct path to file when there is no line', () => {
......
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