Skip to content
代码片段 群组 项目
未验证 提交 911b4003 编辑于 作者: Michał Zając's avatar Michał Zając 提交者: GitLab
浏览文件

Merge branch 'srushik/add_dependency_paths_to_vulnerabilities' into 'master'

No related branches found
No related tags found
2 合并请求!3031Merge per-main-jh to main-jh by luzhiyuan,!3030Merge per-main-jh to main-jh
显示 258 个添加11 个删除
# frozen_string_literal: true
module Sbom
class DependencyPathsFinder
def initialize(project, params: {})
@project = project
@params = params
end
def execute
Sbom::DependencyPath.find(occurrence_id: params[:occurrence_id], project_id: project.id)
end
private
attr_reader :project, :params
end
end
...@@ -21,7 +21,12 @@ def resolve(occurrence:) ...@@ -21,7 +21,12 @@ def resolve(occurrence:)
occurrence_id = resolve_gid(occurrence, ::Sbom::Occurrence) occurrence_id = resolve_gid(occurrence, ::Sbom::Occurrence)
result = Gitlab::Metrics.measure(:dependency_path_cte) do result = Gitlab::Metrics.measure(:dependency_path_cte) do
::Sbom::DependencyPath.find(occurrence_id: occurrence_id, project_id: project.id) ::Sbom::DependencyPathsFinder.new(
project,
params: {
occurrence_id: occurrence_id
}
).execute
end end
record_metrics(result) record_metrics(result)
result result
......
...@@ -65,6 +65,13 @@ module Vulnerability ...@@ -65,6 +65,13 @@ module Vulnerability
has_many :notes, as: :noteable has_many :notes, as: :noteable
has_many :user_mentions, class_name: 'VulnerabilityUserMention' has_many :user_mentions, class_name: 'VulnerabilityUserMention'
has_many :sbom_occurrences_vulnerabilities,
class_name: 'Sbom::OccurrencesVulnerability',
foreign_key: :vulnerability_id,
inverse_of: :vulnerability
has_many :sbom_occurrences, through: :sbom_occurrences_vulnerabilities, class_name: 'Sbom::Occurrence', source: :occurrence
enum state: ::Enums::Vulnerability.vulnerability_states enum state: ::Enums::Vulnerability.vulnerability_states
enum severity: ::Enums::Vulnerability.severity_levels, _prefix: :severity enum severity: ::Enums::Vulnerability.severity_levels, _prefix: :severity
enum confidence: ::Enums::Vulnerability.confidence_levels, _prefix: :confidence enum confidence: ::Enums::Vulnerability.confidence_levels, _prefix: :confidence
...@@ -288,6 +295,26 @@ def has_mr_or_issue_updated_after?(date) ...@@ -288,6 +295,26 @@ def has_mr_or_issue_updated_after?(date)
(merge_requests + related_issues).any? { |mr_or_issue| mr_or_issue.updated_at >= date } (merge_requests + related_issues).any? { |mr_or_issue| mr_or_issue.updated_at >= date }
end end
def dependency_paths
return unless report_type == 'dependency_scanning'
return [] if sbom_occurrences.empty?
all_paths = []
sbom_occurrences.each do |occurrence|
# Each occurrence gives an array of paths
# Flattening all paths into one array
all_paths += ::Sbom::DependencyPathsFinder.new(
project,
params: {
occurrence_id: occurrence.id
}
).execute
end
all_paths
end
private private
def discussions_for_summary def discussions_for_summary
......
# frozen_string_literal: true
module Sbom
class DependencyPathEntity < Grape::Entity
expose :path
expose :is_cyclic
expose :max_depth_reached
end
end
...@@ -20,4 +20,6 @@ class VulnerabilityEntity < Grape::Entity ...@@ -20,4 +20,6 @@ class VulnerabilityEntity < Grape::Entity
expose :issue_links, using: Vulnerabilities::IssueLinkEntity expose :issue_links, using: Vulnerabilities::IssueLinkEntity
expose :merge_request_links, using: Vulnerabilities::MergeRequestLinkEntity expose :merge_request_links, using: Vulnerabilities::MergeRequestLinkEntity
expose :created_at, as: :detected_at expose :created_at, as: :detected_at
expose :dependency_paths, using: Sbom::DependencyPathEntity
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sbom::DependencyPathsFinder, feature_category: :dependency_management do
let_it_be(:project) { create(:project) }
subject(:dependency_paths) { described_class.new(project, params: params).execute }
describe "#execute" do
let_it_be(:component_1) { create(:sbom_component, name: "activestorage") }
let_it_be(:component_version_1) { create(:sbom_component_version, component: component_1, version: '1.2.3') }
let_it_be(:component_2) { create(:sbom_component, name: "rails") }
let_it_be(:component_version_2) { create(:sbom_component_version, component: component_2, version: '1.2.3') }
let_it_be(:occurrence) do
create(
:sbom_occurrence,
component_version: component_version_1,
component: component_1,
project: project,
ancestors: [{ name: component_2.name, version: component_version_2.version }]
)
end
let_it_be(:params) { { occurrence_id: occurrence.id } }
let(:result) do
[Sbom::DependencyPath.new(
id: occurrence.id,
project_id: project.id,
dependency_name: component_1.name,
full_path: [component_2.name, component_1.name],
version: [component_version_2.version, component_version_1.version],
is_cyclic: false,
max_depth_reached: false
)]
end
it "calls find on Sbom::DependencyPath" do
expect(Sbom::DependencyPath).to receive(:find).with(occurrence_id: params[:occurrence_id],
project_id: project.id).and_return(result)
is_expected.to eq(result)
end
end
end
...@@ -62,17 +62,17 @@ ...@@ -62,17 +62,17 @@
)] )]
end end
it 'returns data from DependencyPath.find' do it 'returns data from DependencyPathFinder' do
expect(::Sbom::DependencyPath).to receive(:find) expect_next_instance_of(::Sbom::DependencyPathsFinder) do |finder|
.with(occurrence_id: occurrence.id.to_s, project_id: project.id) expect(finder).to receive(:execute).and_return(result)
.and_return(result) end
is_expected.to eq(result) is_expected.to eq(result)
end end
it 'records execution time' do it 'records execution time' do
expect(::Sbom::DependencyPath).to receive(:find) expect_next_instance_of(::Sbom::DependencyPathsFinder) do |finder|
.with(occurrence_id: occurrence.id.to_s, project_id: project.id) expect(finder).to receive(:execute).and_return(result)
.and_return(result) end
expect(Gitlab::Metrics).to receive(:measure) expect(Gitlab::Metrics).to receive(:measure)
.with(:dependency_path_cte) .with(:dependency_path_cte)
.and_call_original .and_call_original
...@@ -81,9 +81,9 @@ ...@@ -81,9 +81,9 @@
end end
it 'records metrics' do it 'records metrics' do
expect(::Sbom::DependencyPath).to receive(:find) expect_next_instance_of(::Sbom::DependencyPathsFinder) do |finder|
.with(occurrence_id: occurrence.id.to_s, project_id: project.id) expect(finder).to receive(:execute).and_return(result)
.and_return(result) end
counter_double = instance_double(Prometheus::Client::Counter) counter_double = instance_double(Prometheus::Client::Counter)
expect(Gitlab::Metrics).to receive(:counter) expect(Gitlab::Metrics).to receive(:counter)
.with(:dependency_path_cte_paths_found, 'Count of Dependency Paths found using the recursive CTE') .with(:dependency_path_cte_paths_found, 'Count of Dependency Paths found using the recursive CTE')
......
...@@ -1231,4 +1231,114 @@ ...@@ -1231,4 +1231,114 @@
end end
end end
end end
describe '#dependency_paths' do
context "with invalid vulnerability" do
let_it_be(:vulnerability_with_invalid_report_type) { create(:vulnerability, report_type: 'sast') }
let_it_be(:vulnerability_with_no_occurrences) { create(:vulnerability, report_type: 'dependency_scanning') }
it "returns nil if report_type is not 'dependency_scanning'" do
expect(vulnerability_with_invalid_report_type.dependency_paths).to be_nil
end
it "returns empty array if there are no occurrences" do
expect(vulnerability_with_no_occurrences.dependency_paths).to eq []
end
end
context "with valid vulnerability" do
let(:vulnerability) { create(:vulnerability, report_type: 'dependency_scanning', project: project) }
let_it_be(:component_1) { create(:sbom_component, name: "activestorage") }
let_it_be(:component_version_1) { create(:sbom_component_version, component: component_1, version: '1.2.3') }
let_it_be(:component_2) { create(:sbom_component, name: "activesupport") }
let_it_be(:component_version_2) { create(:sbom_component_version, component: component_2, version: '1.2.3') }
let_it_be(:component_3) { create(:sbom_component, name: "rails") }
let_it_be(:component_version_3) { create(:sbom_component_version, component: component_3, version: '1.2.3') }
context "with a single sbom_occurrence" do
let(:occurrence) do
create(
:sbom_occurrence,
vulnerabilities: [vulnerability],
component_version: component_version_1,
component: component_1,
project: project,
ancestors: [{ name: component_3.name, version: component_version_3.version }]
)
end
before do
vulnerability.sbom_occurrences = [occurrence]
end
it "returns the dependency paths" do
expect(vulnerability.dependency_paths).to eq [
Sbom::DependencyPath.new(
id: occurrence.id,
project_id: project.id,
dependency_name: component_1.name,
full_path: [component_3.name, component_1.name],
version: [component_version_3.version, component_version_1.version],
is_cyclic: false,
max_depth_reached: false
)
]
end
end
context "with multiple sbom_occurrences" do
let(:occurrence_1) do
create(
:sbom_occurrence,
vulnerabilities: [vulnerability],
component_version: component_version_1,
component: component_1,
project: project,
ancestors: [{ name: component_3.name, version: component_version_3.version }]
)
end
let(:occurrence_2) do
create(
:sbom_occurrence,
vulnerabilities: [vulnerability],
component_version: component_version_2,
component: component_2,
project: project,
ancestors: [{ name: component_3.name, version: component_version_3.version }]
)
end
before do
vulnerability.sbom_occurrences = [occurrence_1, occurrence_2]
end
it "returns the dependency paths from multiple occurrences flattened into an array" do
expect(vulnerability.dependency_paths).to eq [
Sbom::DependencyPath.new(
id: occurrence_1.id,
project_id: project.id,
dependency_name: component_1.name,
full_path: [component_3.name, component_1.name],
version: [component_version_3.version, component_version_1.version],
is_cyclic: false,
max_depth_reached: false
),
Sbom::DependencyPath.new(
id: occurrence_2.id,
project_id: project.id,
dependency_name: component_2.name,
full_path: [component_3.name, component_2.name],
version: [component_version_3.version, component_version_2.version],
is_cyclic: false,
max_depth_reached: false
)
]
end
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sbom::DependencyPathEntity, feature_category: :dependency_management do
let(:dependency_path) do
Sbom::DependencyPath.new(
full_path: %w[ancestor_1 ancestor_2 dependency],
version: ['0.0.1', '0.0.2', '0.0.3'],
is_cyclic: false,
max_depth_reached: false
)
end
let(:entity) { described_class.new(dependency_path).as_json }
it "returns the required attributes" do
expect(entity).to include(:path, :is_cyclic, :max_depth_reached)
expect(entity[:path]).to eq([
{ name: 'ancestor_1', version: '0.0.1' },
{ name: 'ancestor_2', version: '0.0.2' },
{ name: 'dependency', version: '0.0.3' }
])
end
end
...@@ -1212,6 +1212,8 @@ vulnerabilities: ...@@ -1212,6 +1212,8 @@ vulnerabilities:
- state_transitions - state_transitions
- notes - notes
- user_mentions - user_mentions
- sbom_occurrences_vulnerabilities
- sbom_occurrences
vulnerability_finding: vulnerability_finding:
- scanner - scanner
- primary_identifier - primary_identifier
......
...@@ -75,3 +75,4 @@ ...@@ -75,3 +75,4 @@
- UserGroupsCounter - UserGroupsCounter
- Ai::FeatureSettings::FeatureSettingFinder - Ai::FeatureSettings::FeatureSettingFinder
- Autocomplete::VulnerabilitiesAutocompleteFinder - Autocomplete::VulnerabilitiesAutocompleteFinder
- Sbom::DependencyPathsFinder
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册