Commit 250bff8c authored by Tetiana Chupryna's avatar Tetiana Chupryna Committed by Jan Provaznik

Add filtering options for vulnerabilities in Dependency List

parent 5a308acb
......@@ -44,14 +44,19 @@ module Projects
@dependencies ||= collect_dependencies
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
@pipeline ||= project.all_pipelines.latest_successful_for_ref(project.default_branch)
end
def query_params
params.permit(:sort, :sort_by).delete_if do |key, value|
key == :sort_by && !value.in?(::Security::DependencyListService::SORT_BY_VALUES) ||
key == :sort && !value.in?(::Security::DependencyListService::SORT_VALUES)
params.permit(:sort, :sort_by, :filter).delete_if do |key, value|
match_disallowed(key, value)
end
end
......
......@@ -2,15 +2,17 @@
module Security
class DependencyListService
SORT_BY_VALUES = %w(name packager).freeze
SORT_BY_VALUES = %w(name packager severity).freeze
SORT_VALUES = %w(asc desc).freeze
FILTER_PACKAGE_MANAGERS_VALUES = %w(bundler yarn npm maven composer pip).freeze
FILTER_VALUES = %w(all vulnerable).freeze
# @param pipeline [Ci::Pipeline]
# @param [Hash] params to sort and filter dependencies
# @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 ['all', 'vulnerable'] :filter ('all') Field to filter
def initialize(pipeline:, params: {})
@pipeline = pipeline
@params = params
......@@ -19,7 +21,8 @@ module Security
# @return [Array<Hash>] collection of found dependencies
def execute
collection = init_collection
collection = filter(collection)
collection = filter_by_package_manager(collection)
collection = filter_by_vulnerable(collection)
collection = sort(collection)
collection
end
......@@ -32,7 +35,7 @@ module Security
pipeline.dependency_list_report.dependencies
end
def filter(collection)
def filter_by_package_manager(collection)
return collection unless params[:package_manager]
collection.select do |dependency|
......@@ -40,9 +43,20 @@ module Security
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)
if params[:sort_by] == 'packager'
case params[:sort_by]
when 'packager'
collection.sort_by! { |a| a[:packager] }
when 'severity'
collection = sort_by_severity(collection)
else
collection.sort_by! { |a| a[:name] }
end
......@@ -51,5 +65,20 @@ module Security
collection
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
---
title: Add filtering by vulnerabilities to Dependency List
merge_request: 14825
author:
type: added
......@@ -58,23 +58,55 @@ describe Projects::Security::DependenciesController do
context 'with 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
{
namespace_id: project.namespace,
project_id: project,
sort_by: 'packager',
sort: 'desc',
page: 1
filter: 'vulnerable'
}
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)
it 'return vulnerable dependencies' do
expect(json_response['dependencies'].length).to eq(3)
end
end
......
......@@ -36,6 +36,15 @@ describe Security::DependencyListService do
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
let(:params) do
{
......@@ -77,6 +86,23 @@ describe Security::DependencyListService do
expect(subject.last[:name]).to eq('async')
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
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