diff --git a/ee/app/services/security/scan_result_policies/update_approvals_service.rb b/ee/app/services/security/scan_result_policies/update_approvals_service.rb index 30f22a8998a63019ef7cfd7a6a26f6baa2e14e85..1eba465db1e33c7f20be71a1fcc1e27d6ea49e47 100644 --- a/ee/app/services/security/scan_result_policies/update_approvals_service.rb +++ b/ee/app/services/security/scan_result_policies/update_approvals_service.rb @@ -138,7 +138,7 @@ def findings_count_violated?(approval_rule, target_pipeline_uuids) if only_newly_detected?(approval_rule) new_uuids.count > vulnerabilities_allowed else - vulnerabilities_count = vulnerabilities_count_for_uuids(pipeline_uuids, approval_rule) + vulnerabilities_count = vulnerabilities_count_for_uuids(pipeline_uuids + target_pipeline_uuids, approval_rule) if vulnerabilities_count[:exceeded_allowed_count] true diff --git a/ee/spec/services/security/scan_result_policies/update_approvals_service_spec.rb b/ee/spec/services/security/scan_result_policies/update_approvals_service_spec.rb index e93f96f6265f33c58f561ad5bac20e4e25a4a256..5edaaf2fd960598b771d7e4788f8822ac907ca59 100644 --- a/ee/spec/services/security/scan_result_policies/update_approvals_service_spec.rb +++ b/ee/spec/services/security/scan_result_policies/update_approvals_service_spec.rb @@ -10,9 +10,13 @@ let(:vulnerability_states) { %w[detected newly_detected] } let(:approvals_required) { 2 } + let_it_be(:project) { create(:project, :repository) } let_it_be(:uuids) { Array.new(5) { SecureRandom.uuid } } - let_it_be_with_refind(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } - let_it_be(:project) { merge_request.project } + let_it_be(:scanner) { create(:vulnerabilities_scanner, project: project) } + let_it_be_with_refind(:merge_request) do + create(:merge_request, source_project: project, target_project: project, + source_branch: 'feature', target_branch: 'master') + end let_it_be(:pipeline) do create(:ee_ci_pipeline, :success, :with_dependency_scanning_report, project: project, @@ -37,12 +41,13 @@ end let_it_be(:pipeline_findings) do - create_list(:security_finding, 5, scan: pipeline_scan, severity: 'high') do |finding, i| - finding.update_column(:uuid, uuids[i]) + uuids.map do |uuid| + create(:security_finding, scan: pipeline_scan, scanner: scanner, severity: 'high', uuid: uuid) end end - let_it_be(:scan_result_policy_read) { create(:scan_result_policy_read) } + let_it_be(:scan_result_policy_read) { create(:scan_result_policy_read, project: project) } + let(:last_violation) { merge_request.scan_result_policy_violations.last } let!(:report_approver_rule) do create(:report_approver_rule, :scan_finding, @@ -57,15 +62,6 @@ end before do - create_list(:security_finding, 5, scan: target_scan, severity: 'high') do |finding, i| - finding.update_column(:uuid, uuids[i]) - end - - create_list(:vulnerabilities_finding, 5, project: project) do |finding, i| - vulnerability = create(:vulnerability, project: project) - finding.update_columns(uuid: uuids[i], vulnerability_id: vulnerability.id) - end - allow(pipeline).to receive(:can_store_security_reports?).and_return(true) end @@ -168,8 +164,8 @@ let(:vulnerabilities_allowed) { 100 } it_behaves_like 'sets approvals_required to 0' - it_behaves_like 'triggers policy bot comment', :scan_finding, false + it_behaves_like 'merge request without scan result violations' context 'when there are other scan_finding violations' do let_it_be(:protected_branch) { create(:protected_branch, project: project, name: 'master') } @@ -229,7 +225,6 @@ end it_behaves_like 'does not update approvals_required' - it_behaves_like 'triggers policy bot comment', :scan_finding, true end @@ -263,7 +258,8 @@ end let!(:merge_base_pipeline_finding) do - create(:security_finding, scan: merge_base_pipeline_scan, severity: 'high', uuid: existing_uuid) + create(:security_finding, scan: merge_base_pipeline_scan, severity: 'high', scanner: scanner, + uuid: existing_uuid) end let(:vulnerability_states) { %w[newly_detected] } @@ -276,7 +272,6 @@ context 'when there are no violated approval rules' do it_behaves_like 'sets approvals_required to 0' - it_behaves_like 'triggers policy bot comment', :scan_finding, false end @@ -284,7 +279,6 @@ let(:existing_uuid) { SecureRandom.uuid } it_behaves_like 'does not update approvals_required' - it_behaves_like 'triggers policy bot comment', :scan_finding, true context 'when no common ancestor pipeline has security reports' do @@ -293,42 +287,40 @@ end it_behaves_like 'does not update approvals_required' - it_behaves_like 'triggers policy bot comment', :scan_finding, true end end end - context 'when the number of findings in current pipeline exceed the allowed limit' do - context 'when vulnerability_states has only newly_detected' do - it_behaves_like 'new vulnerability_states', ['newly_detected'] - end + context 'when there are findings in the current pipeline exceed the allowed limit' do + it_behaves_like 'new vulnerability_states', ['newly_detected'] + it_behaves_like 'new vulnerability_states', ['new_needs_triage'] + it_behaves_like 'new vulnerability_states', ['new_dismissed'] + it_behaves_like 'new vulnerability_states', %w[new_dismissed new_needs_triage] - context 'when vulnerability_states has only new_needs_triage' do - it_behaves_like 'new vulnerability_states', ['new_needs_triage'] - end + it_behaves_like 'does not update approvals_required' + it_behaves_like 'triggers policy bot comment', :scan_finding, true - context 'when vulnerability_states has only new_dismissed' do - it_behaves_like 'new vulnerability_states', ['new_dismissed'] - end + context 'when vulnerability_states are new_dismissed' do + let(:vulnerability_states) { %w[new_dismissed] } - context 'when vulnerability_states are new_dismissed and new_needs_triage' do - it_behaves_like 'new vulnerability_states', %w[new_dismissed new_needs_triage] + it_behaves_like 'sets approvals_required to 0' + it_behaves_like 'triggers policy bot comment', :scan_finding, false end - context 'when vulnerabilities count exceeds the allowed limit' do - it_behaves_like 'does not update approvals_required' + context 'when vulnerability_states are new_needs_triage' do + let(:vulnerability_states) { %w[new_needs_triage] } + it_behaves_like 'does not update approvals_required' it_behaves_like 'triggers policy bot comment', :scan_finding, true end - context 'when new findings are introduced and it exceeds the allowed limit' do + context 'when new findings are introduced to previously existing findings and it exceeds the allowed limit' do let(:vulnerabilities_allowed) { 4 } - let(:new_finding_uuid) { SecureRandom.uuid } - - before do - finding = pipeline_findings.last - finding.update_column(:uuid, new_finding_uuid) + let_it_be(:new_finding_uuid) { uuids[4] } + let_it_be(:previously_existing_finding_uuids) { uuids[0..3] } + let_it_be(:target_pipeline_findings) do + create_findings_with_vulnerabilities(target_scan, previously_existing_finding_uuids) end it 'logs update' do @@ -361,25 +353,15 @@ end it_behaves_like 'does not update approvals_required' - it_behaves_like 'triggers policy bot comment', :scan_finding, true context 'when there are no new dismissed vulnerabilities' do let(:vulnerabilities_allowed) { 0 } - context 'when vulnerability_states is new_dismissed' do - let(:vulnerability_states) { %w[new_dismissed] } - - it_behaves_like 'new vulnerability_states', ['new_dismissed'] - - it_behaves_like 'sets approvals_required to 0' - end - context 'when vulnerability_states is new_needs_triage' do let(:vulnerability_states) { %w[new_needs_triage] } it_behaves_like 'new vulnerability_states', ['new_needs_triage'] - it_behaves_like 'does not update approvals_required' end @@ -387,7 +369,6 @@ let(:vulnerability_states) { %w[new_dismissed new_needs_triage] } it_behaves_like 'new vulnerability_states', %w[new_dismissed new_needs_triage] - it_behaves_like 'does not update approvals_required' end @@ -395,9 +376,16 @@ let(:vulnerability_states) { [] } it_behaves_like 'new vulnerability_states', [] - it_behaves_like 'does not update approvals_required' end + + context 'when vulnerability_states is new_dismissed' do + let(:vulnerability_states) { %w[new_dismissed] } + + it_behaves_like 'new vulnerability_states', ['new_dismissed'] + it_behaves_like 'sets approvals_required to 0' + it_behaves_like 'merge request without scan result violations' + end end context 'when there are new dismissed vulnerabilities' do @@ -413,23 +401,13 @@ let(:vulnerability_states) { %w[new_dismissed] } it_behaves_like 'new vulnerability_states', ['new_dismissed'] - it_behaves_like 'does not update approvals_required' end - context 'when vulnerability_states is new_needs_triage' do - let(:vulnerability_states) { %w[new_needs_triage] } - - it_behaves_like 'new vulnerability_states', ['new_needs_triage'] - - it_behaves_like 'sets approvals_required to 0' - end - context 'when vulnerability_states are new_dismissed and new_needs_triage' do let(:vulnerability_states) { %w[new_dismissed new_needs_triage] } it_behaves_like 'new vulnerability_states', %w[new_dismissed new_needs_triage] - it_behaves_like 'does not update approvals_required' end @@ -437,14 +415,22 @@ let(:vulnerability_states) { [] } it_behaves_like 'new vulnerability_states', [] - it_behaves_like 'does not update approvals_required' end + + context 'when vulnerability_states is new_needs_triage' do + let(:vulnerability_states) { %w[new_needs_triage] } + + it_behaves_like 'new vulnerability_states', ['new_needs_triage'] + it_behaves_like 'sets approvals_required to 0' + it_behaves_like 'merge request without scan result violations' + end end context 'when the approval rules had approvals removed' do let_it_be(:approval_project_rule) do - create(:approval_project_rule, :scan_finding, project: project, approvals_required: 2) + create(:approval_project_rule, :scan_finding, project: project, approvals_required: 2, + scan_result_policy_read: scan_result_policy_read) end let!(:report_approver_rule) do @@ -474,56 +460,53 @@ create(:security_scan, :succeeded, pipeline: pipeline, scan_type: 'dependency_scanning') end + let_it_be(:target_pipeline_findings) { create_findings_with_vulnerabilities(target_scan, uuids) } let(:vulnerability_states) { %w[detected] } - context 'when vulnerability_states has newly_detected' do - let(:vulnerability_states) { %w[detected newly_detected] } - - it_behaves_like 'sets approvals_required to 0' - - it_behaves_like 'triggers policy bot comment', :scan_finding, false - end - - context 'when vulnerability_states are empty array' do - let(:vulnerability_states) { [] } - - it_behaves_like 'sets approvals_required to 0' - end - - context 'when vulnerability_states has new_needs_triage' do - let(:vulnerability_states) { %w[detected new_needs_triage] } + # If vulnerability_states only include previously-existing statuses, + # the updates are handled by SyncPreexistingStatesApprovalRulesService + it_behaves_like 'does not update approvals_required' + it_behaves_like 'does not trigger policy bot comment' - it_behaves_like 'sets approvals_required to 0' + context 'when vulnerabilities count does not exceed the allowed limit' do + let(:vulnerabilities_allowed) { 6 } - it_behaves_like 'triggers policy bot comment', :scan_finding, false + it_behaves_like 'does not update approvals_required' + it_behaves_like 'does not trigger policy bot comment' end - context 'when vulnerability_states has new_dismissed' do - let(:vulnerability_states) { %w[detected new_dismissed] } + context 'when vulnerability_states has only newly detected' do + let(:vulnerability_states) { %w[new_needs_triage new_dismissed] } it_behaves_like 'sets approvals_required to 0' - it_behaves_like 'triggers policy bot comment', :scan_finding, false + it_behaves_like 'merge request without scan result violations' end - context 'when vulnerability_states has new_needs_triage and new_dismissed' do - let(:vulnerability_states) { %w[detected new_needs_triage new_dismissed] } + context 'when vulnerability_states are empty array' do + let(:vulnerability_states) { [] } it_behaves_like 'sets approvals_required to 0' - it_behaves_like 'triggers policy bot comment', :scan_finding, false + it_behaves_like 'merge request without scan result violations' end - context 'when vulnerabilities count exceeds the allowed limit' do - it_behaves_like 'does not update approvals_required' - it_behaves_like 'does not trigger policy bot comment' - end - - context 'when vulnerabilities count does not exceed the allowed limit' do - let(:vulnerabilities_allowed) { 6 } - - it_behaves_like 'does not update approvals_required' - it_behaves_like 'does not trigger policy bot comment' + context 'when vulnerability_states include detected' do + let(:base_states) { %w[detected] } + + [ + %w[newly_detected], + %w[new_needs_triage], + %w[new_dismissed], + %w[new_needs_triage new_dismissed] + ].each do |states| + context "and #{states}" do + let(:vulnerability_states) { base_states + states } + + it_behaves_like 'does not update approvals_required' + it_behaves_like 'triggers policy bot comment', :scan_finding, true + end + end end end @@ -566,8 +549,8 @@ end let_it_be(:related_pipeline_findings) do - create_list(:security_finding, 5, scan: related_pipeline_scan, severity: 'high') do |finding, i| - finding.update_column(:uuid, related_uuids[i]) + related_uuids.each do |uuid| + create(:security_finding, scan: related_pipeline_scan, scanner: scanner, severity: 'high', uuid: uuid) end end @@ -579,14 +562,12 @@ ) end - before do - create_list(:security_finding, 5, scan: related_target_scan, severity: 'high') do |finding, i| - finding.update_column(:uuid, related_uuids[i]) - end + before_all do + related_uuids.each do |uuid| + create(:security_finding, scan: related_target_scan, scanner: scanner, severity: 'high', uuid: uuid) - create_list(:vulnerabilities_finding, 5, project: project) do |finding, i| vulnerability = create(:vulnerability, project: project) - finding.update_columns(uuid: related_uuids[i], vulnerability_id: vulnerability.id) + create(:vulnerabilities_finding, project: project, uuid: uuid, vulnerability: vulnerability) end end @@ -614,7 +595,10 @@ context 'when the approval rule has vulnerability attributes' do let(:report_approver_rule) { nil } - let_it_be(:policy) { create(:scan_result_policy_read, vulnerability_attributes: { fix_available: true }) } + let_it_be(:policy) do + create(:scan_result_policy_read, project: project, vulnerability_attributes: { fix_available: true }) + end + let_it_be(:approval_rule) do create(:approval_project_rule, :scan_finding, project: project, scan_result_policy_read: policy) end @@ -651,4 +635,13 @@ end end end + + def create_findings_with_vulnerabilities(scan, uuids) + uuids.each do |uuid| + create(:security_finding, scan: scan, scanner: scanner, severity: 'high', uuid: uuid) + + vulnerability = create(:vulnerability, project: project) + create(:vulnerabilities_finding, project: project, scanner: scanner, uuid: uuid, vulnerability: vulnerability) + end + end end