diff --git a/ee/app/serializers/vulnerabilities/finding_entity.rb b/ee/app/serializers/vulnerabilities/finding_entity.rb index ccb08c0edf629c1821da22727515ecd82e6f06c7..92b55b269e9108d149560ac12154f7e51ebf8976 100644 --- a/ee/app/serializers/vulnerabilities/finding_entity.rb +++ b/ee/app/serializers/vulnerabilities/finding_entity.rb @@ -73,6 +73,8 @@ class Vulnerabilities::FindingEntity < Grape::Entity finding.respond_to?(:ai_resolution_enabled?) } + expose :matches_auto_dismiss_policy?, as: :matches_auto_dismiss_policy, if: ->(_, _) { expose_policy_fields? } + alias_method :occurrence, :object def current_user @@ -85,6 +87,12 @@ def expose_false_positive? project = occurrence.project project.licensed_feature_available?(:sast_fp_reduction) end + + def expose_policy_fields? + project = occurrence.project + ::Feature.enabled?(:auto_dismiss_vulnerability_policies, + project.group) && project.licensed_feature_available?(:security_orchestration_policies) + end end Vulnerabilities::FindingEntity.include_mod_with('ProjectsHelper') diff --git a/ee/app/services/vulnerabilities/compare_security_reports_service.rb b/ee/app/services/vulnerabilities/compare_security_reports_service.rb index 0c2075581e6399bce8d6825aebf11aea494d10ff..7f7e4bc98ca8499d41022254e57921f57df42492 100644 --- a/ee/app/services/vulnerabilities/compare_security_reports_service.rb +++ b/ee/app/services/vulnerabilities/compare_security_reports_service.rb @@ -101,7 +101,11 @@ def get_report(pipeline) limit: Gitlab::Ci::Reports::Security::SecurityFindingsReportsComparer::MAX_FINDINGS_COUNT } ).execute.with_api_scopes - Gitlab::Ci::Reports::Security::AggregatedFinding.new(pipeline, findings) + + findings_array = findings.to_a + Security::Finding.preload_auto_dismissal_checks!(project, findings_array) if findings_array.any? + + Gitlab::Ci::Reports::Security::AggregatedFinding.new(pipeline, findings_array) end def execute(base_pipeline, head_pipeline) diff --git a/ee/spec/serializers/vulnerabilities/finding_entity_spec.rb b/ee/spec/serializers/vulnerabilities/finding_entity_spec.rb index 14390b315fdfce895ada3eaa47e1d304aa6deee4..595a0a750a1d9c7495062a763fcbb4648675394e 100644 --- a/ee/spec/serializers/vulnerabilities/finding_entity_spec.rb +++ b/ee/spec/serializers/vulnerabilities/finding_entity_spec.rb @@ -127,6 +127,29 @@ end end + describe 'matches_auto_dismiss_policy' do + before do + occurrence.matches_auto_dismiss_policy = true + end + + it 'contains matches_auto_dismiss_policy if licensed feature is available' do + stub_licensed_features(security_orchestration_policies: true) + + expect(subject[:matches_auto_dismiss_policy]).to be true + end + + it 'does not contain matches_auto_dismiss_policy by default' do + expect(subject[:matches_auto_dismiss_policy]).to be_nil + end + + it 'does not contain matches_auto_dismiss_policy if licensed feature is available but FF "auto_dismiss_vulnerability_policies" is disabled' do + stub_licensed_features(security_orchestration_policies: true) + stub_feature_flags(auto_dismiss_vulnerability_policies: false) + + expect(subject[:matches_auto_dismiss_policy]).to be_nil + end + end + context 'when not allowed to admin vulnerability feedback' do before do project.add_guest(user) diff --git a/ee/spec/services/vulnerabilities/compare_security_reports_service_spec.rb b/ee/spec/services/vulnerabilities/compare_security_reports_service_spec.rb index 4a21b0f12dbad4929d36cdb06cbb79371edef95f..c36f78338828d1471f0f90cd31a854f1b08841c5 100644 --- a/ee/spec/services/vulnerabilities/compare_security_reports_service_spec.rb +++ b/ee/spec/services/vulnerabilities/compare_security_reports_service_spec.rb @@ -594,5 +594,88 @@ def create_scan_with_findings(scan_type, pipeline, count = 1, partial: false) expect(added_findings_ids[1]).to eq(high_finding.id) end end + + describe 'policy auto-dismissal checks preloading' do + let_it_be(:scan_type) { :sast } + let_it_be(:base_pipeline) { test_pipelines[:default_base] } + let_it_be(:head_pipeline) { test_pipelines[:with_sast_report] } + + before do + stub_licensed_features(security_dashboard: true, sast: true, security_orchestration_policies: true) + end + + context 'when there are no auto-dismiss policies' do + it 'includes attribute matches_auto_dismiss_policy set to false' do + expect(comparison.dig(:data, 'added')).to all match a_hash_including('matches_auto_dismiss_policy' => false) + end + end + + context 'when there are auto-dismiss policies' do + let_it_be(:policy) do + create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) + end + + let_it_be(:rule) do + create(:vulnerability_management_policy_rule, :detected_file_path, + security_policy: policy, + file_path: 'test/**/*') + end + + let!(:policy_finding) do + create_scan_with_findings('sast', head_pipeline, 1).first.tap do |finding| + finding.finding_data['location'] = { file: 'test/sample_spec.rb' } + finding.save! + end + end + + it 'includes the attribute matches_auto_dismiss_policy with the correct value' do + expect(Security::Findings::PolicyAutoDismissalChecker).to receive(:new).once.and_call_original + expect(comparison.dig(:data, 'added')).to all include('matches_auto_dismiss_policy') + + matching_findings, non_matching_findings = comparison.dig(:data, 'added').partition do |finding| + finding['matches_auto_dismiss_policy'] + end + + expect(matching_findings).to all match a_hash_including('matches_auto_dismiss_policy' => true) + expect(non_matching_findings).to all match a_hash_including('matches_auto_dismiss_policy' => false) + end + + context 'when auto_dismiss_vulnerability_policies feature is disabled' do + before do + stub_feature_flags(auto_dismiss_vulnerability_policies: false) + end + + it 'does not precompute auto dismissal checks' do + expect(Security::Findings::PolicyAutoDismissalChecker).not_to receive(:new) + + comparison + end + + it 'does not include the attribute matches_auto_dismiss_policy' do + expect(comparison.dig(:data, 'added')).to all match hash_excluding('matches_auto_dismiss_policy') + end + end + + context 'when feature is not licensed' do + before do + stub_licensed_features(security_dashboard: true, sast: true, security_orchestration_policies: false) + end + + it 'does not include the attribute matches_auto_dismiss_policy' do + expect(comparison.dig(:data, 'added')).to all match hash_excluding('matches_auto_dismiss_policy') + end + end + end + + context 'when findings array is empty' do + let_it_be(:head_pipeline) { create(:ee_ci_pipeline, project: project) } + + it 'does not call preload_auto_dismissal_checks!' do + expect(Security::Finding).not_to receive(:preload_auto_dismissal_checks!) + + comparison + end + end + end end end