Commit 8be301fe authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '12425-filters-for-dependency-scanning-to-dl' into 'master'

Add filtering options for vulnerabilities in Dependency List

See merge request gitlab-org/gitlab-ee!14825
parents 5a308acb 250bff8c
...@@ -44,14 +44,19 @@ module Projects ...@@ -44,14 +44,19 @@ module Projects
@dependencies ||= collect_dependencies @dependencies ||= collect_dependencies
end end
def match_disallowed(param, value)
param == :sort_by && !value.in?(::Security::DependencyListService::SORT_BY_VALUES) ||
param == :sort && !value.in?(::Security::DependencyListService::SORT_VALUES) ||
param == :filter && !value.in?(::Security::DependencyListService::FILTER_VALUES)
end
def pipeline def pipeline
@pipeline ||= project.all_pipelines.latest_successful_for_ref(project.default_branch) @pipeline ||= project.all_pipelines.latest_successful_for_ref(project.default_branch)
end end
def query_params def query_params
params.permit(:sort, :sort_by).delete_if do |key, value| params.permit(:sort, :sort_by, :filter).delete_if do |key, value|
key == :sort_by && !value.in?(::Security::DependencyListService::SORT_BY_VALUES) || match_disallowed(key, value)
key == :sort && !value.in?(::Security::DependencyListService::SORT_VALUES)
end end
end end
......
...@@ -2,15 +2,17 @@ ...@@ -2,15 +2,17 @@
module Security module Security
class DependencyListService class DependencyListService
SORT_BY_VALUES = %w(name packager).freeze SORT_BY_VALUES = %w(name packager severity).freeze
SORT_VALUES = %w(asc desc).freeze SORT_VALUES = %w(asc desc).freeze
FILTER_PACKAGE_MANAGERS_VALUES = %w(bundler yarn npm maven composer pip).freeze FILTER_PACKAGE_MANAGERS_VALUES = %w(bundler yarn npm maven composer pip).freeze
FILTER_VALUES = %w(all vulnerable).freeze
# @param pipeline [Ci::Pipeline] # @param pipeline [Ci::Pipeline]
# @param [Hash] params to sort and filter dependencies # @param [Hash] params to sort and filter dependencies
# @option params ['asc', 'desc'] :sort ('asc') Order # @option params ['asc', 'desc'] :sort ('asc') Order
# @option params ['name', 'packager'] :sort_by ('name') Field to sort # @option params ['name', 'packager', 'severity'] :sort_by ('name') Field to sort
# @option params ['bundler', 'yarn', 'npm', 'maven', 'composer', 'pip'] :package_manager ('bundler') Field to filter # @option params ['bundler', 'yarn', 'npm', 'maven', 'composer', 'pip'] :package_manager ('bundler') Field to filter
# @option params ['all', 'vulnerable'] :filter ('all') Field to filter
def initialize(pipeline:, params: {}) def initialize(pipeline:, params: {})
@pipeline = pipeline @pipeline = pipeline
@params = params @params = params
...@@ -19,7 +21,8 @@ module Security ...@@ -19,7 +21,8 @@ module Security
# @return [Array<Hash>] collection of found dependencies # @return [Array<Hash>] collection of found dependencies
def execute def execute
collection = init_collection collection = init_collection
collection = filter(collection) collection = filter_by_package_manager(collection)
collection = filter_by_vulnerable(collection)
collection = sort(collection) collection = sort(collection)
collection collection
end end
...@@ -32,7 +35,7 @@ module Security ...@@ -32,7 +35,7 @@ module Security
pipeline.dependency_list_report.dependencies pipeline.dependency_list_report.dependencies
end end
def filter(collection) def filter_by_package_manager(collection)
return collection unless params[:package_manager] return collection unless params[:package_manager]
collection.select do |dependency| collection.select do |dependency|
...@@ -40,9 +43,20 @@ module Security ...@@ -40,9 +43,20 @@ module Security
end end
end end
def filter_by_vulnerable(collection)
return collection unless params[:filter] == 'vulnerable'
collection.select do |dependency|
dependency[:vulnerabilities].any?
end
end
def sort(collection) def sort(collection)
if params[:sort_by] == 'packager' case params[:sort_by]
when 'packager'
collection.sort_by! { |a| a[:packager] } collection.sort_by! { |a| a[:packager] }
when 'severity'
collection = sort_by_severity(collection)
else else
collection.sort_by! { |a| a[:name] } collection.sort_by! { |a| a[:name] }
end end
...@@ -51,5 +65,20 @@ module Security ...@@ -51,5 +65,20 @@ module Security
collection collection
end end
# vulnerabilities are initially sorted by severity in report
# https://gitlab.com/gitlab-org/security-products/analyzers/common/blob/ee9d378f46d9cc4e7b7592786a2a558dcc72b0f5/issue/report.go#L15
# So we can assume that first vulnerability in vulnerabilities array
# will have highest severity
def sort_by_severity(collection)
collection.sort do |dep_i, dep_j|
vuln_i = dep_i[:vulnerabilities]
vuln_j = dep_j[:vulnerabilities]
level_i = vuln_i.any? ? vuln_i.first['severity'].downcase : :undefined
level_j = vuln_j.any? ? vuln_j.first['severity'].downcase : :undefined
::Vulnerabilities::Occurrence::SEVERITY_LEVELS[level_j] <=> ::Vulnerabilities::Occurrence::SEVERITY_LEVELS[level_i]
end
end
end end
end end
---
title: Add filtering by vulnerabilities to Dependency List
merge_request: 14825
author:
type: added
...@@ -58,23 +58,55 @@ describe Projects::Security::DependenciesController do ...@@ -58,23 +58,55 @@ describe Projects::Security::DependenciesController do
context 'with params' do context 'with params' do
context 'with sorting params' do context 'with sorting params' do
context 'when sorted by packager' do
let(:params) do
{
namespace_id: project.namespace,
project_id: project,
sort_by: 'packager',
sort: 'desc',
page: 1
}
end
it 'returns sorted list' do
expect(json_response['dependencies'].first['packager']).to eq('Ruby (Bundler)')
expect(json_response['dependencies'].last['packager']).to eq('JavaScript (Yarn)')
end
it 'return 20 dependencies' do
expect(json_response['dependencies'].length).to eq(20)
end
end
context 'when sorted by severity' do
let(:params) do
{
namespace_id: project.namespace,
project_id: project,
sort_by: 'severity',
page: 1
}
end
it 'returns sorted list' do
expect(json_response['dependencies'].first['name']).to eq('nokogiri')
expect(json_response['dependencies'].second['name']).to eq('debug')
end
end
end
context 'with filter by vulnerable' do
let(:params) do let(:params) do
{ {
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
sort_by: 'packager', filter: 'vulnerable'
sort: 'desc',
page: 1
} }
end end
it 'returns sorted list' do it 'return vulnerable dependencies' do
expect(json_response['dependencies'].first['packager']).to eq('Ruby (Bundler)') expect(json_response['dependencies'].length).to eq(3)
expect(json_response['dependencies'].last['packager']).to eq('JavaScript (Yarn)')
end
it 'return 20 dependencies' do
expect(json_response['dependencies'].length).to eq(20)
end end
end end
......
...@@ -36,6 +36,15 @@ describe Security::DependencyListService do ...@@ -36,6 +36,15 @@ describe Security::DependencyListService do
end end
end end
context 'filtered by vulnerable' do
let(:params) { { filter: 'vulnerable' } }
it 'returns filtered items' do
expect(subject.size).to eq(3)
expect(subject.last[:vulnerabilities]).not_to be_empty
end
end
context 'sorted desc by packagers' do context 'sorted desc by packagers' do
let(:params) do let(:params) do
{ {
...@@ -77,6 +86,23 @@ describe Security::DependencyListService do ...@@ -77,6 +86,23 @@ describe Security::DependencyListService do
expect(subject.last[:name]).to eq('async') expect(subject.last[:name]).to eq('async')
end end
end end
context 'sorted by desc severity' do
let(:params) do
{
sort: 'desc',
sort_by: 'severity'
}
end
it 'returns array of data properly sorted' do
nokogiri_index = subject.find_index { |dep| dep[:name] == 'nokogiri' }
saml2js_index = subject.find_index { |dep| dep[:name] == 'saml2-js' }
expect(nokogiri_index).to be > saml2js_index
expect(subject).to end_with(subject[nokogiri_index])
end
end
end end
end end
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