Commit f977d2d6 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '14732-merge-dependencies-url' into 'master'

Merge dependency path to the Licenses List response

See merge request gitlab-org/gitlab!18281
parents 5a5fd1c4 db94c80b
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class LicenseEntity < Grape::Entity class LicenseEntity < Grape::Entity
class ComponentEntity < Grape::Entity class ComponentEntity < Grape::Entity
expose :name expose :name
expose :path, as: :blob_path
end end
expose :name expose :name
......
...@@ -8,11 +8,20 @@ module Security ...@@ -8,11 +8,20 @@ module Security
end end
def execute def execute
pipeline.license_scanning_report.licenses report.merge_dependencies_info!(dependencies) if dependencies.any?
report.licenses
end end
private private
attr_reader :pipeline attr_reader :pipeline
def dependencies
@dependencies ||= pipeline.dependency_list_report.dependencies_with_licenses
end
def report
@report ||= pipeline.license_scanning_report
end
end end
end end
...@@ -23,6 +23,10 @@ module Gitlab ...@@ -23,6 +23,10 @@ module Gitlab
dependency[:licenses].push(name: license.name, url: license.url) dependency[:licenses].push(name: license.name, url: license.url)
end end
end end
def dependencies_with_licenses
dependencies.select { |dependency| dependency[:licenses].any? }
end
end end
end end
end end
......
...@@ -5,6 +5,7 @@ module Gitlab ...@@ -5,6 +5,7 @@ module Gitlab
module Reports module Reports
module LicenseScanning module LicenseScanning
class Dependency class Dependency
attr_accessor :path
attr_reader :name attr_reader :name
def initialize(name) def initialize(name)
......
...@@ -33,6 +33,28 @@ module Gitlab ...@@ -33,6 +33,28 @@ module Gitlab
found_licenses[license.canonical_id] ||= license found_licenses[license.canonical_id] ||= license
end end
def by_license_name(name)
licenses.find { |license| license.name == name }
end
def merge_dependencies_info!(dependencies_with_licenses)
return if found_licenses.empty?
found_licenses.values.each do |license|
matched_dependencies = dependencies_with_licenses.select do |dependency|
dependency[:licenses].map { |l| l[:name] }.include?(license.name)
end
matched_dependencies.each do |dependency|
license_dependency = license.dependencies.find { |l_dependency| l_dependency.name == dependency[:name] }
next unless license_dependency
license_dependency.path = dependency.dig(:location, :blob_path)
end
end
end
def violates?(software_license_policies) def violates?(software_license_policies)
policies_with_matching_license_name = software_license_policies.blacklisted.with_license_by_name(license_names) policies_with_matching_license_name = software_license_policies.blacklisted.with_license_by_name(license_names)
policies_with_matching_spdx_id = software_license_policies.blacklisted.by_spdx(licenses.map(&:id).compact) policies_with_matching_spdx_id = software_license_policies.blacklisted.by_spdx(licenses.map(&:id).compact)
......
...@@ -2,17 +2,21 @@ ...@@ -2,17 +2,21 @@
FactoryBot.define do FactoryBot.define do
factory :dependency, class: Hash do factory :dependency, class: Hash do
name { 'nokogiri' } sequence(:name) { |n| "library#{n}" }
packager { 'Ruby (Bundler)' } packager { 'Ruby (Bundler)' }
version { '1.8.0' } version { '1.8.0' }
licenses { [] } licenses { [] }
location do sequence(:location) do |n|
{ {
blob_path: '/some_project/path/Gemfile.lock', blob_path: "/some_project/path/File_#{n}.lock",
path: 'Gemfile.lock' path: "File_#{n}.lock"
} }
end end
trait :nokogiri do
name { 'nokogiri' }
end
trait :with_vulnerabilities do trait :with_vulnerabilities do
vulnerabilities do vulnerabilities do
[{ [{
......
...@@ -26,6 +26,9 @@ ...@@ -26,6 +26,9 @@
"properties": { "properties": {
"name": { "name": {
"type": "string" "type": "string"
},
"blob_path": {
"type": "string"
} }
} }
} }
......
...@@ -55,7 +55,7 @@ describe Gitlab::Ci::Parsers::Security::DependencyList do ...@@ -55,7 +55,7 @@ describe Gitlab::Ci::Parsers::Security::DependencyList do
describe '#parse_licenses!' do describe '#parse_licenses!' do
let(:artifact) { create(:ee_ci_job_artifact, :license_management) } let(:artifact) { create(:ee_ci_job_artifact, :license_management) }
let(:dependency_info) { build(:dependency, :with_vulnerabilities) } let(:dependency_info) { build(:dependency, :nokogiri, :with_vulnerabilities) }
before do before do
report.add_dependency(dependency) report.add_dependency(dependency)
......
...@@ -57,4 +57,41 @@ describe Gitlab::Ci::Reports::DependencyList::Report do ...@@ -57,4 +57,41 @@ describe Gitlab::Ci::Reports::DependencyList::Report do
end end
end end
end end
describe '#dependencies_with_licenses' do
subject { report.dependencies_with_licenses }
context 'with found dependencies' do
let(:plain_dependency) { build :dependency }
before do
report.add_dependency(plain_dependency)
end
context 'with existing license' do
let(:dependency) { build :dependency, :with_licenses }
before do
report.add_dependency(dependency)
end
it 'returns only dependency with license' do
expect(subject.size).to eq(1)
expect(subject.first).to eq(dependency)
end
end
context 'without existing license' do
it 'returns empty array' do
expect(subject).to be_empty
end
end
end
context 'without found dependencies' do
it 'returns empty array' do
expect(subject).to be_empty
end
end
end
end end
...@@ -3,19 +3,92 @@ ...@@ -3,19 +3,92 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Reports::LicenseScanning::Report do describe Gitlab::Ci::Reports::LicenseScanning::Report do
subject { build(:ci_reports_license_scanning_report, :mit) } include LicenseScanningReportHelpers
describe '#by_license_name' do
subject { report.by_license_name(name) }
let(:report) { build(:ci_reports_license_scanning_report, :report_2) }
context 'with existing license' do
let(:name) { 'MIT' }
it 'finds right name' do
is_expected.to be_a(Gitlab::Ci::Reports::LicenseScanning::License)
expect(subject.name).to eq('MIT')
end
end
context 'without existing license' do
let(:name) { 'TIM' }
it { is_expected.to be_nil }
end
end
describe '#merge_dependencies_info!' do
subject { report.merge_dependencies_info!(dependencies) }
let(:report) { build(:ci_reports_license_scanning_report, :report_2) }
let(:dependency_list_report) { Gitlab::Ci::Reports::DependencyList::Report.new }
context 'without licensed dependencies' do
let(:library1) { build(:dependency, name: 'Library1') }
let(:library3) { build(:dependency, name: 'Library3') }
let(:dependencies) { [library3, library1] }
before do
subject
end
it 'does not merge dependency path' do
paths = all_dependency_paths(report)
expect(paths).to be_empty
end
end
context 'with licensed dependencies' do
let(:library1) { build(:dependency, :with_licenses, name: 'Library1') }
let(:library3) { build(:dependency, :with_licenses, name: 'Library3') }
let(:library4) { build(:dependency, :with_licenses, name: 'Library4') }
let(:dependencies) { [library1, library3, library4] }
let(:mit_license) { report.by_license_name('MIT') }
let(:apache_license) { report.by_license_name('Apache 2.0') }
before do
mit_license.add_dependency('Library4')
apache_license.add_dependency('Library3')
subject
end
it 'merge path to matched dependencies' do
dep1 = dependency_by_name(mit_license, 'Library1')
dep4 = dependency_by_name(mit_license, 'Library4')
dep3 = dependency_by_name(apache_license, 'Library3')
expect(dep1.path).to eq(library1.dig(:location, :blob_path))
expect(dep4.path).to eq(library4.dig(:location, :blob_path))
expect(dep3.path).to be_nil
end
end
end
describe '#violates?' do describe '#violates?' do
subject { report.violates?(project.software_license_policies) }
let(:project) { create(:project) } let(:project) { create(:project) }
context "when checking for violations using v1 license scan report" do context "when checking for violations using v1 license scan report" do
subject { build(:license_scan_report) } let(:report) { build(:license_scan_report) }
let(:mit_license) { build(:software_license, :mit, spdx_identifier: nil) } let(:mit_license) { build(:software_license, :mit, spdx_identifier: nil) }
let(:apache_license) { build(:software_license, :apache_2_0, spdx_identifier: nil) } let(:apache_license) { build(:software_license, :apache_2_0, spdx_identifier: nil) }
before do before do
subject report
.add_license(id: nil, name: 'MIT') .add_license(id: nil, name: 'MIT')
.add_dependency('rails') .add_dependency('rails')
end end
...@@ -27,7 +100,7 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -27,7 +100,7 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
project.software_license_policies << mit_blacklist project.software_license_policies << mit_blacklist
end end
specify { expect(subject.violates?(project.software_license_policies)).to be(true) } it { is_expected.to be_truthy }
end end
context 'when a blacklisted license is discovered with a different casing for the name' do context 'when a blacklisted license is discovered with a different casing for the name' do
...@@ -38,7 +111,7 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -38,7 +111,7 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
project.software_license_policies << mit_blacklist project.software_license_policies << mit_blacklist
end end
specify { expect(subject.violates?(project.software_license_policies)).to be(true) } it { is_expected.to be_truthy }
end end
context 'when none of the licenses discovered in the report violate the blacklist policy' do context 'when none of the licenses discovered in the report violate the blacklist policy' do
...@@ -48,12 +121,12 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -48,12 +121,12 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
project.software_license_policies << apache_blacklist project.software_license_policies << apache_blacklist
end end
specify { expect(subject.violates?(project.software_license_policies)).to be(false) } it { is_expected.to be_falsey }
end end
end end
context "when checking for violations using the v2 license scan reports" do context "when checking for violations using the v2 license scan reports" do
subject { build(:license_scan_report) } let(:report) { build(:license_scan_report) }
context "when a blacklisted license with a SPDX identifier is also in the report" do context "when a blacklisted license with a SPDX identifier is also in the report" do
let(:mit_spdx_id) { 'MIT' } let(:mit_spdx_id) { 'MIT' }
...@@ -61,11 +134,11 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -61,11 +134,11 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
let(:mit_policy) { build(:software_license_policy, :blacklist, software_license: mit_license) } let(:mit_policy) { build(:software_license_policy, :blacklist, software_license: mit_license) }
before do before do
subject.add_license(id: mit_spdx_id, name: 'MIT License') report.add_license(id: mit_spdx_id, name: 'MIT License')
project.software_license_policies << mit_policy project.software_license_policies << mit_policy
end end
specify { expect(subject.violates?(project.software_license_policies)).to be(true) } it { is_expected.to be_truthy }
end end
context "when a blacklisted license does not have an SPDX identifier because it was provided by an end user" do context "when a blacklisted license does not have an SPDX identifier because it was provided by an end user" do
...@@ -73,11 +146,11 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -73,11 +146,11 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
let(:custom_policy) { build(:software_license_policy, :blacklist, software_license: custom_license) } let(:custom_policy) { build(:software_license_policy, :blacklist, software_license: custom_license) }
before do before do
subject.add_license(id: nil, name: 'Custom') report.add_license(id: nil, name: 'Custom')
project.software_license_policies << custom_policy project.software_license_policies << custom_policy
end end
specify { expect(subject.violates?(project.software_license_policies)).to be(true) } it { is_expected.to be_truthy }
end end
context "when none of the licenses discovered match any of the blacklisted software policies" do context "when none of the licenses discovered match any of the blacklisted software policies" do
...@@ -85,12 +158,12 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -85,12 +158,12 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
let(:apache_policy) { build(:software_license_policy, :blacklist, software_license: apache_license) } let(:apache_policy) { build(:software_license_policy, :blacklist, software_license: apache_license) }
before do before do
subject.add_license(id: nil, name: 'Custom') report.add_license(id: nil, name: 'Custom')
subject.add_license(id: 'MIT', name: 'MIT License') report.add_license(id: 'MIT', name: 'MIT License')
project.software_license_policies << apache_policy project.software_license_policies << apache_policy
end end
specify { expect(subject.violates?(project.software_license_policies)).to be(false) } it { is_expected.to be_falsey }
end end
end end
end end
......
...@@ -267,7 +267,7 @@ describe Ci::Build do ...@@ -267,7 +267,7 @@ describe Ci::Build do
describe '#collect_licenses_for_dependency_list!' do describe '#collect_licenses_for_dependency_list!' do
let!(:lm_artifact) { create(:ee_ci_job_artifact, :license_management, job: job, project: job.project) } let!(:lm_artifact) { create(:ee_ci_job_artifact, :license_management, job: job, project: job.project) }
let(:dependency_list_report) { Gitlab::Ci::Reports::DependencyList::Report.new } let(:dependency_list_report) { Gitlab::Ci::Reports::DependencyList::Report.new }
let(:dependency) { build(:dependency) } let(:dependency) { build(:dependency, :nokogiri) }
subject { job.collect_licenses_for_dependency_list!(dependency_list_report) } subject { job.collect_licenses_for_dependency_list!(dependency_list_report) }
......
...@@ -32,24 +32,24 @@ describe DependencyEntity do ...@@ -32,24 +32,24 @@ describe DependencyEntity do
end end
context 'with reporter' do context 'with reporter' do
let(:dependency_info) { build(:dependency, :with_licenses) }
before do before do
project.add_reporter(user) project.add_reporter(user)
end end
it { is_expected.to eq(dependency_info) } it 'includes license info and not vulnerabilities' do
is_expected.to eq(dependency.except(:vulnerabilities))
end
end end
end end
context 'when all required features are unavailable' do context 'when all required features are unavailable' do
let(:dependency_info) { build(:dependency).except(:licenses) }
before do before do
project.add_developer(user) project.add_developer(user)
end end
it { is_expected.to eq(dependency_info) } it 'does not include licenses and vulnerabilities' do
is_expected.to eq(dependency.except(:vulnerabilities, :licenses))
end
end end
end end
end end
...@@ -12,10 +12,17 @@ describe LicenseEntity do ...@@ -12,10 +12,17 @@ describe LicenseEntity do
{ {
name: 'MIT', name: 'MIT',
url: 'https://opensource.org/licenses/mit', url: 'https://opensource.org/licenses/mit',
components: [{ name: 'rails' }] components: [{
name: 'rails',
blob_path: 'some_path'
}]
} }
end end
before do
license.dependencies.first.path = 'some_path'
end
it { is_expected.to eq(assert_license) } it { is_expected.to eq(assert_license) }
end end
end end
...@@ -3,17 +3,43 @@ ...@@ -3,17 +3,43 @@
require 'spec_helper' require 'spec_helper'
describe Security::LicensesListService do describe Security::LicensesListService do
describe '#execute' do include LicenseScanningReportHelpers
let!(:pipeline) { create(:ee_ci_pipeline, :with_license_management_report) }
describe '#execute' do
subject { described_class.new(pipeline: pipeline).execute } subject { described_class.new(pipeline: pipeline).execute }
let!(:pipeline) { create(:ee_ci_pipeline, :with_license_management_report) }
let(:project) { pipeline.project }
let(:mit_license) { find_license_by_name(subject, 'MIT') }
before do before do
stub_licensed_features(license_management: true) stub_licensed_features(license_management: true, dependency_list: true)
end
context 'with matching dependency list' do
let!(:build) { create(:ci_build, :success, name: 'dependency_list', pipeline: pipeline, project: project) }
let!(:artifact) { create(:ee_ci_job_artifact, :dependency_list, job: build, project: project) }
it 'merges dependency location for found dependencies' do
nokogiri = dependency_by_name(mit_license, 'nokogiri')
actioncable = dependency_by_name(mit_license, 'actioncable')
nokogiri_path = "/#{project.full_path}/blob/#{pipeline.sha}/rails/Gemfile.lock"
expect(nokogiri.path).to eq(nokogiri_path)
expect(actioncable.path).to be_nil
end
end end
context 'without matching dependency list' do
it 'returns array of Licenses' do it 'returns array of Licenses' do
is_expected.to be_an(Array) is_expected.to be_an(Array)
end end
it 'returns empty path in dependencies' do
nokogiri = dependency_by_name(mit_license, 'nokogiri')
expect(nokogiri.path).to be_nil
end
end
end end
end end
# frozen_string_literal: true
module LicenseScanningReportHelpers
def all_dependency_paths(report)
report.licenses.map { |license| license.dependencies.map(&:path) }.flatten.compact
end
def dependency_by_name(license, name)
license.dependencies.find { |dep| dep.name == name }
end
def find_license_by_name(licenses, name)
licenses.find { |license| license.name == name }
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