Commit 2598cb41 authored by James Lopez's avatar James Lopez

Merge branch '14793-missing-security-reports-be' into 'master'

Abort rendering of security reports that aren't set up

See merge request gitlab-org/gitlab!20381
parents 6c1f03fe fe94aa5c
...@@ -275,6 +275,7 @@ export default { ...@@ -275,6 +275,7 @@ export default {
:head-blob-path="mr.headBlobPath" :head-blob-path="mr.headBlobPath"
:source-branch="mr.sourceBranch" :source-branch="mr.sourceBranch"
:base-blob-path="mr.baseBlobPath" :base-blob-path="mr.baseBlobPath"
:enabled-reports="mr.enabledSecurityReports"
:sast-head-path="mr.sast.head_path" :sast-head-path="mr.sast.head_path"
:sast-base-path="mr.sast.base_path" :sast-base-path="mr.sast.base_path"
:sast-help-path="mr.sastHelp" :sast-help-path="mr.sastHelp"
......
import CEMergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store'; import CEMergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { mapApprovalsResponse, mapApprovalRulesResponse } from '../mappers'; import { mapApprovalsResponse, mapApprovalRulesResponse } from '../mappers';
import CodeQualityComparisonWorker from '../workers/code_quality_comparison_worker'; import CodeQualityComparisonWorker from '../workers/code_quality_comparison_worker';
...@@ -38,6 +39,8 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -38,6 +39,8 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.licenseManagement = data.license_management; this.licenseManagement = data.license_management;
this.metricsReportsPath = data.metrics_reports_path; this.metricsReportsPath = data.metrics_reports_path;
this.enabledSecurityReports = convertObjectPropsToCamelCase(data.enabled_reports);
this.blockingMergeRequests = data.blocking_merge_requests; this.blockingMergeRequests = data.blocking_merge_requests;
this.hasApprovalsAvailable = data.has_approvals_available; this.hasApprovalsAvailable = data.has_approvals_available;
......
...@@ -20,6 +20,11 @@ export default { ...@@ -20,6 +20,11 @@ export default {
}, },
mixins: [securityReportsMixin], mixins: [securityReportsMixin],
props: { props: {
enabledReports: {
type: Object,
required: false,
default: () => ({}),
},
headBlobPath: { headBlobPath: {
type: String, type: String,
required: true, required: true,
...@@ -168,25 +173,22 @@ export default { ...@@ -168,25 +173,22 @@ export default {
securityTab() { securityTab() {
return `${this.pipelinePath}/security`; return `${this.pipelinePath}/security`;
}, },
shouldRenderSastContainer() { hasContainerScanningReports() {
const type = 'containerScanning';
if (this.isMergeRequestReportApiEnabled(type)) {
return this.enabledReports[type];
}
const { head, diffEndpoint } = this.sastContainer.paths; const { head, diffEndpoint } = this.sastContainer.paths;
return Boolean(head || diffEndpoint);
return head || diffEndpoint;
}, },
shouldRenderDependencyScanning() { hasDependencyScanningReports() {
const { head, diffEndpoint } = this.dependencyScanning.paths; return this.hasReportsType('dependencyScanning');
return head || diffEndpoint;
}, },
shouldRenderDast() { hasDastReports() {
const { head, diffEndpoint } = this.dast.paths; return this.hasReportsType('dast');
return head || diffEndpoint;
}, },
shouldRenderSast() { hasSastReports() {
const { head, diffEndpoint } = this.sast.paths; return this.hasReportsType('sast');
return head || diffEndpoint;
}, },
}, },
...@@ -209,7 +211,7 @@ export default { ...@@ -209,7 +211,7 @@ export default {
const sastDiffEndpoint = gl && gl.mrWidgetData && gl.mrWidgetData.sast_comparison_path; const sastDiffEndpoint = gl && gl.mrWidgetData && gl.mrWidgetData.sast_comparison_path;
if (gon.features && gon.features.sastMergeRequestReportApi && sastDiffEndpoint) { if (this.isMergeRequestReportApiEnabled('sast') && sastDiffEndpoint && this.hasSastReports) {
this.setSastDiffEndpoint(sastDiffEndpoint); this.setSastDiffEndpoint(sastDiffEndpoint);
this.fetchSastDiff(); this.fetchSastDiff();
} else if (this.sastHeadPath) { } else if (this.sastHeadPath) {
...@@ -225,9 +227,9 @@ export default { ...@@ -225,9 +227,9 @@ export default {
gl && gl.mrWidgetData && gl.mrWidgetData.container_scanning_comparison_path; gl && gl.mrWidgetData && gl.mrWidgetData.container_scanning_comparison_path;
if ( if (
gon.features && this.isMergeRequestReportApiEnabled('containerScanning') &&
gon.features.containerScanningMergeRequestReportApi && sastContainerDiffEndpoint &&
sastContainerDiffEndpoint this.hasContainerScanningReports
) { ) {
this.setSastContainerDiffEndpoint(sastContainerDiffEndpoint); this.setSastContainerDiffEndpoint(sastContainerDiffEndpoint);
this.fetchSastContainerDiff(); this.fetchSastContainerDiff();
...@@ -242,7 +244,7 @@ export default { ...@@ -242,7 +244,7 @@ export default {
const dastDiffEndpoint = gl && gl.mrWidgetData && gl.mrWidgetData.dast_comparison_path; const dastDiffEndpoint = gl && gl.mrWidgetData && gl.mrWidgetData.dast_comparison_path;
if (gon.features && gon.features.dastMergeRequestReportApi && dastDiffEndpoint) { if (this.isMergeRequestReportApiEnabled('dast') && dastDiffEndpoint && this.hasDastReports) {
this.setDastDiffEndpoint(dastDiffEndpoint); this.setDastDiffEndpoint(dastDiffEndpoint);
this.fetchDastDiff(); this.fetchDastDiff();
} else if (this.dastHeadPath) { } else if (this.dastHeadPath) {
...@@ -258,9 +260,9 @@ export default { ...@@ -258,9 +260,9 @@ export default {
gl && gl.mrWidgetData && gl.mrWidgetData.dependency_scanning_comparison_path; gl && gl.mrWidgetData && gl.mrWidgetData.dependency_scanning_comparison_path;
if ( if (
gon.features && this.isMergeRequestReportApiEnabled('dependencyScanning') &&
gon.features.dependencyScanningMergeRequestReportApi && dependencyScanningDiffEndpoint &&
dependencyScanningDiffEndpoint this.hasDependencyScanningReports
) { ) {
this.setDependencyScanningDiffEndpoint(dependencyScanningDiffEndpoint); this.setDependencyScanningDiffEndpoint(dependencyScanningDiffEndpoint);
this.fetchDependencyScanningDiff(); this.fetchDependencyScanningDiff();
...@@ -321,6 +323,16 @@ export default { ...@@ -321,6 +323,16 @@ export default {
fetchSastReports: 'fetchReports', fetchSastReports: 'fetchReports',
fetchSastDiff: 'fetchDiff', fetchSastDiff: 'fetchDiff',
}), }),
isMergeRequestReportApiEnabled(type) {
return Boolean(gon.features && gon.features[`${type}MergeRequestReportApi`]);
},
hasReportsType(type) {
if (this.isMergeRequestReportApiEnabled(type)) {
return this.enabledReports[type];
}
const { head, diffEndpoint } = this[type].paths;
return Boolean(head || diffEndpoint);
},
}, },
}; };
</script> </script>
...@@ -340,12 +352,13 @@ export default { ...@@ -340,12 +352,13 @@ export default {
target="_blank" target="_blank"
class="btn btn-default btn-sm float-right append-right-default" class="btn btn-default btn-sm float-right append-right-default"
> >
<span>{{ s__('ciReport|View full report') }}</span> <icon :size="16" name="external-link" /> <span>{{ s__('ciReport|View full report') }}</span>
<icon :size="16" name="external-link" />
</a> </a>
</div> </div>
<div slot="body" class="mr-widget-grouped-section report-block"> <div slot="body" class="mr-widget-grouped-section report-block">
<template v-if="shouldRenderSast"> <template v-if="hasSastReports">
<summary-row <summary-row
:summary="groupedSastText" :summary="groupedSastText"
:status-icon="sastStatusIcon" :status-icon="sastStatusIcon"
...@@ -364,7 +377,7 @@ export default { ...@@ -364,7 +377,7 @@ export default {
/> />
</template> </template>
<template v-if="shouldRenderDependencyScanning"> <template v-if="hasDependencyScanningReports">
<summary-row <summary-row
:summary="groupedDependencyText" :summary="groupedDependencyText"
:status-icon="dependencyScanningStatusIcon" :status-icon="dependencyScanningStatusIcon"
...@@ -382,7 +395,7 @@ export default { ...@@ -382,7 +395,7 @@ export default {
/> />
</template> </template>
<template v-if="shouldRenderSastContainer"> <template v-if="hasContainerScanningReports">
<summary-row <summary-row
:summary="groupedSastContainerText" :summary="groupedSastContainerText"
:status-icon="sastContainerStatusIcon" :status-icon="sastContainerStatusIcon"
...@@ -400,7 +413,7 @@ export default { ...@@ -400,7 +413,7 @@ export default {
/> />
</template> </template>
<template v-if="shouldRenderDast"> <template v-if="hasDastReports">
<summary-row <summary-row
:summary="groupedDastText" :summary="groupedDastText"
:status-icon="dastStatusIcon" :status-icon="dastStatusIcon"
......
...@@ -134,6 +134,16 @@ module EE ...@@ -134,6 +134,16 @@ module EE
end end
end end
def enabled_reports
{
sast: report_type_enabled?(:sast),
container_scanning: report_type_enabled?(:container_scanning),
dast: report_type_enabled?(:dast),
dependency_scanning: report_type_enabled?(:dependency_scanning),
license_management: report_type_enabled?(:license_management)
}
end
def has_dependency_scanning_reports? def has_dependency_scanning_reports?
!!(actual_head_pipeline&.has_reports?(::Ci::JobArtifact.dependency_list_reports)) !!(actual_head_pipeline&.has_reports?(::Ci::JobArtifact.dependency_list_reports))
end end
...@@ -208,5 +218,9 @@ module EE ...@@ -208,5 +218,9 @@ module EE
def missing_report_error(report_type) def missing_report_error(report_type)
{ status: :error, status_reason: "This merge request does not have #{report_type} reports" } { status: :error, status_reason: "This merge request does not have #{report_type} reports" }
end end
def report_type_enabled?(report_type)
!!actual_head_pipeline&.batch_lookup_report_artifact_for_file_type(report_type)
end
end end
end end
...@@ -36,6 +36,10 @@ module EE ...@@ -36,6 +36,10 @@ module EE
end end
end end
expose :enabled_reports do |merge_request|
merge_request.enabled_reports
end
expose :sast, if: -> (mr, _) { head_pipeline_downloadable_path_for_report_type(:sast) } do expose :sast, if: -> (mr, _) { head_pipeline_downloadable_path_for_report_type(:sast) } do
expose :head_path do |merge_request| expose :head_path do |merge_request|
head_pipeline_downloadable_path_for_report_type(:sast) head_pipeline_downloadable_path_for_report_type(:sast)
......
---
title: Abort rendering of security reports that aren't enabled
merge_request: 20381
author:
type: fixed
...@@ -10,6 +10,13 @@ export default Object.assign({}, mockData, { ...@@ -10,6 +10,13 @@ export default Object.assign({}, mockData, {
head_path: 'blob_path', head_path: 'blob_path',
}, },
vulnerability_feedback_help_path: '/help/user/application_security/index', vulnerability_feedback_help_path: '/help/user/application_security/index',
enabled_reports: {
sast: true,
container_scanning: false,
dast: true,
dependency_scanning: false,
license_management: true,
},
}); });
// Codeclimate // Codeclimate
......
...@@ -118,6 +118,38 @@ describe MergeRequest do ...@@ -118,6 +118,38 @@ describe MergeRequest do
end end
end end
describe '#enabled_reports' do
let(:project) { create(:project, :repository) }
where(:report_type, :with_reports) do
:sast | :with_sast_reports
:container_scanning | :with_container_scanning_reports
:dast | :with_dast_reports
:dependency_scanning | :with_dependency_scanning_reports
:license_management | :with_license_management_reports
end
with_them do
subject { merge_request.enabled_reports[report_type] }
before do
stub_licensed_features({ report_type => true })
end
context "when head pipeline has reports" do
let(:merge_request) { create(:ee_merge_request, with_reports, source_project: project) }
it { is_expected.to be_truthy }
end
context "when head pipeline does not have reports" do
let(:merge_request) { create(:ee_merge_request, source_project: project) }
it { is_expected.to be_falsy }
end
end
end
describe '#participant_approvers with approval_rules disabled' do describe '#participant_approvers with approval_rules disabled' do
let!(:approver) { create(:approver, target: project) } let!(:approver) { create(:approver, target: project) }
let(:code_owners) { [double(:code_owner)] } let(:code_owners) { [double(:code_owner)] }
......
...@@ -59,6 +59,37 @@ describe MergeRequestWidgetEntity do ...@@ -59,6 +59,37 @@ describe MergeRequestWidgetEntity do
expect { serializer.represent(merge_request) }.not_to exceed_query_limit(control) expect { serializer.represent(merge_request) }.not_to exceed_query_limit(control)
end end
describe 'enabled_reports' do
it 'marks all reports as disabled by default' do
expect(subject.as_json).to include(:enabled_reports)
expect(subject.as_json[:enabled_reports]).to eq({
sast: false,
container_scanning: false,
dast: false,
dependency_scanning: false,
license_management: false
})
end
it 'marks reports as enabled if artifacts exist' do
allow(merge_request).to receive(:enabled_reports).and_return({
sast: true,
container_scanning: true,
dast: true,
dependency_scanning: true,
license_management: true
})
expect(subject.as_json).to include(:enabled_reports)
expect(subject.as_json[:enabled_reports]).to eq({
sast: true,
container_scanning: true,
dast: true,
dependency_scanning: true,
license_management: true
})
end
end
describe 'test report artifacts', :request_store do describe 'test report artifacts', :request_store do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
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