Commit df049de2 authored by James Lopez's avatar James Lopez

Merge branch '34824-exclude-policies' into 'master'

Add a detected licenses search filter

See merge request gitlab-org/gitlab!21603
parents 98ea352a 44846cc3
...@@ -15,7 +15,7 @@ module Projects ...@@ -15,7 +15,7 @@ module Projects
license_compliance = project.license_compliance license_compliance = project.license_compliance
render json: serializer.represent( render json: serializer.represent(
pageable(license_compliance.policies), pageable(matching_policies_from(license_compliance)),
build: license_compliance.latest_build_for_default_branch build: license_compliance.latest_build_for_default_branch
) )
end end
...@@ -64,5 +64,13 @@ module Projects ...@@ -64,5 +64,13 @@ module Projects
def render_error_for(result) def render_error_for(result)
render json: { errors: result[:message].as_json }, status: result.fetch(:http_status, :unprocessable_entity) render json: { errors: result[:message].as_json }, status: result.fetch(:http_status, :unprocessable_entity)
end end
def matching_policies_from(license_compliance)
if params[:detected]
license_compliance.detected_policies
else
license_compliance.policies
end
end
end end
end end
...@@ -10,10 +10,14 @@ module SCA ...@@ -10,10 +10,14 @@ module SCA
def policies def policies
strong_memoize(:policies) do strong_memoize(:policies) do
new_policies.merge(known_policies).sort.map(&:last) unclassified_policies.merge(known_policies).sort.map(&:last)
end end
end end
def detected_policies
policies.reject { |policy| policy.dependencies.count.zero? }
end
def latest_build_for_default_branch def latest_build_for_default_branch
return if pipeline.blank? return if pipeline.blank?
...@@ -38,7 +42,7 @@ module SCA ...@@ -38,7 +42,7 @@ module SCA
end end
end end
def new_policies def unclassified_policies
license_scan_report.licenses.map do |reported_license| license_scan_report.licenses.map do |reported_license|
next if known_policies[reported_license.canonical_id] next if known_policies[reported_license.canonical_id]
......
...@@ -8,7 +8,9 @@ class LicenseEntity < Grape::Entity ...@@ -8,7 +8,9 @@ class LicenseEntity < Grape::Entity
expose :id expose :id
expose :name expose :name
expose :url expose :url do |license|
license.url.presence
end
expose :spdx_identifier expose :spdx_identifier
expose :classification expose :classification
expose :dependencies, using: ComponentEntity, as: :components expose :dependencies, using: ComponentEntity, as: :components
......
- breadcrumb_title _('License Compliance') - breadcrumb_title _('License Compliance')
- page_title _('License Compliance') - page_title _('License Compliance')
#js-licenses-app{ data: { endpoint: project_licenses_path(@project, format: :json), #js-licenses-app{ data: { endpoint: project_licenses_path(@project, detected: true, format: :json),
documentation_path: help_page_path('user/application_security/license_compliance/index'), documentation_path: help_page_path('user/application_security/license_compliance/index'),
empty_state_svg_path: image_path('illustrations/Dependency-list-empty-state.svg') } } empty_state_svg_path: image_path('illustrations/Dependency-list-empty-state.svg') } }
...@@ -76,23 +76,21 @@ describe Projects::LicensesController do ...@@ -76,23 +76,21 @@ describe Projects::LicensesController do
end end
context "when software policies are applied to some of the most recently detected licenses" do context "when software policies are applied to some of the most recently detected licenses" do
let_it_be(:raw_report) { fixture_file_upload(Rails.root.join('ee/spec/fixtures/security_reports/gl-license-management-report-v2.json'), 'application/json') }
let_it_be(:mit) { create(:software_license, :mit) } let_it_be(:mit) { create(:software_license, :mit) }
let_it_be(:mit_policy) { create(:software_license_policy, :denied, software_license: mit, project: project) } let_it_be(:mit_policy) { create(:software_license_policy, :denied, software_license: mit, project: project) }
let_it_be(:pipeline) do let_it_be(:other_license) { create(:software_license, spdx_identifier: "Other-Id") }
create(:ee_ci_pipeline, :with_license_management_report, project: project).tap do |pipeline| let_it_be(:other_license_policy) { create(:software_license_policy, :allowed, software_license: other_license, project: project) }
pipeline.job_artifacts.license_management.last.update!(file: raw_report) let_it_be(:pipeline) { create(:ee_ci_pipeline, project: project, builds: [create(:ee_ci_build, :license_scan_v2, :success)]) }
end
end
context "when loading all policies" do
before do before do
get :index, params: { namespace_id: project.namespace, project_id: project }, format: :json get :index, params: { namespace_id: project.namespace, project_id: project }, format: :json
end end
it { expect(response).to have_http_status(:ok) } it { expect(response).to have_http_status(:ok) }
it { expect(json_response["licenses"].count).to be(4) }
it 'generates the proper JSON response' do it 'includes a policy for an unclassified and known license that was detected in the scan report' do
expect(json_response["licenses"].count).to be(3)
expect(json_response.dig("licenses", 0)).to include({ expect(json_response.dig("licenses", 0)).to include({
"id" => nil, "id" => nil,
"spdx_identifier" => "BSD-3-Clause", "spdx_identifier" => "BSD-3-Clause",
...@@ -100,7 +98,9 @@ describe Projects::LicensesController do ...@@ -100,7 +98,9 @@ describe Projects::LicensesController do
"url" => "http://spdx.org/licenses/BSD-3-Clause.json", "url" => "http://spdx.org/licenses/BSD-3-Clause.json",
"classification" => "unclassified" "classification" => "unclassified"
}) })
end
it 'includes a policy for a denied license found in the scan report' do
expect(json_response.dig("licenses", 1)).to include({ expect(json_response.dig("licenses", 1)).to include({
"id" => mit_policy.id, "id" => mit_policy.id,
"spdx_identifier" => "MIT", "spdx_identifier" => "MIT",
...@@ -108,16 +108,71 @@ describe Projects::LicensesController do ...@@ -108,16 +108,71 @@ describe Projects::LicensesController do
"url" => "http://spdx.org/licenses/MIT.json", "url" => "http://spdx.org/licenses/MIT.json",
"classification" => "denied" "classification" => "denied"
}) })
end
it 'includes a policy for an allowed license NOT found in the latest scan report' do
expect(json_response.dig("licenses", 2)).to include({
"id" => other_license_policy.id,
"spdx_identifier" => other_license.spdx_identifier,
"name" => other_license.name,
"url" => nil,
"classification" => "allowed"
})
end
it 'includes an entry for an unclassified and unknown license found in the scan report' do
expect(json_response.dig("licenses", 3)).to include({
"id" => nil,
"spdx_identifier" => nil,
"name" => "unknown",
"url" => nil,
"classification" => "unclassified"
})
end
end
context "when loading software policies that match licenses detected in the most recent license scan report" do
before do
get :index, params: {
namespace_id: project.namespace,
project_id: project,
detected: true
}, format: :json
end
it { expect(response).to have_http_status(:ok) }
it 'only includes policies for licenses detected in the most recent scan report' do
expect(json_response["licenses"].count).to be(3)
end
it 'includes an unclassified policy for a known license detected in the scan report' do
expect(json_response.dig("licenses", 0)).to include({
"id" => nil,
"spdx_identifier" => "BSD-3-Clause",
"classification" => "unclassified"
})
end
it 'includes a classified license for a known license detected in the scan report' do
expect(json_response.dig("licenses", 1)).to include({
"id" => mit_policy.id,
"spdx_identifier" => "MIT",
"classification" => "denied"
})
end
it 'includes an unclassified and unknown license discovered in the scan report' do
expect(json_response.dig("licenses", 2)).to include({ expect(json_response.dig("licenses", 2)).to include({
"id" => nil, "id" => nil,
"spdx_identifier" => nil, "spdx_identifier" => nil,
"name" => "unknown", "name" => "unknown",
"url" => "", "url" => nil,
"classification" => "unclassified" "classification" => "unclassified"
}) })
end end
end end
end
context 'without existing report' do context 'without existing report' do
let!(:pipeline) { create(:ee_ci_pipeline, :with_dependency_list_report, project: project) } let!(:pipeline) { create(:ee_ci_pipeline, :with_dependency_list_report, project: project) }
......
...@@ -104,7 +104,7 @@ FactoryBot.define do ...@@ -104,7 +104,7 @@ FactoryBot.define do
trait :corrupted_license_management_report do trait :corrupted_license_management_report do
after(:build) do |build| after(:build) do |build|
build.job_artifacts << create(:ee_ci_job_artifact, :corrupted_license_management_report, job: build) build.job_artifacts << create(:ee_ci_job_artifact, :license_scan, :with_corrupted_data, job: build)
end end
end end
...@@ -113,5 +113,13 @@ FactoryBot.define do ...@@ -113,5 +113,13 @@ FactoryBot.define do
build.job_artifacts << create(:ee_ci_job_artifact, :low_severity_dast_report, job: build) build.job_artifacts << create(:ee_ci_job_artifact, :low_severity_dast_report, job: build)
end end
end end
%w[1 1_1 2].each do |version|
trait :"license_scan_v#{version}" do
after :build do |build|
build.job_artifacts << build(:ee_ci_job_artifact, :license_scan, :"v#{version}", job: build)
end
end
end
end end
end end
...@@ -122,16 +122,6 @@ FactoryBot.define do ...@@ -122,16 +122,6 @@ FactoryBot.define do
end end
end end
trait :corrupted_license_management_report do
file_type { :license_management }
file_format { :raw }
after(:build) do |artifact, _|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/trace/sample_trace'), 'application/json')
end
end
trait :performance do trait :performance do
file_format { :raw } file_format { :raw }
file_type { :performance } file_type { :performance }
...@@ -142,16 +132,6 @@ FactoryBot.define do ...@@ -142,16 +132,6 @@ FactoryBot.define do
end end
end end
trait :license_management do
file_format { :raw }
file_type { :license_management }
after(:build) do |artifact, _|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain')
end
end
trait :dependency_scanning do trait :dependency_scanning do
file_format { :raw } file_format { :raw }
file_type { :dependency_scanning } file_type { :dependency_scanning }
...@@ -271,5 +251,27 @@ FactoryBot.define do ...@@ -271,5 +251,27 @@ FactoryBot.define do
Rails.root.join('ee/spec/fixtures/security_reports/dependency_list/gl-dependency-scanning-report.json'), 'application/json') Rails.root.join('ee/spec/fixtures/security_reports/dependency_list/gl-dependency-scanning-report.json'), 'application/json')
end end
end end
trait :license_scan do
file_type { :license_management }
file_format { :raw }
end
%w[1 1_1 2].each do |version|
trait :"v#{version}" do
after(:build) do |artifact, _|
filename = "gl-#{artifact.file_type.dasherize}-report-v#{version.sub(/_/, '.')}.json"
path = Rails.root.join("ee/spec/fixtures/security_reports/#{filename}")
artifact.file = fixture_file_upload(path, "application/json")
end
end
end
trait :with_corrupted_data do
after :build do |artifact, _|
path = Rails.root.join('spec/fixtures/trace/sample_trace')
artifact.file = fixture_file_upload(path, 'application/json')
end
end
end end
end end
...@@ -9,5 +9,11 @@ FactoryBot.define do ...@@ -9,5 +9,11 @@ FactoryBot.define do
name { 'MIT License' } name { 'MIT License' }
url { 'https://opensource.org/licenses/MIT' } url { 'https://opensource.org/licenses/MIT' }
end end
trait :unknown do
id { 'unknown' }
name { 'Unknown' }
url { '' }
end
end end
end end
...@@ -198,7 +198,7 @@ describe Ci::Build do ...@@ -198,7 +198,7 @@ describe Ci::Build do
context 'when there is a corrupted license management report' do context 'when there is a corrupted license management report' do
before do before do
create(:ee_ci_job_artifact, :corrupted_license_management_report, job: job, project: job.project) create(:ee_ci_job_artifact, :license_scan, :with_corrupted_data, job: job, project: job.project)
end end
it 'raises an error' do it 'raises an error' do
......
...@@ -26,5 +26,15 @@ describe LicenseEntity do ...@@ -26,5 +26,15 @@ describe LicenseEntity do
components: [{ name: 'rails', blob_path: path }] components: [{ name: 'rails', blob_path: path }]
}) })
end end
context "when the url is blank" do
where(url: ['', nil])
with_them do
let(:license) { build(:license_scanning_license, :unknown) }
it { expect(subject[:url]).to be_nil }
end
end
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