|
33 | 33 | from vulnerabilities.models import CodeFixV2 |
34 | 34 | from vulnerabilities.models import ImpactedPackage |
35 | 35 | from vulnerabilities.models import Package |
| 36 | +from vulnerabilities.models import PackageCommitPatch |
36 | 37 | from vulnerabilities.models import PackageV2 |
| 38 | +from vulnerabilities.models import Patch |
37 | 39 | from vulnerabilities.models import PipelineRun |
38 | 40 | from vulnerabilities.models import PipelineSchedule |
39 | 41 | from vulnerabilities.models import Vulnerability |
40 | 42 | from vulnerabilities.models import VulnerabilityReference |
41 | 43 | from vulnerabilities.models import VulnerabilitySeverity |
42 | 44 | from vulnerabilities.models import Weakness |
43 | 45 | from vulnerabilities.throttling import PermissionBasedUserRateThrottle |
| 46 | +from vulnerabilities.utils import get_patch_url |
44 | 47 | from vulnerabilities.utils import group_advisories_by_content |
45 | 48 |
|
46 | 49 |
|
@@ -333,20 +336,49 @@ def get_fixing_vulnerabilities(self, obj): |
333 | 336 | return [vuln.vulnerability_id for vuln in obj.fixing_vulnerabilities.all()] |
334 | 337 |
|
335 | 338 |
|
| 339 | +class PackageCommitPatchSerializer(serializers.ModelSerializer): |
| 340 | + patch_url = serializers.SerializerMethodField() |
| 341 | + |
| 342 | + class Meta: |
| 343 | + model = PackageCommitPatch |
| 344 | + fields = [ |
| 345 | + "id", |
| 346 | + "commit_hash", |
| 347 | + "vcs_url", |
| 348 | + "patch_url", |
| 349 | + ] |
| 350 | + |
| 351 | + def get_patch_url(self, obj): |
| 352 | + return get_patch_url(obj.vcs_url, obj.commit_hash) |
| 353 | + |
| 354 | + |
| 355 | +class PatchSerializer(serializers.ModelSerializer): |
| 356 | + class Meta: |
| 357 | + model = Patch |
| 358 | + fields = [ |
| 359 | + "id", |
| 360 | + "patch_url", |
| 361 | + ] |
| 362 | + |
| 363 | + |
336 | 364 | class PackageV3Serializer(serializers.ModelSerializer): |
337 | 365 | purl = serializers.CharField(source="package_url") |
338 | 366 | risk_score = serializers.FloatField(read_only=True) |
339 | 367 | affected_by_vulnerabilities = serializers.SerializerMethodField() |
340 | 368 | fixing_vulnerabilities = serializers.SerializerMethodField() |
341 | 369 | next_non_vulnerable_version = serializers.SerializerMethodField() |
342 | 370 | latest_non_vulnerable_version = serializers.SerializerMethodField() |
| 371 | + introduced_by_package_commit_patches = serializers.SerializerMethodField() |
| 372 | + fixed_by_package_commit_patches = serializers.SerializerMethodField() |
343 | 373 |
|
344 | 374 | class Meta: |
345 | 375 | model = Package |
346 | 376 | fields = [ |
347 | 377 | "purl", |
348 | 378 | "affected_by_vulnerabilities", |
349 | 379 | "fixing_vulnerabilities", |
| 380 | + "introduced_by_package_commit_patches", |
| 381 | + "fixed_by_package_commit_patches", |
350 | 382 | "next_non_vulnerable_version", |
351 | 383 | "latest_non_vulnerable_version", |
352 | 384 | "risk_score", |
@@ -425,6 +457,98 @@ def get_fixing_vulnerabilities(self, package): |
425 | 457 |
|
426 | 458 | return result |
427 | 459 |
|
| 460 | + def get_introduced_by_package_commit_patches(self, package): |
| 461 | + impacts = package.affected_in_impacts.select_related("advisory").prefetch_related( |
| 462 | + "introduced_by_package_commit_patches" |
| 463 | + ) |
| 464 | + |
| 465 | + avids = {impact.advisory.avid for impact in impacts if impact.advisory_id} |
| 466 | + if not avids: |
| 467 | + return [] |
| 468 | + |
| 469 | + latest_advisories = AdvisoryV2.objects.latest_for_avids(avids) |
| 470 | + advisory_by_avid = {adv.avid: adv for adv in latest_advisories} |
| 471 | + impact_by_avid = {} |
| 472 | + |
| 473 | + advisories = [] |
| 474 | + for impact in impacts: |
| 475 | + avid = impact.advisory.avid |
| 476 | + advisory = advisory_by_avid.get(avid) |
| 477 | + if not advisory: |
| 478 | + continue |
| 479 | + advisories.append(advisory) |
| 480 | + impact_by_avid[avid] = impact |
| 481 | + |
| 482 | + grouped_advisories = group_advisories_by_content(advisories=advisories) |
| 483 | + |
| 484 | + result = [] |
| 485 | + for advisory_group in grouped_advisories.values(): |
| 486 | + primary_advisory = advisory_group["primary"] |
| 487 | + avid = primary_advisory.avid |
| 488 | + impact = impact_by_avid.get(avid) |
| 489 | + |
| 490 | + if not impact: |
| 491 | + continue |
| 492 | + |
| 493 | + patches = impact.introduced_by_package_commit_patches.all() |
| 494 | + if not patches: |
| 495 | + continue |
| 496 | + |
| 497 | + result.append( |
| 498 | + { |
| 499 | + "advisory_id": primary_advisory.avid, |
| 500 | + "duplicate_advisory_ids": [adv.avid for adv in advisory_group["secondary"]], |
| 501 | + "commit_patches": [patch.to_dict() for patch in patches], |
| 502 | + } |
| 503 | + ) |
| 504 | + |
| 505 | + return result |
| 506 | + |
| 507 | + def get_fixed_by_package_commit_patches(self, package): |
| 508 | + impacts = package.affected_in_impacts.select_related("advisory").prefetch_related( |
| 509 | + "fixed_by_package_commit_patches" |
| 510 | + ) |
| 511 | + |
| 512 | + avids = {impact.advisory.avid for impact in impacts if impact.advisory_id} |
| 513 | + if not avids: |
| 514 | + return [] |
| 515 | + |
| 516 | + latest_advisories = AdvisoryV2.objects.latest_for_avids(avids) |
| 517 | + advisory_by_avid = {adv.avid: adv for adv in latest_advisories} |
| 518 | + impact_by_avid = {} |
| 519 | + |
| 520 | + advisories = [] |
| 521 | + for impact in impacts: |
| 522 | + avid = impact.advisory.avid |
| 523 | + if advisory := advisory_by_avid.get(avid): |
| 524 | + advisories.append(advisory) |
| 525 | + impact_by_avid[avid] = impact |
| 526 | + |
| 527 | + grouped_advisories = group_advisories_by_content(advisories=advisories) |
| 528 | + |
| 529 | + result = [] |
| 530 | + for advisory_group in grouped_advisories.values(): |
| 531 | + primary_advisory = advisory_group["primary"] |
| 532 | + impact = impact_by_avid.get(primary_advisory.avid) |
| 533 | + |
| 534 | + if not impact: |
| 535 | + continue |
| 536 | + |
| 537 | + # Query the fixing patches instead |
| 538 | + patches = impact.fixed_by_package_commit_patches.all() |
| 539 | + if not patches: |
| 540 | + continue |
| 541 | + |
| 542 | + result.append( |
| 543 | + { |
| 544 | + "advisory_id": primary_advisory.avid, |
| 545 | + "duplicate_advisory_ids": [adv.avid for adv in advisory_group["secondary"]], |
| 546 | + "commit_patches": [patch.to_dict() for patch in patches], |
| 547 | + } |
| 548 | + ) |
| 549 | + |
| 550 | + return result |
| 551 | + |
428 | 552 | def get_next_non_vulnerable_version(self, package): |
429 | 553 | if next_non_vulnerable := package.get_non_vulnerable_versions()[0]: |
430 | 554 | return next_non_vulnerable.version |
@@ -889,6 +1013,40 @@ def get_queryset(self): |
889 | 1013 | return queryset |
890 | 1014 |
|
891 | 1015 |
|
| 1016 | +class PackageCommitPatchViewSet(viewsets.ReadOnlyModelViewSet): |
| 1017 | + """ |
| 1018 | + API endpoint that allows viewing PackageCommitPatch entries. |
| 1019 | + """ |
| 1020 | + |
| 1021 | + queryset = PackageCommitPatch.objects.all() |
| 1022 | + serializer_class = PackageCommitPatchSerializer |
| 1023 | + throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle] |
| 1024 | + |
| 1025 | + def get_queryset(self): |
| 1026 | + queryset = PackageCommitPatch.objects.all() |
| 1027 | + pk = self.request.query_params.get("id") |
| 1028 | + if pk: |
| 1029 | + queryset = queryset.filter(id=pk) |
| 1030 | + return queryset |
| 1031 | + |
| 1032 | + |
| 1033 | +class PatchViewSet(viewsets.ReadOnlyModelViewSet): |
| 1034 | + """ |
| 1035 | + API endpoint that allows viewing PackageCommitPatch entries. |
| 1036 | + """ |
| 1037 | + |
| 1038 | + queryset = Patch.objects.all() |
| 1039 | + serializer_class = PatchSerializer |
| 1040 | + throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle] |
| 1041 | + |
| 1042 | + def get_queryset(self): |
| 1043 | + queryset = Patch.objects.all() |
| 1044 | + pk = self.request.query_params.get("id") |
| 1045 | + if pk: |
| 1046 | + queryset = queryset.filter(id=pk) |
| 1047 | + return queryset |
| 1048 | + |
| 1049 | + |
892 | 1050 | class CodeFixV2ViewSet(viewsets.ReadOnlyModelViewSet): |
893 | 1051 | """ |
894 | 1052 | API endpoint that allows viewing CodeFix entries. |
|
0 commit comments