Commit ad0f45fe authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '6240-filtering-for-sec-dashboard' into 'master'

Add filtering for group vulerabilities in group sec dashboard

See merge request gitlab-org/gitlab-ee!8876
parents 6039a3ed 0a302d26
# frozen_string_literal: true
class Groups::Security::VulnerabilitiesController < Groups::Security::ApplicationController
HISTORY_RANGE = 3.months
def index
@vulnerabilities = group.latest_vulnerabilities
.sast # FIXME: workaround until https://gitlab.com/gitlab-org/gitlab-ee/issues/6240
@vulnerabilities = ::Security::VulnerabilitiesFinder.new(group: group, params: finder_params)
.execute
.ordered
.page(params[:page])
......@@ -33,4 +34,11 @@ class Groups::Security::VulnerabilitiesController < Groups::Security::Applicatio
end
end
end
private
def finder_params
params.permit(report_type: [], project_id: [], severity: [])
.merge(hide_dismissed: Gitlab::Utils.to_boolean(params[:hide_dismissed]))
end
end
# frozen_string_literal: true
# Security::VulnerabilitiesFinder
#
# Used to filter Vulnerabilities::Occurrences by set of params for Security Dashboard
#
# Arguments:
# group - object to filter vulnerabilities
# params:
# severity: Array<String>
# project: Array<String>
# report_type: Array<String>
module Security
class VulnerabilitiesFinder
attr_accessor :params
attr_reader :group
def initialize(group:, params: {})
@group = group
@params = params
end
def execute
collection = group.latest_vulnerabilities
collection = by_report_type(collection)
collection = by_project(collection)
collection = by_severity(collection)
collection
end
private
def by_report_type(items)
return items unless params[:report_type].present?
items.by_report_types(
Vulnerabilities::Occurrence::REPORT_TYPES.values_at(
*params[:report_type]).compact)
end
def by_project(items)
return items unless params[:project_id].present?
items.by_projects(params[:project_id])
end
def by_severity(items)
return items unless params[:severity].present?
items.by_severities(
Vulnerabilities::Occurrence::LEVELS.values_at(
*params[:severity]).compact)
end
end
end
......@@ -64,6 +64,10 @@ module Vulnerabilities
scope :ordered, -> { order("severity desc", :id) }
scope :counted_by_report_and_severity, -> { group(:report_type, :severity).count }
scope :by_report_types, -> (values) { where(report_type: values) }
scope :by_projects, -> (values) { where(project_id: values) }
scope :by_severities, -> (values) { where(severity: values) }
scope :all_preloaded, -> do
preload(:scanner, :identifiers, :project)
end
......
---
title: Add Filtering vulnerabilities in the Group Security Dashboard
merge_request: 8817
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
describe Groups::Security::VulnerabilitiesController do
......@@ -103,7 +105,7 @@ describe Groups::Security::VulnerabilitiesController do
end
end
context 'whith multiple report types' do
context 'with multiple report types' do
before do
projects.each do |project|
create_vulnerabilities(2, project_guest, { report_type: :sast })
......@@ -111,17 +113,32 @@ describe Groups::Security::VulnerabilitiesController do
end
end
# FIXME: we only support SAST in group dashboard until https://gitlab.com/gitlab-org/gitlab-ee/issues/6240
# and https://gitlab.com/gitlab-org/gitlab-ee/issues/8481
it "returns a list of vulnerabilities but only for SAST report type" do
it "returns a list of vulnerabilities for all report types without filter" do
subject
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an(Array)
expect(json_response.length).to eq 3
expect(json_response.map { |v| v['report_type'] }.uniq).to contain_exactly('sast', 'dependency_scanning')
expect(response).to match_response_schema('vulnerabilities/occurrence_list', dir: 'ee')
end
it "returns a list of vulnerabilities for sast only if filter is enabled" do
get :index, params: { group_id: group, report_type: ['sast'] }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an(Array)
expect(json_response.length).to eq 2
expect(json_response.map { |v| v['report_type'] }.uniq).to contain_exactly('sast')
expect(response).to match_response_schema('vulnerabilities/occurrence_list', dir: 'ee')
end
it "returns a list of vulnerabilities of all types with multi filter" do
get :index, params: { group_id: group, report_type: %w[sast dependency_scanning] }, format: :json
expect(json_response.length).to eq 3
expect(json_response.map { |v| v['report_type'] }.uniq).to contain_exactly('sast', 'dependency_scanning')
end
end
def create_vulnerabilities(count, project, options = {})
......
# frozen_string_literal: true
require 'spec_helper'
describe Security::VulnerabilitiesFinder do
describe '#execute' do
set(:group) { create(:group) }
set(:project1) { create(:project, :private, :repository, group: group) }
set(:project2) { create(:project, :private, :repository, group: group) }
set(:pipeline1) { create(:ci_pipeline, :success, project: project1) }
set(:pipeline2) { create(:ci_pipeline, :success, project: project2) }
set(:vulnerability1) { create(:vulnerabilities_occurrence, report_type: :sast, severity: :high, pipelines: [pipeline1], project: project1) }
set(:vulnerability2) { create(:vulnerabilities_occurrence, report_type: :dependency_scanning, severity: :medium, pipelines: [pipeline2], project: project2) }
set(:vulnerability3) { create(:vulnerabilities_occurrence, report_type: :sast, severity: :low, pipelines: [pipeline2], project: project2) }
set(:vulnerability4) { create(:vulnerabilities_occurrence, report_type: :dast, severity: :medium, pipelines: [pipeline1], project: project1) }
subject { described_class.new(group: group, params: params).execute }
context 'by report type' do
context 'when sast' do
let(:params) { { report_type: %w[sast] } }
it 'includes only sast' do
is_expected.to contain_exactly(vulnerability1, vulnerability3)
end
end
context 'when dependency_scanning' do
let(:params) { { report_type: %w[dependency_scanning] } }
it 'includes only depscan' do
is_expected.to contain_exactly(vulnerability2)
end
end
end
context 'by severity' do
context 'when high' do
let(:params) { { severity: %w[high] } }
it 'includes only high' do
is_expected.to contain_exactly(vulnerability1)
end
end
context 'when medium' do
let(:params) { { severity: %w[medium] } }
it 'includes only medium' do
is_expected.to contain_exactly(vulnerability2, vulnerability4)
end
end
end
context 'by project' do
let(:params) { { project_id: [project2.id] } }
it 'includes only vulnerabilities for one project' do
is_expected.to contain_exactly(vulnerability2, vulnerability3)
end
end
# FIXME: unskip when this filter is implemented
context 'by dismissals' do
let!(:dismissal) do
create(:vulnerability_feedback, :sast, :dismissal,
pipeline: pipeline1,
project: project1,
project_fingerprint: vulnerability1.project_fingerprint)
end
let(:params) { { hide_dismissed: true } }
skip 'exclude dismissal' do
is_expected.to contain_exactly(vulnerability2, vulnerability3, vulnerability4)
end
end
context 'by all filters' do
context 'with found entity' do
let(:params) { { severity: %w[high medium low], project_id: [project1.id, project2.id], report_type: %w[sast dast] } }
it 'filters by all params' do
is_expected.to contain_exactly(vulnerability1, vulnerability3, vulnerability4)
end
end
context 'without found entity' do
let(:params) { { severity: %w[low], project_id: [project1.id], report_type: %w[sast] } }
it 'did not find anything' do
is_expected.to be_empty
end
end
end
context 'by some filters' do
context 'with found entity' do
let(:params) { { project_id: [project2.id], severity: %w[medium low] } }
it 'filters by all params' do
is_expected.to contain_exactly(vulnerability2, vulnerability3)
end
end
context 'without found entity' do
let(:params) { { project_id: project1.id, severity: %w[low] } }
it 'did not find anything' do
is_expected.to be_empty
end
end
end
end
end
......@@ -125,4 +125,74 @@ describe Vulnerabilities::Occurrence do
expect(second.count).to eq(1)
end
end
describe '.by_report_types' do
let!(:vulnerability_sast) { create(:vulnerabilities_occurrence, report_type: :sast) }
let!(:vulnerability_dast) { create(:vulnerabilities_occurrence, report_type: :dast) }
let!(:vulnerability_depscan) { create(:vulnerabilities_occurrence, report_type: :dependency_scanning) }
subject { described_class.by_report_types(param) }
context 'with one param' do
let(:param) { 0 }
it 'returns found record' do
is_expected.to contain_exactly(vulnerability_sast)
end
end
context 'with array of params' do
let(:param) { [1, 3] }
it 'returns found records' do
is_expected.to contain_exactly(vulnerability_dast, vulnerability_depscan)
end
end
context 'without found record' do
let(:param) { 2 }
it 'returns empty collection' do
is_expected.to be_empty
end
end
end
describe '.by_projects' do
let!(:vulnerability1) { create(:vulnerabilities_occurrence) }
let!(:vulnerability2) { create(:vulnerabilities_occurrence) }
subject { described_class.by_projects(param) }
context 'with found record' do
let(:param) { vulnerability1.project_id }
it 'returns found record' do
is_expected.to contain_exactly(vulnerability1)
end
end
end
describe '.by_severities' do
let!(:vulnerability_high) { create(:vulnerabilities_occurrence, severity: :high) }
let!(:vulnerability_low) { create(:vulnerabilities_occurrence, severity: :low) }
subject { described_class.by_severities(param) }
context 'with one param' do
let(:param) { 4 }
it 'returns found record' do
is_expected.to contain_exactly(vulnerability_low)
end
end
context 'without found record' do
let(:param) { 7 }
it 'returns empty collection' do
is_expected.to be_empty
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