Commit ffc0b0aa authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'remove_vulnerable_projects_controller' into 'master'

Remove VulnerableProjectsController(s) and related logic

See merge request gitlab-org/gitlab!40179
parents 30ba441f 3e1bc4f3
# frozen_string_literal: true
class Groups::Security::VulnerableProjectsController < Groups::ApplicationController
include SecurityDashboardsPermissions
alias_method :vulnerable, :group
def index
vulnerable_projects = ::Security::VulnerableProjectsFinder.new(projects).execute
presented_projects = vulnerable_projects.map do |project|
::Security::VulnerableProjectPresenter.new(project)
end
render json: VulnerableProjectSerializer.new.represent(presented_projects)
end
private
def projects
::Project.for_group_and_its_subgroups(group).non_archived.without_deleted.with_route
end
end
# frozen_string_literal: true
class Security::VulnerableProjectsController < Security::ApplicationController
def index
vulnerable_projects = ::Security::VulnerableProjectsFinder.new(projects).execute
presented_projects = vulnerable_projects.map do |project|
::Security::VulnerableProjectPresenter.new(project)
end
render json: VulnerableProjectSerializer.new.represent(presented_projects)
end
private
def projects
vulnerable.projects.non_archived.without_deleted.with_route
end
end
# frozen_string_literal: true
module Security
class VulnerableProjectsFinder
PROJECTS_LIMIT = 5000
def initialize(projects)
@projects = projects
end
def execute
projects.where("EXISTS(?)", vulnerabilities).limit(PROJECTS_LIMIT) # rubocop:disable CodeReuse/ActiveRecord
end
private
attr_reader :projects
def vulnerabilities
::Vulnerabilities::Finding
.select(1)
.undismissed
.scoped_project
end
end
end
...@@ -38,7 +38,6 @@ module Groups::SecurityFeaturesHelper ...@@ -38,7 +38,6 @@ module Groups::SecurityFeaturesHelper
no_vulnerabilities_svg_path: image_path('illustrations/issues.svg'), no_vulnerabilities_svg_path: image_path('illustrations/issues.svg'),
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'), empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index'), dashboard_documentation: help_page_path('user/application_security/security_dashboard/index'),
vulnerable_projects_endpoint: group_security_vulnerable_projects_path(group),
vulnerabilities_export_endpoint: expose_path(api_v4_security_groups_vulnerability_exports_path(id: group.id)) vulnerabilities_export_endpoint: expose_path(api_v4_security_groups_vulnerability_exports_path(id: group.id))
} }
end end
......
...@@ -9,7 +9,6 @@ module SecurityHelper ...@@ -9,7 +9,6 @@ module SecurityHelper
empty_state_svg_path: image_path('illustrations/operations-dashboard_empty.svg'), empty_state_svg_path: image_path('illustrations/operations-dashboard_empty.svg'),
project_add_endpoint: security_projects_path, project_add_endpoint: security_projects_path,
project_list_endpoint: security_projects_path, project_list_endpoint: security_projects_path,
vulnerable_projects_endpoint: security_vulnerable_projects_path,
instance_dashboard_settings_path: security_settings_dashboard_path, instance_dashboard_settings_path: security_settings_dashboard_path,
vulnerability_feedback_help_path: help_page_path('user/application_security/index', anchor: 'interacting-with-the-vulnerabilities'), vulnerability_feedback_help_path: help_page_path('user/application_security/index', anchor: 'interacting-with-the-vulnerabilities'),
vulnerabilities_export_endpoint: expose_path(api_v4_security_vulnerability_exports_path) vulnerabilities_export_endpoint: expose_path(api_v4_security_vulnerability_exports_path)
......
# frozen_string_literal: true
module Security
class VulnerableProjectPresenter < ::Gitlab::View::Presenter::Delegated
SEVERITY_LEVELS = ::Vulnerabilities::Finding::SEVERITY_LEVELS.keys
presents :project
def initialize(project)
super(project, counts_for_project(project))
end
private
def counts_for_project(project)
SEVERITY_LEVELS.each_with_object({}) do |severity, counts|
counts["#{severity}_vulnerability_count".to_sym] = ::Vulnerabilities::Finding.batch_count_by_project_and_severity(project.id, severity)
end
end
end
end
# frozen_string_literal: true
class VulnerableProjectEntity < ProjectEntity
::Vulnerabilities::Finding::SEVERITY_LEVELS.each_key do |severity_level|
expose "#{severity_level}_vulnerability_count"
end
end
# frozen_string_literal: true
class VulnerableProjectSerializer < BaseSerializer
entity VulnerableProjectEntity
end
...@@ -149,7 +149,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -149,7 +149,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :dashboard, only: [:show], controller: :dashboard resource :dashboard, only: [:show], controller: :dashboard
resources :vulnerabilities, only: [:index] resources :vulnerabilities, only: [:index]
resource :compliance_dashboard, only: [:show] resource :compliance_dashboard, only: [:show]
resources :vulnerable_projects, only: [:index]
resource :discover, only: [:show], controller: :discover resource :discover, only: [:show], controller: :discover
resources :credentials, only: [:index] resources :credentials, only: [:index]
resources :merge_commit_reports, only: [:index], constraints: { format: :csv } resources :merge_commit_reports, only: [:index], constraints: { format: :csv }
......
...@@ -5,5 +5,4 @@ namespace :security do ...@@ -5,5 +5,4 @@ namespace :security do
get 'dasboard/settings', to: 'dashboard#settings', as: :settings_dashboard get 'dasboard/settings', to: 'dashboard#settings', as: :settings_dashboard
resources :projects, only: [:index, :create, :destroy] resources :projects, only: [:index, :create, :destroy]
resources :vulnerable_projects, only: [:index]
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::Security::VulnerableProjectsController do
let(:group) { create(:group) }
let(:user) { create(:user) }
it_behaves_like SecurityDashboardsPermissions do
let(:vulnerable) { group }
let(:security_dashboard_action) { get :index, params: { group_id: group }, format: :json }
end
describe '#index' do
before do
stub_licensed_features(security_dashboard: true)
group.add_developer(user)
sign_in(user)
end
subject { get :index, params: { group_id: group }, format: :json }
it "responds with a list of the group's most vulnerable projects" do
_ungrouped_project = create(:project)
_safe_project = create(:project, namespace: group)
vulnerable_project = create(:project, namespace: group)
pipeline = create(:ci_pipeline, :success, project: vulnerable_project)
create_list(
:vulnerabilities_occurrence,
2,
pipelines: [pipeline],
project: vulnerable_project,
severity: :critical
)
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to be(1)
expect(json_response.first['id']).to eq(vulnerable_project.id)
expect(json_response.first['full_path']).to eq(project_path(vulnerable_project))
expect(json_response.first['critical_vulnerability_count']).to eq(2)
end
it 'includes projects in subgroups' do
subgroup = create(:group, parent: group)
project = create(:project, namespace: subgroup)
create(:vulnerabilities_occurrence, project: project)
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to be(1)
expect(json_response.first['id']).to eq(project.id)
end
it 'does not include archived or deleted projects' do
archived_project = create(:project, :archived, namespace: group)
deleted_project = create(:project, namespace: group, pending_delete: true)
archived_pipeline = create(:ci_pipeline, :success, project: archived_project)
deleted_pipeline = create(:ci_pipeline, :success, project: deleted_project)
create(:vulnerabilities_occurrence, pipelines: [archived_pipeline], project: archived_project)
create(:vulnerabilities_occurrence, pipelines: [deleted_pipeline], project: deleted_project)
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_empty
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::VulnerableProjectsFinder do
describe '#execute' do
let(:projects) { Project.all }
let!(:safe_project) { create(:project) }
let(:vulnerable_project) { create(:project) }
let!(:vulnerability) { create(:vulnerabilities_occurrence, project: vulnerable_project) }
subject { described_class.new(projects).execute }
it 'returns the projects that have vulnerabilities from the collection of projects given to it' do
expect(subject).to contain_exactly(vulnerable_project)
end
it 'does not include projects that only have dismissed vulnerabilities' do
create(:vulnerabilities_occurrence, :dismissed, project: safe_project)
expect(subject).to contain_exactly(vulnerable_project)
end
it 'only uses 1 query' do
another_project = create(:project)
create(:vulnerabilities_occurrence, :dismissed, project: another_project)
expect { subject }.not_to exceed_query_limit(1)
expect(subject).to contain_exactly(vulnerable_project)
end
end
end
...@@ -129,7 +129,6 @@ RSpec.describe Groups::SecurityFeaturesHelper do ...@@ -129,7 +129,6 @@ RSpec.describe Groups::SecurityFeaturesHelper do
vulnerability_feedback_help_path: '/help/user/application_security/index#interacting-with-the-vulnerabilities', vulnerability_feedback_help_path: '/help/user/application_security/index#interacting-with-the-vulnerabilities',
empty_state_svg_path: '/images/illustrations/security-dashboard-empty-state.svg', empty_state_svg_path: '/images/illustrations/security-dashboard-empty-state.svg',
dashboard_documentation: '/help/user/application_security/security_dashboard/index', dashboard_documentation: '/help/user/application_security/security_dashboard/index',
vulnerable_projects_endpoint: "/groups/#{group.full_path}/-/security/vulnerable_projects",
vulnerabilities_export_endpoint: "/api/v4/security/groups/#{group.id}/vulnerability_exports" vulnerabilities_export_endpoint: "/api/v4/security/groups/#{group.id}/vulnerability_exports"
} }
end end
......
...@@ -14,7 +14,6 @@ RSpec.describe SecurityHelper do ...@@ -14,7 +14,6 @@ RSpec.describe SecurityHelper do
empty_state_svg_path: image_path('illustrations/operations-dashboard_empty.svg'), empty_state_svg_path: image_path('illustrations/operations-dashboard_empty.svg'),
project_add_endpoint: security_projects_path, project_add_endpoint: security_projects_path,
project_list_endpoint: security_projects_path, project_list_endpoint: security_projects_path,
vulnerable_projects_endpoint: security_vulnerable_projects_path,
instance_dashboard_settings_path: security_settings_dashboard_path, instance_dashboard_settings_path: security_settings_dashboard_path,
vulnerability_feedback_help_path: help_page_path('user/application_security/index', anchor: 'interacting-with-the-vulnerabilities'), vulnerability_feedback_help_path: help_page_path('user/application_security/index', anchor: 'interacting-with-the-vulnerabilities'),
vulnerabilities_export_endpoint: api_v4_security_vulnerability_exports_path vulnerabilities_export_endpoint: api_v4_security_vulnerability_exports_path
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::VulnerableProjectPresenter do
let(:project) { create(:project) }
before do
allow(::Vulnerabilities::Finding).to receive(:batch_count_by_project_and_severity).and_return(1)
end
subject { described_class.new(project) }
it 'presents the given project' do
expect(subject.id).to be(project.id)
end
::Vulnerabilities::Finding::SEVERITY_LEVELS.keys.each do |severity_level|
it "exposes a vulnerability count attribute for #{severity_level} vulnerabilities" do
expect(subject.public_send("#{severity_level}_vulnerability_count")).to be(1)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'GET /groups/*group_id/-/security/projects' do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
stub_licensed_features(security_dashboard: true)
login_as(user)
group.add_developer(user)
end
it 'does not use N+1 queries' do
control_project = create(:project, namespace: group)
create(:vulnerabilities_occurrence, project: control_project)
control_count = ActiveRecord::QueryRecorder.new do
get group_security_vulnerable_projects_path(group, format: :json)
end
projects = create_list(:project, 2, namespace: group)
projects.each do |project|
::Vulnerabilities::Finding::SEVERITY_LEVELS.keys.each do |severity|
create(:vulnerabilities_occurrence, severity: severity, project: project)
end
end
expect do
get group_security_vulnerable_projects_path(group, format: :json)
end.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to be(3)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'GET /-/security/vulnerable_projects' do
it_behaves_like 'security dashboard JSON endpoint' do
let(:security_dashboard_request) do
get security_vulnerable_projects_path, headers: { 'ACCEPT' => 'application/json' }
end
end
context 'with an authenticated user' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
stub_licensed_features(security_dashboard: true)
project.add_developer(user)
user.security_dashboard_projects << project
login_as(user)
end
subject { get security_vulnerable_projects_path, headers: { 'ACCEPT' => 'application/json' } }
it "responds with the projects on the user's dashboard and their vulnerability counts" do
safe_project = create(:project)
safe_project.add_developer(user)
user.security_dashboard_projects << safe_project
pipeline = create(:ci_pipeline, :success, project: project)
create_list(
:vulnerabilities_occurrence,
2,
pipelines: [pipeline],
project: project,
severity: :critical
)
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to be(1)
expect(json_response.first['id']).to eq(project.id)
expect(json_response.first['full_path']).to eq(project_path(project))
expect(json_response.first['critical_vulnerability_count']).to eq(2)
end
it 'does not include archived or deleted projects' do
archived_project = create(:project, :archived)
deleted_project = create(:project, pending_delete: true)
archived_pipeline = create(:ci_pipeline, :success, project: archived_project)
deleted_pipeline = create(:ci_pipeline, :success, project: deleted_project)
create(:vulnerabilities_occurrence, pipelines: [archived_pipeline], project: archived_project)
create(:vulnerabilities_occurrence, pipelines: [deleted_pipeline], project: deleted_project)
archived_project.add_developer(user)
deleted_project.add_developer(user)
user.security_dashboard_projects << [archived_project, deleted_project]
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_empty
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe VulnerableProjectEntity do
let(:project) { create(:project) }
let(:vulnerable_project) { ::Security::VulnerableProjectPresenter.new(project) }
before do
allow(::Vulnerabilities::Finding).to receive(:batch_count_by_project_and_severity).and_return(2)
end
subject { described_class.new(vulnerable_project) }
::Vulnerabilities::Finding::SEVERITY_LEVELS.keys.each do |severity_level|
it "exposes a vulnerability count attribute for #{severity_level} vulnerabilities" do
expect(subject.as_json["#{severity_level}_vulnerability_count".to_sym]).to be(2)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe VulnerableProjectSerializer do
let(:project) { create(:project) }
let(:serializer) { described_class.new(project: project, current_user: user) }
let(:user) { create(:user) }
let(:vulnerable_project) { ::Security::VulnerableProjectPresenter.new(project) }
before do
project.add_developer(user)
allow(::Vulnerabilities::Finding).to receive(:batch_count_by_project_and_severity)
end
describe '#represent' do
subject { serializer.represent(vulnerable_project) }
it 'includes counts for each severity of vulnerability' do
::Vulnerabilities::Finding::SEVERITY_LEVELS.keys.each do |severity_level|
expect(subject).to include("#{severity_level}_vulnerability_count".to_sym)
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