|
9 | 9 |
|
10 | 10 |
|
11 | 11 | from django.db.models import Prefetch |
| 12 | +from django.db.models import Q |
12 | 13 | from django_filters import rest_framework as filters |
13 | 14 | from drf_spectacular.utils import OpenApiParameter |
14 | 15 | from drf_spectacular.utils import extend_schema |
|
33 | 34 | from vulnerabilities.models import CodeFixV2 |
34 | 35 | from vulnerabilities.models import ImpactedPackage |
35 | 36 | from vulnerabilities.models import Package |
| 37 | +from vulnerabilities.models import PackageCommitPatch |
36 | 38 | from vulnerabilities.models import PackageV2 |
| 39 | +from vulnerabilities.models import Patch |
37 | 40 | from vulnerabilities.models import PipelineRun |
38 | 41 | from vulnerabilities.models import PipelineSchedule |
39 | 42 | from vulnerabilities.models import Vulnerability |
40 | 43 | from vulnerabilities.models import VulnerabilityReference |
41 | 44 | from vulnerabilities.models import VulnerabilitySeverity |
42 | 45 | from vulnerabilities.models import Weakness |
43 | 46 | from vulnerabilities.throttling import PermissionBasedUserRateThrottle |
| 47 | +from vulnerabilities.utils import generate_patch_url |
44 | 48 | from vulnerabilities.utils import group_advisories_by_content |
45 | 49 |
|
46 | 50 |
|
@@ -333,6 +337,48 @@ def get_fixing_vulnerabilities(self, obj): |
333 | 337 | return [vuln.vulnerability_id for vuln in obj.fixing_vulnerabilities.all()] |
334 | 338 |
|
335 | 339 |
|
| 340 | +class PackageCommitPatchSerializer(serializers.ModelSerializer): |
| 341 | + introduced_in_advisories = serializers.SerializerMethodField() |
| 342 | + fixed_in_advisories = serializers.SerializerMethodField() |
| 343 | + |
| 344 | + class Meta: |
| 345 | + model = PackageCommitPatch |
| 346 | + fields = [ |
| 347 | + "id", |
| 348 | + "commit_hash", |
| 349 | + "vcs_url", |
| 350 | + "patch_url", |
| 351 | + "introduced_in_advisories", |
| 352 | + "fixed_in_advisories", |
| 353 | + ] |
| 354 | + |
| 355 | + def get_introduced_in_advisories(self, obj): |
| 356 | + impacts = obj.introduced_in_impacts.all() |
| 357 | + return self.serialize_impacts(impacts) |
| 358 | + |
| 359 | + def get_fixed_in_advisories(self, obj): |
| 360 | + impacts = obj.fixed_in_impacts.all() |
| 361 | + return self.serialize_impacts(impacts) |
| 362 | + |
| 363 | + @staticmethod |
| 364 | + def serialize_impacts(impacts): |
| 365 | + unique_pairs = set() |
| 366 | + for impact in impacts: |
| 367 | + unique_pairs.add((impact.base_purl, impact.advisory.avid)) |
| 368 | + return [{"package": base_purl, "avid": avid} for base_purl, avid in unique_pairs] |
| 369 | + |
| 370 | + |
| 371 | +class PatchSerializer(serializers.ModelSerializer): |
| 372 | + in_advisories = serializers.SerializerMethodField() |
| 373 | + |
| 374 | + class Meta: |
| 375 | + model = Patch |
| 376 | + fields = ["id", "patch_url", "in_advisories"] |
| 377 | + |
| 378 | + def get_in_advisories(self, obj): |
| 379 | + return [{"avid": advisory.avid} for advisory in obj.advisories.all()] |
| 380 | + |
| 381 | + |
336 | 382 | class PackageV3Serializer(serializers.ModelSerializer): |
337 | 383 | purl = serializers.CharField(source="package_url") |
338 | 384 | risk_score = serializers.FloatField(read_only=True) |
@@ -889,6 +935,65 @@ def get_queryset(self): |
889 | 935 | return queryset |
890 | 936 |
|
891 | 937 |
|
| 938 | +class PackageCommitPatchViewSet(viewsets.ReadOnlyModelViewSet): |
| 939 | + """ |
| 940 | + API endpoint that allows viewing PackageCommitPatch entries. |
| 941 | + """ |
| 942 | + |
| 943 | + serializer_class = PackageCommitPatchSerializer |
| 944 | + throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle] |
| 945 | + |
| 946 | + def get_queryset(self): |
| 947 | + queryset = PackageCommitPatch.objects.prefetch_related( |
| 948 | + "introduced_in_impacts__advisory", "fixed_in_impacts__advisory" |
| 949 | + ) |
| 950 | + |
| 951 | + pk = self.request.query_params.get("id") |
| 952 | + if pk: |
| 953 | + queryset = queryset.filter(id=pk) |
| 954 | + |
| 955 | + advisory_id = self.request.query_params.get("advisory_id") |
| 956 | + if advisory_id: |
| 957 | + queryset = queryset.filter( |
| 958 | + Q(introduced_in_impacts__advisory__avid=advisory_id) |
| 959 | + | Q(fixed_in_impacts__advisory__avid=advisory_id) |
| 960 | + ).distinct() |
| 961 | + |
| 962 | + purl = self.request.query_params.get("purl") |
| 963 | + if purl: |
| 964 | + queryset = queryset.filter( |
| 965 | + Q(introduced_in_impacts__base_purl__icontains=purl) |
| 966 | + | Q(fixed_in_impacts__base_purl__icontains=purl) |
| 967 | + ).distinct() |
| 968 | + |
| 969 | + return queryset |
| 970 | + |
| 971 | + |
| 972 | +class PatchViewSet(viewsets.ReadOnlyModelViewSet): |
| 973 | + """ |
| 974 | + API endpoint that allows viewing PackageCommitPatch entries. |
| 975 | + """ |
| 976 | + |
| 977 | + serializer_class = PatchSerializer |
| 978 | + throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle] |
| 979 | + |
| 980 | + def get_queryset(self): |
| 981 | + queryset = Patch.objects.all() |
| 982 | + |
| 983 | + pk = self.request.query_params.get("id") |
| 984 | + if pk: |
| 985 | + queryset = queryset.filter(id=pk) |
| 986 | + |
| 987 | + advisory_id = self.request.query_params.get("advisory_id") |
| 988 | + if advisory_id: |
| 989 | + queryset = queryset.filter(advisory__advisory_id=advisory_id).distinct() |
| 990 | + |
| 991 | + purl = self.request.query_params.get("purl") |
| 992 | + if purl: |
| 993 | + queryset = queryset.filter(package__package_url__icontains=purl).distinct() |
| 994 | + return queryset |
| 995 | + |
| 996 | + |
892 | 997 | class CodeFixV2ViewSet(viewsets.ReadOnlyModelViewSet): |
893 | 998 | """ |
894 | 999 | API endpoint that allows viewing CodeFix entries. |
|
0 commit comments