|
12 | 12 |
|
13 | 13 | from django.contrib.postgres.aggregates import ArrayAgg |
14 | 14 | from django.db.models import Exists |
| 15 | +from django.db.models import F |
15 | 16 | from django.db.models import Max |
16 | 17 | from django.db.models import OuterRef |
17 | 18 | from django.db.models import Prefetch |
@@ -46,6 +47,7 @@ class PackageQuerySerializer(serializers.Serializer): |
46 | 47 | details = serializers.BooleanField(default=False) |
47 | 48 | ignore_qualifiers_subpath = serializers.BooleanField(default=False) |
48 | 49 | max_advisories = serializers.IntegerField(default=100, min_value=1, max_value=10000) |
| 50 | + reachability = serializers.BooleanField(default=False) |
49 | 51 |
|
50 | 52 | def validate(self, data): |
51 | 53 | if not data["purls"]: |
@@ -258,6 +260,7 @@ def create(self, request, *args, **kwargs): |
258 | 260 |
|
259 | 261 | purls = serializer.validated_data["purls"] |
260 | 262 | details = serializer.validated_data["details"] |
| 263 | + reachability = serializer.validated_data["reachability"] |
261 | 264 | ignore_qualifiers_subpath = serializer.validated_data["ignore_qualifiers_subpath"] |
262 | 265 | max_advisories = serializer.validated_data["max_advisories"] |
263 | 266 |
|
@@ -316,7 +319,9 @@ def create(self, request, *args, **kwargs): |
316 | 319 | if request: |
317 | 320 | base_url = request.build_absolute_uri("/")[:-1] |
318 | 321 | page = self.paginate_queryset(query) |
319 | | - affected_advisory_map = get_affected_advisories_bulk(page, max_advisories, base_url) |
| 322 | + affected_advisory_map = get_affected_advisories_bulk( |
| 323 | + page, max_advisories, base_url, reachability |
| 324 | + ) |
320 | 325 | fixing_advisory_map = get_fixing_advisories_bulk(page, max_advisories, base_url) |
321 | 326 | serializer = self.get_serializer( |
322 | 327 | page, |
@@ -449,7 +454,99 @@ class AffectedByAdvisoriesViewSet(PackageAdvisoriesViewSet): |
449 | 454 | serializer_class = AffectedByAdvisoryV3Serializer |
450 | 455 |
|
451 | 456 |
|
452 | | -def get_affected_advisories_bulk(packages, max_advisories, base_url): |
| 457 | +def get_patches_bulk(package_ids, advisory_ids): |
| 458 | + """Get introduced and fixed patches""" |
| 459 | + |
| 460 | + base_qs = ImpactedPackageAffecting.objects.filter( |
| 461 | + package_id__in=package_ids, |
| 462 | + impacted_package__advisory_id__in=advisory_ids, |
| 463 | + impacted_package__advisory__is_latest=True, |
| 464 | + impacted_package__advisory___all_impacts_unfurled_at__isnull=False, |
| 465 | + ) |
| 466 | + |
| 467 | + introduced_rows = base_qs.filter( |
| 468 | + impacted_package__introduced_by_package_commit_patches__isnull=False |
| 469 | + ).values( |
| 470 | + "package_id", |
| 471 | + "impacted_package__advisory_id", |
| 472 | + commit_hash=F("impacted_package__introduced_by_package_commit_patches__commit_hash"), |
| 473 | + vcs_url=F("impacted_package__introduced_by_package_commit_patches__vcs_url"), |
| 474 | + ) |
| 475 | + |
| 476 | + fixed_rows = base_qs.filter( |
| 477 | + impacted_package__fixed_by_package_commit_patches__isnull=False |
| 478 | + ).values( |
| 479 | + "package_id", |
| 480 | + "impacted_package__advisory_id", |
| 481 | + commit_hash=F("impacted_package__fixed_by_package_commit_patches__commit_hash"), |
| 482 | + vcs_url=F("impacted_package__fixed_by_package_commit_patches__vcs_url"), |
| 483 | + ) |
| 484 | + |
| 485 | + introduced_patches_map = defaultdict(list) |
| 486 | + fixed_patches_map = defaultdict(list) |
| 487 | + |
| 488 | + for row in introduced_rows: |
| 489 | + if row["commit_hash"] or row["vcs_url"]: |
| 490 | + key = (row["package_id"], row["impacted_package__advisory_id"]) |
| 491 | + introduced_patches_map[key].append( |
| 492 | + { |
| 493 | + "commit_hash": row["commit_hash"], |
| 494 | + "vcs_url": row["vcs_url"], |
| 495 | + } |
| 496 | + ) |
| 497 | + |
| 498 | + for row in fixed_rows: |
| 499 | + if row["commit_hash"] or row["vcs_url"]: |
| 500 | + key = (row["package_id"], row["impacted_package__advisory_id"]) |
| 501 | + fixed_patches_map[key].append( |
| 502 | + { |
| 503 | + "commit_hash": row["commit_hash"], |
| 504 | + "vcs_url": row["vcs_url"], |
| 505 | + } |
| 506 | + ) |
| 507 | + |
| 508 | + return introduced_patches_map, fixed_patches_map |
| 509 | + |
| 510 | + |
| 511 | +def build_patch_set_map(patches_map, advisory_ids_by_set): |
| 512 | + """ |
| 513 | + Returns: |
| 514 | + { |
| 515 | + advisory_set_id: [ |
| 516 | + { |
| 517 | + "commit_hash": "...", |
| 518 | + "vcs_url": "...", |
| 519 | + } |
| 520 | + ] |
| 521 | + } |
| 522 | + """ |
| 523 | + result = {} |
| 524 | + |
| 525 | + for advisory_set_id, advisory_ids in advisory_ids_by_set.items(): |
| 526 | + seen = set() |
| 527 | + collected = [] |
| 528 | + |
| 529 | + for (package_id, advisory_id), patches in patches_map.items(): |
| 530 | + if advisory_id not in advisory_ids: |
| 531 | + continue |
| 532 | + |
| 533 | + for patch in patches: |
| 534 | + key = ( |
| 535 | + patch["commit_hash"], |
| 536 | + patch["vcs_url"], |
| 537 | + ) |
| 538 | + |
| 539 | + if key in seen: |
| 540 | + continue |
| 541 | + |
| 542 | + seen.add(key) |
| 543 | + collected.append(patch) |
| 544 | + |
| 545 | + result[advisory_set_id] = collected |
| 546 | + return result |
| 547 | + |
| 548 | + |
| 549 | +def get_affected_advisories_bulk(packages, max_advisories, base_url, reachability=False): |
453 | 550 | package_ids = [p.id for p in packages] |
454 | 551 |
|
455 | 552 | package_ids_with_multiple_importers = PackageV2.objects.filter( |
@@ -542,6 +639,24 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url): |
542 | 639 | advisory_ids_by_set[advisory_set_id].add(advisory_id) |
543 | 640 | all_advisory_ids.add(advisory_id) |
544 | 641 |
|
| 642 | + introduced_patches_by_set = {} |
| 643 | + fixed_patches_by_set = {} |
| 644 | + if reachability: |
| 645 | + introduced_patches_map, fixed_patches_map = get_patches_bulk( |
| 646 | + package_ids, |
| 647 | + advisory_ids=all_advisory_ids, |
| 648 | + ) |
| 649 | + |
| 650 | + introduced_patches_by_set = build_patch_set_map( |
| 651 | + introduced_patches_map, |
| 652 | + advisory_ids_by_set, |
| 653 | + ) |
| 654 | + |
| 655 | + fixed_patches_by_set = build_patch_set_map( |
| 656 | + fixed_patches_map, |
| 657 | + advisory_ids_by_set, |
| 658 | + ) |
| 659 | + |
545 | 660 | ssvc_rows = ( |
546 | 661 | SSVC.objects.filter( |
547 | 662 | related_advisories__id__in=all_advisory_ids, |
@@ -613,7 +728,6 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url): |
613 | 728 |
|
614 | 729 | for adv in groups: |
615 | 730 | primary = adv.primary_advisory |
616 | | - |
617 | 731 | fixed_by_packages = impact_by_package_and_advisory.get( |
618 | 732 | (package.id, primary.id), |
619 | 733 | [], |
@@ -647,6 +761,14 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url): |
647 | 761 | "exploitability": exploitability, |
648 | 762 | "risk_score": risk_score, |
649 | 763 | "fixed_by_packages": fixed_by_packages, |
| 764 | + "introduced_in_patch": introduced_patches_by_set.get( |
| 765 | + adv.id, |
| 766 | + [], |
| 767 | + ), |
| 768 | + "fixed_in_patch": fixed_patches_by_set.get( |
| 769 | + adv.id, |
| 770 | + [], |
| 771 | + ), |
650 | 772 | "ssvc_trees": adv.ssvc_trees, |
651 | 773 | "resource_url": resource_url, |
652 | 774 | } |
@@ -742,6 +864,8 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url): |
742 | 864 | "exploitability": advisory.exploitability, |
743 | 865 | "risk_score": advisory.risk_score, |
744 | 866 | "fixed_by_packages": fixed_by_packages, |
| 867 | + "introduced_in_patch": introduced_patches_by_set.get(advisory_id, []), |
| 868 | + "fixed_in_patch": introduced_patches_by_set.get(advisory_id, []), |
745 | 869 | "ssvc_trees": [ |
746 | 870 | { |
747 | 871 | "vector": ssvc.vector, |
|
0 commit comments