Commit 15c84f5e authored by mo khan's avatar mo khan Committed by Douglas Barbosa Alexandre

Allow parsing v2 license scan reports

* Updates parser to parse v1, v1.1 and v2 license scan reports
* Update license scan report diff to use license id when available
parent 9f416afd
---
title: Parse v2 license scanning reports
merge_request: 17646
author:
type: changed
...@@ -6,53 +6,21 @@ module Gitlab ...@@ -6,53 +6,21 @@ module Gitlab
module LicenseCompliance module LicenseCompliance
class LicenseScanning class LicenseScanning
LicenseScanningParserError = Class.new(Gitlab::Ci::Parsers::ParserError) LicenseScanningParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
DEFAULT_VERSION = '1.0'
PARSERS = { '1' => V1, '2' => V2 }.freeze
def parse!(json_data, license_scanning_report) def parse!(json_data, report)
root = JSON.parse(json_data) json = JSON.parse(json_data, symbolize_names: true)
report.version = json[:version].presence || DEFAULT_VERSION
root['licenses'].each do |license_hash| parser = PARSERS.fetch(report.major_version)
license_expression = license_hash['name'] parser.new(report).parse(json)
# Extract licenses from the license_expression as it can contain comas.
each_license(license_expression) do |license_name|
license_dependencies = root['dependencies'].select do |dependency|
uses_license?(dependency['license']['name'], license_name)
end
license_dependencies.each do |dependency|
license_scanning_report.add_dependency(license_name,
license_hash['count'],
dependency['license']['url'],
dependency['dependency']['name'])
end
end
end
rescue JSON::ParserError rescue JSON::ParserError
raise LicenseScanningParserError, 'JSON parsing failed' raise LicenseScanningParserError, 'JSON parsing failed'
rescue => e rescue => e
Gitlab::Sentry.track_exception(e) Gitlab::Sentry.track_exception(e)
raise LicenseScanningParserError, 'License scanning report parsing failed' raise LicenseScanningParserError, 'License scanning report parsing failed'
end end
def remove_suffix(name)
name.gsub(/-or-later$|-only$|\+$/, '')
end
def expression_to_list(expression)
expression.split(',').map(&:strip).map { |name| remove_suffix(name) }
end
# Split the license expression when it is separated by spaces. Removes suffixes
# specified in https://spdx.org/ids-how
def each_license(expression)
expression_to_list(expression).each do |license_name|
yield(license_name)
end
end
# Check that the license expression uses the given license name
def uses_license?(expression, name)
expression_to_list(expression).any? { |name1| name1.casecmp(remove_suffix(name)) == 0 }
end
end end
end end
end end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Parsers
module LicenseCompliance
class V1
attr_reader :report
def initialize(report)
@report = report
end
def parse(json)
json.fetch(:dependencies, []).each do |dependency|
each_license_for(dependency) do |license_hash|
license = report.add_license(id: nil, name: license_hash[:name], url: license_hash[:url])
license.add_dependency(dependency[:dependency][:name])
end
end
end
private
def each_license_for(dependency)
if dependency.key?(:licenses)
dependency[:licenses].each do |license|
yield license
end
else
dependency[:license][:name].split(',').each do |name|
yield(name: remove_suffix(name.strip), url: dependency.dig(:license, :url))
end
end
end
def remove_suffix(name)
name.gsub(/-or-later$|-only$|\+$/, '')
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Parsers
module LicenseCompliance
class V2
attr_reader :report
def initialize(report)
@report = report
end
def parse(report_hash)
add_licenses(report_hash)
add_dependencies(report_hash)
end
private
def add_licenses(report_hash)
report_hash[:licenses].map do |license_hash|
report.add_license(id: license_hash[:id], name: license_hash[:name], url: license_hash[:url])
end
end
def add_dependencies(report_hash)
report_hash[:dependencies].each do |dependency_hash|
dependency_hash[:licenses].map do |license_id|
license_for(license_id).add_dependency(dependency_hash[:name])
end
end
end
def license_for(license_id)
report.fetch(license_id) do |_key|
report.add_license(id: license_id, name: 'unknown')
end
end
end
end
end
end
end
...@@ -10,6 +10,14 @@ module Gitlab ...@@ -10,6 +10,14 @@ module Gitlab
def initialize(name) def initialize(name)
@name = name @name = name
end end
def hash
name.hash
end
def eql?(other)
self.name == other.name
end
end end
end end
end end
......
...@@ -5,15 +5,25 @@ module Gitlab ...@@ -5,15 +5,25 @@ module Gitlab
module Reports module Reports
module LicenseScanning module LicenseScanning
class License class License
attr_reader :name, :url, :count attr_reader :id, :name, :url
def initialize(name, count, url) delegate :count, to: :dependencies
def initialize(id:, name:, url:)
@id = 'unknown' == id ? nil : id
@name = name @name = name
@count = count
@url = url @url = url
@dependencies = Set.new @dependencies = Set.new
end end
def canonical_id
id || name&.downcase
end
def hash
canonical_id.hash
end
def add_dependency(name) def add_dependency(name)
@dependencies.add(::Gitlab::Ci::Reports::LicenseScanning::Dependency.new(name)) @dependencies.add(::Gitlab::Ci::Reports::LicenseScanning::Dependency.new(name))
end end
...@@ -21,6 +31,12 @@ module Gitlab ...@@ -21,6 +31,12 @@ module Gitlab
def dependencies def dependencies
@dependencies.to_a @dependencies.to_a
end end
def eql?(other)
super(other) ||
(id && other.id && id.eql?(other.id)) ||
(name && other.name && name.casecmp?(other.name))
end
end end
end end
end end
......
...@@ -5,12 +5,18 @@ module Gitlab ...@@ -5,12 +5,18 @@ module Gitlab
module Reports module Reports
module LicenseScanning module LicenseScanning
class Report class Report
attr_reader :found_licenses delegate :empty?, :fetch, to: :found_licenses
attr_accessor :version
def initialize def initialize(version: '1.0')
@version = version
@found_licenses = {} @found_licenses = {}
end end
def major_version
version.split('.')[0]
end
def licenses def licenses
found_licenses.values.sort_by { |license| license.name.downcase } found_licenses.values.sort_by { |license| license.name.downcase }
end end
...@@ -19,10 +25,12 @@ module Gitlab ...@@ -19,10 +25,12 @@ module Gitlab
found_licenses.values.map(&:name) found_licenses.values.map(&:name)
end end
def add_dependency(license_name, license_count, license_url, dependency_name) def add_license(id:, name:, url: '')
key = license_name.upcase add(::Gitlab::Ci::Reports::LicenseScanning::License.new(id: id, name: name, url: url))
found_licenses[key] ||= ::Gitlab::Ci::Reports::LicenseScanning::License.new(license_name, license_count, license_url) end
found_licenses[key].add_dependency(dependency_name)
def add(license)
found_licenses[license.canonical_id] ||= license
end end
def violates?(software_license_policies) def violates?(software_license_policies)
...@@ -30,25 +38,16 @@ module Gitlab ...@@ -30,25 +38,16 @@ module Gitlab
end end
def diff_with(other_report) def diff_with(other_report)
base = self.license_names.map { |name| canonicalize(name) } base = self.licenses
head = other_report.license_names.map { |name| canonicalize(name) } head = other_report.licenses
{ {
added: other_report.find_by_names(head - base), added: (head - base),
unchanged: find_by_names(base & head), unchanged: (base & head),
removed: find_by_names(base - head) removed: (base - head)
} }
end end
def find_by_names(names)
names = names.map { |name| canonicalize(name) }
licenses.select { |license| names.include?(canonicalize(license.name)) }
end
def empty?
found_licenses.empty?
end
def self.parse_from(json) def self.parse_from(json)
new.tap do |report| new.tap do |report|
::Gitlab::Ci::Parsers::LicenseCompliance::LicenseScanning.new.parse!(json, report) ::Gitlab::Ci::Parsers::LicenseCompliance::LicenseScanning.new.parse!(json, report)
...@@ -57,9 +56,7 @@ module Gitlab ...@@ -57,9 +56,7 @@ module Gitlab
private private
def canonicalize(name) attr_reader :found_licenses
name.downcase
end
end end
end end
end end
......
...@@ -291,7 +291,7 @@ describe Projects::PipelinesController do ...@@ -291,7 +291,7 @@ describe Projects::PipelinesController do
it 'will return mit license approved status' do it 'will return mit license approved status' do
payload_mit = payload.find { |l| l['name'] == 'MIT' } payload_mit = payload.find { |l| l['name'] == 'MIT' }
expect(payload_mit['count']).to eq(pipeline.license_scanning_report.found_licenses['MIT'].count) expect(payload_mit['count']).to eq(pipeline.license_scanning_report.licenses.find { |x| x.name == 'MIT' }.count)
expect(payload_mit['url']).to eq('http://opensource.org/licenses/mit-license') expect(payload_mit['url']).to eq('http://opensource.org/licenses/mit-license')
expect(payload_mit['classification']['approval_status']).to eq('approved') expect(payload_mit['classification']['approval_status']).to eq('approved')
end end
......
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :ci_reports_license_scanning_report, class: ::Gitlab::Ci::Reports::LicenseScanning::Report do factory :ci_reports_license_scanning_report, class: ::Gitlab::Ci::Reports::LicenseScanning::Report, aliases: [:license_scan_report] do
trait :version_1 do
version { '1.0' }
end
trait :version_2 do
version { '2.0' }
end
trait :report_1 do trait :report_1 do
after(:build) do |report, evaluator| after(:build) do |report, evaluator|
report.add_dependency('MIT', 1, 'https://opensource.org/licenses/mit', 'Library1') report.add_license(id: 'MIT', name: 'MIT', url: 'https://opensource.org/licenses/mit').add_dependency('Library1')
report.add_dependency('WTFPL', 1, 'https://opensource.org/licenses/wtfpl', 'Library2') report.add_license(id: 'WTFPL', name: 'WTFPL', url: 'https://opensource.org/licenses/wtfpl').add_dependency('Library2')
end end
end end
trait :report_2 do trait :report_2 do
after(:build) do |report, evaluator| after(:build) do |report, evaluator|
report.add_dependency('MIT', 1, 'https://opensource.org/licenses/mit', 'Library1') report.add_license(id: 'MIT', name: 'MIT', url: 'https://opensource.org/licenses/mit').add_dependency('Library1')
report.add_dependency('Apache 2.0', 1, 'https://opensource.org/licenses/apache', 'Library3') report.add_license(id: 'Apache-2.0', name: 'Apache 2.0', url: 'https://opensource.org/licenses/apache').add_dependency('Library3')
end end
end end
trait :mit do trait :mit do
after(:build) do |report, evaluator| after(:build) do |report, evaluator|
report.add_dependency('MIT', 1, 'https://opensource.org/licenses/mit', 'rails') report.add_license(id: 'MIT', name: 'MIT', url: 'https://opensource.org/licenses/mit').add_dependency('rails')
end end
end end
end end
......
{
"version": "1.1",
"licenses": [
{
"count": 2,
"name": "MIT"
},
{
"count": 2,
"name": "BSD"
},
{
"count": 1,
"name": "unknown"
}
],
"dependencies": [
{
"licenses": [
{
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
}
],
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "a",
"url": "https://example.org/a",
"description": "Dependency A.",
"pathes": [ "." ]
}
},
{
"licenses": [
{
"name": "BSD",
"url": "http://spdx.org/licenses/BSD-3-Clause.json"
}
],
"license": {
"name": "BSD",
"url": "http://spdx.org/licenses/BSD-3-Clause.json"
},
"dependency": {
"name": "b",
"url": "https://example.org/b",
"description": "Dependency B.",
"pathes": [ "." ]
}
},
{
"licenses": [
{
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
{
"name": "BSD",
"url": "http://spdx.org/licenses/BSD-3-Clause.json"
}
],
"license": {
"name": "MIT, BSD",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "c",
"url": "https://example.org/c",
"description": "Dependency C.",
"pathes": [ "." ]
}
},
{
"licenses": [
{
"name": "unknown",
"url": ""
}
],
"license": {
"name": "unknown"
},
"dependency": {
"name": "d",
"url": "https://example.org/d",
"description": "Dependency D.",
"pathes": [ "." ]
}
}
]
}
{
"version": "2.0",
"licenses": [
{
"id": "BSD-3-Clause",
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
"url": "http://spdx.org/licenses/BSD-3-Clause.json",
"count": 2
},
{
"id": "MIT",
"name": "MIT License",
"url": "http://spdx.org/licenses/MIT.json",
"count": 2
},
{
"id": "unknown",
"name": "unknown",
"url": "",
"count": 1
}
],
"dependencies": [
{
"name": "a",
"url": "https://example.org/a",
"description": "Dependency A.",
"paths": ["."],
"licenses": ["MIT"]
},
{
"name": "b",
"url": "https://example.com/b",
"description": "Dependency B.",
"paths": [ "." ],
"licenses": [ "BSD-3-Clause" ]
},
{
"name": "c",
"url": "https://example.org/c",
"description": "Dependency C.",
"paths": [ "." ],
"licenses": [ "MIT", "BSD-3-Clause" ]
},
{
"name": "d",
"url": "https://example.org/d",
"description": "Dependency D.",
"paths": [ "." ],
"licenses": [ "unknown" ]
}
]
}
...@@ -4,17 +4,169 @@ require 'spec_helper' ...@@ -4,17 +4,169 @@ require 'spec_helper'
describe Gitlab::Ci::Parsers::LicenseCompliance::LicenseScanning do describe Gitlab::Ci::Parsers::LicenseCompliance::LicenseScanning do
describe '#parse!' do describe '#parse!' do
subject { described_class.new.parse!(data, report) }
let(:report) { Gitlab::Ci::Reports::LicenseScanning::Report.new } let(:report) { Gitlab::Ci::Reports::LicenseScanning::Report.new }
context 'when data is a JSON license management report' do context 'when parsing a valid v1 report' do
let(:data) { File.read(Rails.root.join('ee/spec/fixtures/security_reports/master/gl-license-management-report.json')) } let(:v1_json) { fixture_file('security_reports/master/gl-license-management-report.json', dir: 'ee') }
before do
subject.parse!(v1_json, report)
end
it { expect(report.version).to eql('1.0') }
it { expect(report.licenses.count).to eq(4) }
it { expect(report.licenses[0].name).to eql('Apache 2.0') }
it { expect(report.licenses[0].url).to eql('http://www.apache.org/licenses/LICENSE-2.0.txt') }
it { expect(report.licenses[0].count).to be(1) }
it { expect(report.licenses[0].dependencies.count).to be(1) }
it { expect(report.licenses[0].dependencies[0].name).to eql('thread_safe') }
it { expect(report.licenses[1].name).to eql('MIT') }
it { expect(report.licenses[1].url).to eql('http://opensource.org/licenses/mit-license') }
it { expect(report.licenses[1].count).to be(52) }
it { expect(report.licenses[1].dependencies.count).to be(52) }
it { expect(report.licenses[1].dependencies[0].name).to eql('actioncable') }
it { expect(report.licenses[2].name).to eql('New BSD') }
it { expect(report.licenses[2].url).to eql('http://opensource.org/licenses/BSD-3-Clause') }
it { expect(report.licenses[2].count).to be(3) }
it { expect(report.licenses[2].dependencies.count).to be(3) }
it { expect(report.licenses[2].dependencies.map(&:name)).to contain_exactly('ffi', 'puma', 'sqlite3') }
it { expect(report.licenses[3].name).to eql('unknown') }
it { expect(report.licenses[3].url).to be_nil }
it { expect(report.licenses[3].count).to be(1) }
it { expect(report.licenses[3].dependencies.count).to be(1) }
it { expect(report.licenses[3].dependencies[0].name).to eql('ruby-bundler-rails') }
end
context 'when parsing a valid v1.1 report' do
let(:v1_1_data) { fixture_file('security_reports/gl-license-management-report-v1.1.json', dir: 'ee') }
before do
subject.parse!(v1_1_data, report)
end
it { expect(report.version).to eql('1.1') }
it { expect(report.licenses.count).to eq(3) }
it { expect(report.licenses[0].id).to be_nil }
it { expect(report.licenses[0].name).to eql('BSD') }
it { expect(report.licenses[0].url).to eql('http://spdx.org/licenses/BSD-3-Clause.json') }
it { expect(report.licenses[0].count).to be(2) }
it { expect(report.licenses[0].dependencies.count).to be(2) }
it { expect(report.licenses[0].dependencies.map(&:name)).to contain_exactly('b', 'c') }
it { expect(report.licenses[1].id).to be_nil }
it { expect(report.licenses[1].name).to eql('MIT') }
it { expect(report.licenses[1].url).to eql('http://opensource.org/licenses/mit-license') }
it { expect(report.licenses[1].count).to be(2) }
it { expect(report.licenses[1].dependencies.count).to be(2) }
it { expect(report.licenses[1].dependencies.map(&:name)).to contain_exactly('a', 'c') }
it { expect(report.licenses[2].id).to be_nil }
it { expect(report.licenses[2].name).to eql('unknown') }
it { expect(report.licenses[2].url).to eql('') }
it { expect(report.licenses[2].count).to be(1) }
it { expect(report.licenses[2].dependencies.count).to be(1) }
it { expect(report.licenses[2].dependencies.map(&:name)).to contain_exactly('d') }
end
context 'when parsing a valid v2 report' do
let(:v2_data) { fixture_file('security_reports/gl-license-management-report-v2.json', dir: 'ee') }
before do
subject.parse!(v2_data, report)
end
it { expect(report.version).to eql('2.0') }
it { expect(report.licenses.count).to eq(3) }
it { expect(report.licenses[0].id).to eql('BSD-3-Clause') }
it { expect(report.licenses[0].name).to eql('BSD 3-Clause "New" or "Revised" License') }
it { expect(report.licenses[0].url).to eql('http://spdx.org/licenses/BSD-3-Clause.json') }
it { expect(report.licenses[0].count).to be(2) }
it { expect(report.licenses[0].dependencies.count).to be(2) }
it { expect(report.licenses[0].dependencies.map(&:name)).to contain_exactly('b', 'c') }
it { expect(report.licenses[1].id).to eql('MIT') }
it { expect(report.licenses[1].name).to eql('MIT License') }
it { expect(report.licenses[1].url).to eql('http://spdx.org/licenses/MIT.json') }
it { expect(report.licenses[1].count).to be(2) }
it { expect(report.licenses[1].dependencies.count).to be(2) }
it { expect(report.licenses[1].dependencies.map(&:name)).to contain_exactly('a', 'c') }
it 'parses without error' do it { expect(report.licenses[2].id).to be_nil }
expect { subject }.not_to raise_error it { expect(report.licenses[2].name).to eql('unknown') }
it { expect(report.licenses[2].url).to eql('') }
it { expect(report.licenses[2].count).to be(1) }
it { expect(report.licenses[2].dependencies.count).to be(1) }
it { expect(report.licenses[2].dependencies.map(&:name)).to contain_exactly('d') }
end
context 'when parsing a v2 report with a missing license definition' do
let(:v2_data) do
{
version: '2.0',
licenses: [],
dependencies: [
{ name: 'saml-kit', licenses: ['MIT'] }
]
}.to_json
end
before do
subject.parse!(v2_data, report)
end
it { expect(report.licenses.count).to be(1) }
it { expect(report.licenses[0].id).to eql('MIT') }
it { expect(report.licenses[0].name).to eql('unknown') }
it { expect(report.licenses[0].dependencies.count).to be(1) }
it { expect(report.licenses[0].dependencies[0].name).to eql('saml-kit') }
end
context 'when the report version is not recognized' do
it do
expect do
subject.parse!(JSON.pretty_generate({ version: 'x' }), report)
end.to raise_error(KeyError)
end
end
context 'when the report version is missing' do
before do
subject.parse!(JSON.pretty_generate({}), report)
end
it { expect(report.version).to eq('1.0') }
it { expect(report).to be_empty }
end
context 'when the report version is nil' do
before do
subject.parse!(JSON.pretty_generate({ version: nil }), report)
end
it { expect(report.version).to eq('1.0') }
it { expect(report).to be_empty }
end
context 'when the report version is blank' do
before do
subject.parse!(JSON.pretty_generate({ version: '' }), report)
end
it { expect(report.version).to eq('1.0') }
it { expect(report).to be_empty }
end
expect(report.licenses.count).to eq(4) context 'when the report is not a valid JSON document' do
it do
expect do
subject.parse!('blah', report)
end.to raise_error(Gitlab::Ci::Parsers::LicenseCompliance::LicenseScanning::LicenseScanningParserError)
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::LicenseScanning::Dependency do
describe 'value equality' do
let(:set) { Set.new }
it 'cannot add the same dependency to a set twice' do
set.add(described_class.new('bundler'))
set.add(described_class.new('bundler'))
expect(set.count).to eq(1)
end
it { expect(described_class.new('bundler')).to eql(described_class.new('bundler')) }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Reports::LicenseScanning::License do
describe 'equality' do
let(:blank) { described_class.new(id: nil, name: nil, url: nil) }
let(:v1_mit) { described_class.new(id: nil, name: 'MIT', url: '') }
let(:v1_apache) { described_class.new(id: nil, name: 'Apache 2.0', url: '') }
let(:v2_mit) { described_class.new(id: 'MIT', name: 'MIT', url: '') }
let(:v2_apache) { described_class.new(id: 'Apache-2.0', name: 'Apache 2.0', url: '') }
describe '#eql?' do
it { expect([v1_mit, v1_apache] - [v2_mit, v2_apache]).to be_empty }
it { expect([v2_apache, v2_mit] & [v2_mit]).to match_array([v2_mit]) }
it { expect([v2_apache, v2_mit] - [v1_apache, v1_mit]).to be_empty }
it { expect([v2_apache] & [v2_mit]).to be_empty }
it { expect([v2_apache] - [v1_apache]).to be_empty }
it { expect([v2_apache] - [v1_mit]).to match_array([v2_apache]) }
it { expect(blank).not_to eql(v1_mit) }
it { expect(blank).not_to eql(v2_mit) }
it { expect(blank).to eql(blank) }
it { expect(v1_mit).not_to eql(blank) }
it { expect(v1_mit).not_to eql(v1_apache) }
it { expect(v1_mit).not_to eql(v2_apache) }
it { expect(v1_mit).to eql(v1_mit) }
it { expect(v1_mit).to eql(v2_mit) }
it { expect(v2_mit).not_to eql(blank) }
it { expect(v2_mit).not_to eql(v1_apache) }
it { expect(v2_mit).not_to eql(v2_apache) }
it { expect(v2_mit).to eql(v1_mit) }
it { expect(v2_mit).to eql(v2_mit) }
it { expect(v2_mit).to eql(described_class.new(id: v2_mit.id, name: '', url: '')) }
end
describe '#hash' do
it { expect(blank.hash).to eql(blank.dup.hash) }
it { expect(v1_mit.hash).to eql(v1_mit.dup.hash) }
it { expect(v2_mit.hash).to eql(v2_mit.dup.hash) }
end
end
describe '#canonical_id' do
context 'when the license was produced from a v1 report' do
subject { described_class.new(id: nil, name: 'MIT License', url: nil) }
it { expect(subject.canonical_id).to eql(subject.name.downcase) }
end
context 'when the license was produced from a v2 report' do
subject { described_class.new(id: 'MIT', name: 'MIT License', url: nil) }
it { expect(subject.canonical_id).to eql(subject.id) }
end
end
end
...@@ -10,17 +10,17 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -10,17 +10,17 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
let(:mit_license) { build(:software_license, :mit) } let(:mit_license) { build(:software_license, :mit) }
let(:apache_license) { build(:software_license, :apache_2_0) } let(:apache_license) { build(:software_license, :apache_2_0) }
context "when a blacklisted license is found in the report" do context 'when a blacklisted license is found in the report' do
let(:mit_blacklist) { build(:software_license_policy, :blacklist, software_license: mit_license) } let(:mit_blacklist) { build(:software_license_policy, :blacklist, software_license: mit_license) }
before do before 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 { expect(subject.violates?(project.software_license_policies)).to be(true) }
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
let(:mit_blacklist) { build(:software_license_policy, :blacklist, software_license: mit_license) } let(:mit_blacklist) { build(:software_license_policy, :blacklist, software_license: mit_license) }
before do before do
...@@ -28,40 +28,107 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -28,40 +28,107 @@ 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 { expect(subject.violates?(project.software_license_policies)).to be(true) }
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
let(:apache_blacklist) { build(:software_license_policy, :blacklist, software_license: apache_license) } let(:apache_blacklist) { build(:software_license_policy, :blacklist, software_license: apache_license) }
before do before 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 { expect(subject.violates?(project.software_license_policies)).to be(false) }
end end
end end
describe "#diff_with" do describe '#diff_with' do
let(:report_1) { build(:ci_reports_license_scanning_report, :report_1) } def names_from(licenses)
let(:report_2) { build(:ci_reports_license_scanning_report, :report_2) } licenses.map(&:name)
subject { report_1.diff_with(report_2) } end
context 'when diffing two v1 reports' do
let(:base_report) { build(:license_scan_report, :version_1) }
let(:head_report) { build(:license_scan_report, :version_1) }
subject { base_report.diff_with(head_report) }
before do
base_report.add_license(id: nil, name: 'MIT').add_dependency('Library1')
base_report.add_license(id: nil, name: 'BSD').add_dependency('Library1')
base_report.add_license(id: nil, name: 'WTFPL').add_dependency('Library2')
head_report.add_license(id: nil, name: 'MIT').add_dependency('Library1')
head_report.add_license(id: nil, name: 'Apache 2.0').add_dependency('Library3')
head_report.add_license(id: nil, name: 'bsd').add_dependency('Library1')
end
before do it { expect(names_from(subject[:added])).to contain_exactly('Apache 2.0') }
report_1.add_dependency('BSD', 1, 'https://opensource.org/licenses/0BSD', 'Library1') it { expect(names_from(subject[:unchanged])).to contain_exactly('MIT', 'BSD') }
report_2.add_dependency('bsd', 1, 'https://opensource.org/licenses/0BSD', 'Library1') it { expect(names_from(subject[:removed])).to contain_exactly('WTFPL') }
end end
def names_from(licenses) context 'when diffing two v2 reports' do
licenses.map(&:name) let(:base_report) { build(:license_scan_report, :version_2) }
let(:head_report) { build(:license_scan_report, :version_2) }
subject { base_report.diff_with(head_report) }
before do
base_report.add_license(id: 'MIT', name: 'MIT').add_dependency('Library1')
base_report.add_license(id: 'BSD-3-Clause', name: 'BSD').add_dependency('Library1')
base_report.add_license(id: 'WTFPL', name: 'WTFPL').add_dependency('Library2')
head_report.add_license(id: 'BSD-3-Clause', name: 'bsd').add_dependency('Library1')
head_report.add_license(id: 'Apache-2.0', name: 'Apache 2.0').add_dependency('Library3')
head_report.add_license(id: 'MIT', name: 'MIT License').add_dependency('Library1')
end
it { expect(names_from(subject[:added])).to contain_exactly('Apache 2.0') }
it { expect(names_from(subject[:unchanged])).to contain_exactly('MIT', 'BSD') }
it { expect(names_from(subject[:removed])).to contain_exactly('WTFPL') }
end end
it { expect(names_from(subject[:added])).to contain_exactly('Apache 2.0') } context 'when diffing a v1 report with a v2 report' do
it { expect(names_from(subject[:unchanged])).to contain_exactly('MIT', 'BSD') } let(:base_report) { build(:license_scan_report, :version_1) }
it { expect(names_from(subject[:removed])).to contain_exactly('WTFPL') } let(:head_report) { build(:license_scan_report, :version_2) }
subject { base_report.diff_with(head_report) }
before do
base_report.add_license(id: nil, name: 'MIT').add_dependency('Library1')
base_report.add_license(id: nil, name: 'BSD').add_dependency('Library1')
base_report.add_license(id: nil, name: 'WTFPL').add_dependency('Library2')
head_report.add_license(id: 'BSD-3-Clause', name: 'bsd').add_dependency('Library1')
head_report.add_license(id: 'Apache-2.0', name: 'Apache 2.0').add_dependency('Library3')
head_report.add_license(id: 'MIT', name: 'MIT').add_dependency('Library1')
end
it { expect(names_from(subject[:added])).to contain_exactly('Apache 2.0') }
it { expect(names_from(subject[:unchanged])).to contain_exactly('MIT', 'BSD') }
it { expect(names_from(subject[:removed])).to contain_exactly('WTFPL') }
end
context 'when diffing a v2 report with a v1 report' do
let(:base_report) { build(:license_scan_report, :version_2) }
let(:head_report) { build(:license_scan_report, :version_1) }
subject { base_report.diff_with(head_report) }
before do
base_report.add_license(id: 'MIT', name: 'MIT').add_dependency('Library1')
base_report.add_license(id: 'BSD-3-Clause', name: 'BSD').add_dependency('Library1')
base_report.add_license(id: 'WTFPL', name: 'WTFPL').add_dependency('Library2')
head_report.add_license(id: nil, name: 'bsd').add_dependency('Library1')
head_report.add_license(id: nil, name: 'Apache 2.0').add_dependency('Library3')
head_report.add_license(id: nil, name: 'MIT').add_dependency('Library1')
end
it { expect(names_from(subject[:added])).to contain_exactly('Apache 2.0') }
it { expect(names_from(subject[:unchanged])).to contain_exactly('MIT', 'BSD') }
it { expect(names_from(subject[:removed])).to contain_exactly('WTFPL') }
end
end end
describe "#empty?" do describe '#empty?' do
let(:completed_report) { build(:ci_reports_license_scanning_report, :report_1) } let(:completed_report) { build(:ci_reports_license_scanning_report, :report_1) }
let(:empty_report) { build(:ci_reports_license_scanning_report) } let(:empty_report) { build(:ci_reports_license_scanning_report) }
...@@ -69,12 +136,21 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do ...@@ -69,12 +136,21 @@ describe Gitlab::Ci::Reports::LicenseScanning::Report do
it { expect(completed_report).not_to be_empty } it { expect(completed_report).not_to be_empty }
end end
describe ".parse_from" do describe '.parse_from' do
context "when parsing a v1 report" do context 'when parsing a v1 report' do
subject { described_class.parse_from(v1_json) } subject { described_class.parse_from(v1_json) }
let(:v1_json) { fixture_file('security_reports/master/gl-license-management-report.json', dir: 'ee') } let(:v1_json) { fixture_file('security_reports/master/gl-license-management-report.json', dir: 'ee') }
specify { expect(subject.licenses.count).to eq(4) } it { expect(subject.version).to eql('1.0') }
it { expect(subject.licenses.count).to eq(4) }
end
context 'when parsing a v2 report' do
subject { described_class.parse_from(v2_json) }
let(:v2_json) { fixture_file('security_reports/gl-license-management-report-v2.json', dir: 'ee') }
it { expect(subject.version).to eql('2.0') }
it { expect(subject.licenses.count).to eq(3) }
end end
end end
end end
...@@ -8,8 +8,8 @@ describe Gitlab::Ci::Reports::LicenseScanning::ReportsComparer do ...@@ -8,8 +8,8 @@ describe Gitlab::Ci::Reports::LicenseScanning::ReportsComparer do
let(:report_comparer) { described_class.new(report_1, report_2) } let(:report_comparer) { described_class.new(report_1, report_2) }
before do before do
report_1.add_dependency('BSD', 1, 'https://opensource.org/licenses/0BSD', 'Library1') report_1.add_license(id: nil, name: 'BSD').add_dependency('Library1')
report_2.add_dependency('bsd', 1, 'https://opensource.org/licenses/0BSD', 'Library1') report_2.add_license(id: nil, name: 'bsd').add_dependency('Library1')
end end
def names_from(licenses) def names_from(licenses)
......
...@@ -191,8 +191,8 @@ describe Ci::Build do ...@@ -191,8 +191,8 @@ describe Ci::Build do
expect { subject }.not_to raise_error expect { subject }.not_to raise_error
expect(license_scanning_report.licenses.count).to eq(4) expect(license_scanning_report.licenses.count).to eq(4)
expect(license_scanning_report.found_licenses['MIT'].name).to eq('MIT') expect(license_scanning_report.licenses.map(&:name)).to contain_exactly("Apache 2.0", "MIT", "New BSD", "unknown")
expect(license_scanning_report.found_licenses['MIT'].dependencies.count).to eq(52) expect(license_scanning_report.licenses.find { |x| x.name == 'MIT' }.dependencies.count).to eq(52)
end end
end end
......
...@@ -5,7 +5,7 @@ module LicenseScanningReportHelper ...@@ -5,7 +5,7 @@ module LicenseScanningReportHelper
Gitlab::Ci::Reports::LicenseScanning::Report.new.tap do |report| Gitlab::Ci::Reports::LicenseScanning::Report.new.tap do |report|
dependencies.each do |license_name, dependencies| dependencies.each do |license_name, dependencies|
dependencies.each do |dependency_name| dependencies.each do |dependency_name|
report.add_dependency(license_name.to_s, 1, "https://opensource.org/licenses/license1", dependency_name) report.add_license(id: nil, name: license_name.to_s, url: "https://opensource.org/licenses/license1").add_dependency(dependency_name)
end end
end end
end end
...@@ -32,7 +32,7 @@ module LicenseScanningReportHelper ...@@ -32,7 +32,7 @@ module LicenseScanningReportHelper
end end
def create_license def create_license
Gitlab::Ci::Reports::LicenseScanning::License.new('License1', 1, "https://opensource.org/licenses/license1").tap do |license| Gitlab::Ci::Reports::LicenseScanning::License.new(id: nil, name: 'License1', url: "https://opensource.org/licenses/license1").tap do |license|
license.add_dependency('Dependency1') license.add_dependency('Dependency1')
license.add_dependency('Dependency2') license.add_dependency('Dependency2')
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