|
1 | 1 |
|
2 | 2 | from dojo.models import Test |
3 | | -from dojo.tools.fortify.parser import FortifyParser |
| 3 | +from dojo.tools.fortify.parser import FortifyParser, FortifyParserV2 |
4 | 4 | from unittests.dojo_test_case import DojoTestCase, get_unit_tests_scans_path |
5 | 5 |
|
6 | 6 |
|
@@ -189,3 +189,65 @@ def test_fortify_hello_world_fpr_rule_without_metainfo(self): |
189 | 189 | self.assertEqual("D3166922519EDD92D132761602EB71B4", finding.unique_id_from_tool) |
190 | 190 | self.assertEqual("src/main/java/hello/HelloWorld.java", finding.file_path) |
191 | 191 | self.assertEqual(13, finding.line) |
| 192 | + |
| 193 | + |
| 194 | +class TestFortifyV2Parser(DojoTestCase): |
| 195 | + |
| 196 | + """ |
| 197 | + Regression: the v1 FPR parser stores the snippet context StartLine (Fortify's |
| 198 | + reported line minus up to 3 context lines) instead of the reported SourceLocation |
| 199 | + line, which breaks file_path + line deduplication against other tools. The |
| 200 | + "Fortify Scan v2" scan type stores the reported line; v1 is intentionally left |
| 201 | + unchanged so existing hashcodes are unaffected. |
| 202 | + """ |
| 203 | + |
| 204 | + def test_fortify_v2_scan_type(self): |
| 205 | + self.assertEqual(["Fortify Scan v2"], FortifyParserV2().get_scan_types()) |
| 206 | + |
| 207 | + def test_fortify_v2_fpr_uses_true_source_location_line(self): |
| 208 | + with (get_unit_tests_scans_path("fortify") / "many_findings.fpr").open(encoding="utf-8") as testfile: |
| 209 | + parser = FortifyParserV2() |
| 210 | + findings = parser.get_findings(testfile, Test()) |
| 211 | + self.assertEqual(61, len(findings)) |
| 212 | + self.validate_locations(findings) |
| 213 | + finding = findings[0] |
| 214 | + # control: the description records Fortify's reported line verbatim |
| 215 | + self.assertIn("**SourceLocationLine:** 222", finding.description) |
| 216 | + # the stored line must match the reported line, not the snippet start (219) |
| 217 | + self.assertEqual( |
| 218 | + 222, finding.line, |
| 219 | + msg=f"expected line=222 (SourceLocationLine), stored line={finding.line}", |
| 220 | + ) |
| 221 | + self.assertEqual("Cross-Site Request Forgery - category.html: 222 (114E5A67-3446-4DD5-B578-D0E6FDBB304E)", finding.title) |
| 222 | + |
| 223 | + def test_fortify_v2_fpr_clamped_snippet(self): |
| 224 | + with (get_unit_tests_scans_path("fortify") / "hello_world.fpr").open(encoding="utf-8") as testfile: |
| 225 | + parser = FortifyParserV2() |
| 226 | + findings = parser.get_findings(testfile, Test()) |
| 227 | + self.assertEqual(4, len(findings)) |
| 228 | + with self.subTest(i=0): |
| 229 | + finding = findings[0] |
| 230 | + self.assertIn("**SourceLocationLine:** 8", finding.description) |
| 231 | + self.assertEqual( |
| 232 | + 8, finding.line, |
| 233 | + msg=f"expected line=8 (SourceLocationLine), stored line={finding.line}", |
| 234 | + ) |
| 235 | + with self.subTest(i=2): |
| 236 | + # near the top of a file the snippet start clamps to 1, so the v1 |
| 237 | + # offset is not even constant: reported line 3, snippet start 1 |
| 238 | + finding = findings[2] |
| 239 | + self.assertIn("**SourceLocationLine:** 3", finding.description) |
| 240 | + self.assertEqual( |
| 241 | + 3, finding.line, |
| 242 | + msg=f"expected line=3 (SourceLocationLine), stored line={finding.line}", |
| 243 | + ) |
| 244 | + |
| 245 | + def test_fortify_v2_xml_unchanged_from_v1(self): |
| 246 | + # the XML report path already uses the reported line in v1; v2 must match |
| 247 | + with (get_unit_tests_scans_path("fortify") / "fortify_many_findings.xml").open(encoding="utf-8") as testfile: |
| 248 | + parser = FortifyParserV2() |
| 249 | + findings = parser.get_findings(testfile, Test()) |
| 250 | + self.assertEqual(324, len(findings)) |
| 251 | + finding = findings[0] |
| 252 | + self.assertEqual("src/main/java/org/joychou/controller/XXE.java", finding.file_path) |
| 253 | + self.assertEqual(81, finding.line) |
0 commit comments