|
7 | 7 | from crum import impersonate |
8 | 8 | from django.conf import settings |
9 | 9 | from django.core import serializers |
| 10 | +from django.test import override_settings |
10 | 11 | from django.utils import timezone |
11 | 12 |
|
12 | 13 | from dojo.finding.deduplication import ( |
|
24 | 25 | Engagement, |
25 | 26 | Finding, |
26 | 27 | Product, |
| 28 | + Product_Type, |
27 | 29 | System_Settings, |
28 | 30 | Test, |
29 | 31 | Test_Import, |
30 | 32 | Test_Import_Finding_Action, |
| 33 | + Test_Type, |
31 | 34 | User, |
32 | 35 | copy_model_util, |
33 | 36 | ) |
@@ -617,6 +620,72 @@ def test_deduplication_ordering_key_is_order_independent(self): |
617 | 620 | backward = sorted([f_b, f_a], key=deduplication_ordering_key) |
618 | 621 | self.assertEqual([f.id for f in forward], [f.id for f in backward]) |
619 | 622 |
|
| 623 | + def _dedupe_fortify_repro_scan(self, fpr_filename, engagement_name): |
| 624 | + """ |
| 625 | + Import one corrected Fortify repro .fpr into its own (isolated) engagement |
| 626 | + under a shared product, using unique_id_from_tool deduplication, and return |
| 627 | + the single surviving non-duplicate finding. |
| 628 | +
|
| 629 | + The two fixtures contain the same pair of findings - which share a |
| 630 | + unique_id_from_tool but have different content (different rule/title/line) - |
| 631 | + in opposite document order. |
| 632 | + """ |
| 633 | + from dojo.tools.fortify.parser import FortifyParser # noqa: PLC0415 |
| 634 | + |
| 635 | + product_type, _ = Product_Type.objects.get_or_create(name="Fortify UID Repro PT") |
| 636 | + product, _ = Product.objects.get_or_create( |
| 637 | + name="Fortify UID Repro Product", |
| 638 | + defaults={"description": "Fortify UID dedupe repro", "prod_type": product_type}, |
| 639 | + ) |
| 640 | + test_type, _ = Test_Type.objects.get_or_create(name="Fortify Scan") |
| 641 | + engagement = Engagement.objects.create( |
| 642 | + name=engagement_name, |
| 643 | + product=product, |
| 644 | + target_start=timezone.now().date(), |
| 645 | + target_end=timezone.now().date(), |
| 646 | + deduplication_on_engagement=True, # isolate each scan's dedupe scope |
| 647 | + ) |
| 648 | + test = Test.objects.create( |
| 649 | + engagement=engagement, |
| 650 | + test_type=test_type, |
| 651 | + scan_type="Fortify Scan", |
| 652 | + target_start=timezone.now(), |
| 653 | + target_end=timezone.now(), |
| 654 | + ) |
| 655 | + with (get_unit_tests_scans_path("fortify") / fpr_filename).open(encoding="utf-8") as testfile: |
| 656 | + parsed_findings = FortifyParser().get_findings(testfile, test) |
| 657 | + |
| 658 | + admin = User.objects.get(username="admin") |
| 659 | + saved = [] |
| 660 | + for parsed in parsed_findings: |
| 661 | + parsed.test = test |
| 662 | + parsed.reporter = admin |
| 663 | + parsed.save(dedupe_option=False) # defer dedupe to the batch call below |
| 664 | + saved.append(parsed) |
| 665 | + |
| 666 | + dedupe_batch_of_findings(get_finding_models_for_deduplication([f.id for f in saved])) |
| 667 | + |
| 668 | + refreshed = [Finding.objects.get(id=f.id) for f in saved] |
| 669 | + non_duplicates = [f for f in refreshed if not f.duplicate] |
| 670 | + # unique_id_from_tool dedupe collapses the shared-uid pair to one original |
| 671 | + self.assertEqual(len(non_duplicates), 1, f"expected exactly one original, got {[(f.id, f.line) for f in refreshed]}") |
| 672 | + return non_duplicates[0] |
| 673 | + |
| 674 | + @override_settings(DEDUPLICATION_ALGORITHM_PER_PARSER={"Fortify Scan": settings.DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL}) |
| 675 | + def test_fortify_same_uid_different_content_original_is_order_independent(self): |
| 676 | + # Corrected Fortify import-order repro. Two findings share unique_id_from_tool |
| 677 | + # but differ in content, provided in opposite order across two .fpr files. |
| 678 | + # Under unique_id_from_tool dedupe the surviving "original" must be the same |
| 679 | + # (content-canonical) finding regardless of the scanner's export order. |
| 680 | + # Pre-fix this flips (lowest-id / first-in-file wins); with the fix it is stable. |
| 681 | + original_scan1 = self._dedupe_fortify_repro_scan("fortify_dedupe_uid_repro_scan1.fpr", "fortify-uid-repro-scan1") |
| 682 | + original_scan2 = self._dedupe_fortify_repro_scan("fortify_dedupe_uid_repro_scan2.fpr", "fortify-uid-repro-scan2") |
| 683 | + |
| 684 | + self.assertEqual(original_scan1.unique_id_from_tool, original_scan2.unique_id_from_tool) |
| 685 | + # The same content survives regardless of file order (line/title stable). |
| 686 | + self.assertEqual(original_scan1.line, original_scan2.line) |
| 687 | + self.assertEqual(original_scan1.title, original_scan2.title) |
| 688 | + |
620 | 689 | def test_identical_except_title_hash_code(self): |
621 | 690 | # 4 is already a duplicate of 2, let's see what happens if we create an identical finding with different title (and reset status) |
622 | 691 | # expect: NOT marked as duplicate as title is part of hash_code calculation |
|
0 commit comments