Skip to content

Commit 7a34791

Browse files
authored
Merge branch 'main' into 1953-pypa-importer-package-first
2 parents d1a7cc1 + 5121e31 commit 7a34791

54 files changed

Lines changed: 959 additions & 1058 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

vulnerabilities/api_v2.py

Lines changed: 133 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from vulnerabilities.models import AdvisoryWeakness
3636
from vulnerabilities.models import CodeFix
3737
from vulnerabilities.models import CodeFixV2
38+
from vulnerabilities.models import ImpactedPackage
3839
from vulnerabilities.models import Package
3940
from vulnerabilities.models import PackageV2
4041
from vulnerabilities.models import PipelineRun
@@ -46,6 +47,10 @@
4647
from vulnerabilities.throttling import PermissionBasedUserRateThrottle
4748

4849

50+
class CharInFilter(filters.BaseInFilter, filters.CharFilter):
51+
pass
52+
53+
4954
class WeaknessV2Serializer(serializers.ModelSerializer):
5055
cwe_id = serializers.CharField()
5156
name = serializers.CharField()
@@ -310,8 +315,8 @@ class AdvisoryPackageV2Serializer(serializers.ModelSerializer):
310315
risk_score = serializers.FloatField(read_only=True)
311316
affected_by_vulnerabilities = serializers.SerializerMethodField()
312317
fixing_vulnerabilities = serializers.SerializerMethodField()
313-
next_non_vulnerable_version = serializers.CharField(read_only=True)
314-
latest_non_vulnerable_version = serializers.CharField(read_only=True)
318+
next_non_vulnerable_version = serializers.SerializerMethodField()
319+
latest_non_vulnerable_version = serializers.SerializerMethodField()
315320

316321
class Meta:
317322
model = Package
@@ -324,36 +329,36 @@ class Meta:
324329
"risk_score",
325330
]
326331

327-
def get_affected_by_vulnerabilities(self, obj):
328-
"""
329-
Return a dictionary with vulnerabilities as keys and their details, including fixed_by_packages.
330-
"""
332+
def get_affected_by_vulnerabilities(self, package):
333+
"""Return a dictionary with advisory as keys and their details, including fixed_by_packages."""
331334
result = {}
332335
request = self.context.get("request")
333-
for adv in getattr(obj, "prefetched_affected_advisories", []):
334-
fixed_by_package = adv.fixed_by_packages.first()
335-
purl = None
336-
if fixed_by_package:
337-
purl = fixed_by_package.package_url
338-
# Get code fixed for a vulnerability
339-
code_fixes = CodeFixV2.objects.filter(advisory=adv).distinct()
336+
for impact in package.affected_in_impacts.all():
337+
advisory = impact.advisory
338+
fixed_by_packages = [pkg.purl for pkg in impact.fixed_by_packages.all()]
339+
code_fixes = CodeFixV2.objects.filter(advisory=advisory).distinct()
340340
code_fix_urls = [
341341
reverse("advisory-codefix-detail", args=[code_fix.id], request=request)
342342
for code_fix in code_fixes
343343
]
344-
345-
result[adv.avid] = {
346-
"advisory_id": adv.avid,
347-
"fixed_by_packages": purl,
344+
result[advisory.avid] = {
345+
"advisory_id": advisory.avid,
346+
"fixed_by_packages": fixed_by_packages,
348347
"code_fixes": code_fix_urls,
349348
}
349+
350350
return result
351351

352-
def get_fixing_vulnerabilities(self, obj):
353-
# Ghost package should not fix any vulnerability.
354-
if obj.is_ghost:
355-
return []
356-
return [adv.avid for adv in obj.fixing_advisories.all()]
352+
def get_fixing_vulnerabilities(self, package):
353+
return [impact.advisory.avid for impact in package.fixed_in_impacts.all()]
354+
355+
def get_next_non_vulnerable_version(self, package):
356+
if next_non_vulnerable := package.get_non_vulnerable_versions()[0]:
357+
return next_non_vulnerable.version
358+
359+
def get_latest_non_vulnerable_version(self, package):
360+
if latest_non_vulnerable := package.get_non_vulnerable_versions()[-1]:
361+
return latest_non_vulnerable.version
357362

358363

359364
class PackageurlListSerializer(serializers.Serializer):
@@ -385,9 +390,24 @@ class PackageV2FilterSet(filters.FilterSet):
385390

386391

387392
class AdvisoryPackageV2FilterSet(filters.FilterSet):
388-
affected_by_vulnerability = filters.CharFilter(field_name="affected_by_advisory__advisory_id")
389-
fixing_vulnerability = filters.CharFilter(field_name="fixing_advisories__advisory_id")
390-
purl = filters.CharFilter(field_name="package_url")
393+
affected_by_advisory = filters.CharFilter(
394+
field_name="affected_in_impacts__advisory__avid",
395+
label="Affected By Advisory ID",
396+
help_text="Filter packages affected by a specific Advisory ID.",
397+
)
398+
399+
fixing_advisory = filters.CharFilter(
400+
field_name="fixed_in_impacts__advisory__avid",
401+
label="Fixed By Advisory ID",
402+
help_text="Filter packages fixed by a specific Advisory ID.",
403+
)
404+
405+
purls = CharInFilter(
406+
field_name="package_url",
407+
lookup_expr="in",
408+
label="Package URL",
409+
help_text="Filter by one or more Package URLs. Multi-value supported (comma-separated).",
410+
)
391411

392412

393413
class PackageV2ViewSet(viewsets.ReadOnlyModelViewSet):
@@ -972,56 +992,54 @@ def get_view_name(self):
972992

973993

974994
class AdvisoriesPackageV2ViewSet(viewsets.ReadOnlyModelViewSet):
975-
queryset = PackageV2.objects.all().prefetch_related(
976-
Prefetch(
977-
"affected_by_advisories",
978-
queryset=AdvisoryV2.objects.prefetch_related("fixed_by_packages"),
979-
to_attr="prefetched_affected_advisories",
980-
)
981-
)
995+
queryset = PackageV2.objects.all()
982996
serializer_class = AdvisoryPackageV2Serializer
983-
filter_backends = (filters.DjangoFilterBackend,)
997+
filter_backends = [filters.DjangoFilterBackend]
984998
filterset_class = AdvisoryPackageV2FilterSet
985999

9861000
def get_queryset(self):
987-
queryset = super().get_queryset()
988-
package_purls = self.request.query_params.getlist("purl")
989-
affected_by_advisory = self.request.query_params.get("affected_by_advisory")
990-
fixing_advisory = self.request.query_params.get("fixing_advisory")
991-
if package_purls:
992-
queryset = queryset.filter(package_url__in=package_purls)
993-
if affected_by_advisory:
994-
queryset = queryset.filter(affected_by_advisories__advisory_id=affected_by_advisory)
995-
if fixing_advisory:
996-
queryset = queryset.filter(fixing_advisories__advisory=fixing_advisory)
997-
return queryset.with_is_vulnerable()
1001+
return (
1002+
super()
1003+
.get_queryset()
1004+
.prefetch_related(
1005+
Prefetch(
1006+
"affected_in_impacts",
1007+
queryset=ImpactedPackage.objects.select_related("advisory").prefetch_related(
1008+
"fixed_by_packages",
1009+
),
1010+
),
1011+
Prefetch(
1012+
"fixed_in_impacts",
1013+
queryset=ImpactedPackage.objects.select_related("advisory"),
1014+
),
1015+
)
1016+
.with_is_vulnerable()
1017+
)
9981018

9991019
def list(self, request, *args, **kwargs):
1000-
queryset = self.get_queryset()
1001-
# Apply pagination
1002-
page = self.paginate_queryset(queryset)
1020+
filtered_queryset = self.filter_queryset(self.get_queryset())
1021+
page = self.paginate_queryset(filtered_queryset)
1022+
1023+
advisories = set()
10031024
if page is not None:
1004-
# Collect only vulnerabilities for packages in the current page
1005-
advisories = set()
10061025
for package in page:
1007-
advisories.update(package.affected_by_advisories.all())
1008-
advisories.update(package.fixing_advisories.all())
1026+
advisories.update({impact.advisory for impact in package.affected_in_impacts.all()})
1027+
advisories.update({impact.advisory for impact in package.fixed_in_impacts.all()})
10091028

10101029
# Serialize the vulnerabilities with advisory_id and advisory label as keys
10111030
advisory_data = {f"{adv.avid}": AdvisoryV2Serializer(adv).data for adv in advisories}
10121031

10131032
# Serialize the current page of packages
10141033
serializer = self.get_serializer(page, many=True)
10151034
data = serializer.data
1016-
print(data)
1035+
10171036
# Use 'self.get_paginated_response' to include pagination data
10181037
return self.get_paginated_response({"advisories": advisory_data, "packages": data})
10191038

10201039
# If pagination is not applied, collect vulnerabilities for all packages
1021-
advisories = set()
10221040
for package in queryset:
1023-
advisories.update(package.affected_by_vulnerabilities.all())
1024-
advisories.update(package.fixing_vulnerabilities.all())
1041+
advisories.update({impact.advisory for impact in package.affected_in_impacts.all()})
1042+
advisories.update({impact.advisory for impact in package.fixed_in_impacts.all()})
10251043

10261044
advisory_data = {f"{adv.avid}": AdvisoryV2Serializer(adv).data for adv in advisories}
10271045

@@ -1057,13 +1075,28 @@ def bulk_lookup(self, request):
10571075
purls = validated_data.get("purls")
10581076

10591077
# Fetch packages matching the provided purls
1060-
packages = PackageV2.objects.for_purls(purls).with_is_vulnerable()
1078+
packages = (
1079+
PackageV2.objects.for_purls(purls)
1080+
.prefetch_related(
1081+
Prefetch(
1082+
"affected_in_impacts",
1083+
queryset=ImpactedPackage.objects.select_related("advisory").prefetch_related(
1084+
"fixed_by_packages",
1085+
),
1086+
),
1087+
Prefetch(
1088+
"fixed_in_impacts",
1089+
queryset=ImpactedPackage.objects.select_related("advisory"),
1090+
),
1091+
)
1092+
.with_is_vulnerable()
1093+
)
10611094

10621095
# Collect vulnerabilities associated with these packages
10631096
advisories = set()
10641097
for package in packages:
1065-
advisories.update(package.affected_by_advisories.all())
1066-
advisories.update(package.fixing_advisories.all())
1098+
advisories.update({impact.advisory for impact in package.affected_in_impacts.all()})
1099+
advisories.update({impact.advisory for impact in package.fixed_in_impacts.all()})
10671100

10681101
# Serialize vulnerabilities with vulnerability_id as keys
10691102
advisory_data = {adv.avid: AdvisoryV2Serializer(adv).data for adv in advisories}
@@ -1128,6 +1161,20 @@ def bulk_search(self, request):
11281161
PackageV2.objects.filter(plain_package_url__in=plain_purls)
11291162
.order_by("plain_package_url")
11301163
.distinct("plain_package_url")
1164+
.prefetch_related(
1165+
Prefetch(
1166+
"affected_in_impacts",
1167+
queryset=ImpactedPackage.objects.select_related(
1168+
"advisory"
1169+
).prefetch_related(
1170+
"fixed_by_packages",
1171+
),
1172+
),
1173+
Prefetch(
1174+
"fixed_in_impacts",
1175+
queryset=ImpactedPackage.objects.select_related("advisory"),
1176+
),
1177+
)
11311178
.with_is_vulnerable()
11321179
)
11331180

@@ -1136,14 +1183,16 @@ def bulk_search(self, request):
11361183
# Collect vulnerabilities associated with these packages
11371184
advisories = set()
11381185
for package in packages:
1139-
advisories.update(package.affected_by_advisories.all())
1140-
advisories.update(package.fixing_advisories.all())
1186+
advisories.update({impact.advisory for impact in package.affected_in_impacts.all()})
1187+
advisories.update({impact.advisory for impact in package.fixed_in_impacts.all()})
11411188

11421189
advisory_data = {adv.avid: AdvisoryV2Serializer(adv).data for adv in advisories}
11431190

11441191
if not purl_only:
11451192
package_data = AdvisoryPackageV2Serializer(
1146-
packages, many=True, context={"request": request}
1193+
packages,
1194+
many=True,
1195+
context={"request": request},
11471196
).data
11481197
return Response(
11491198
{
@@ -1158,20 +1207,39 @@ def bulk_search(self, request):
11581207
vulnerable_purls = [str(package.plain_package_url) for package in vulnerable_purls]
11591208
return Response(data=vulnerable_purls)
11601209

1161-
query = PackageV2.objects.filter(package_url__in=purls).distinct().with_is_vulnerable()
1210+
query = (
1211+
PackageV2.objects.filter(package_url__in=purls)
1212+
.order_by("plain_package_url")
1213+
.distinct("plain_package_url")
1214+
.prefetch_related(
1215+
Prefetch(
1216+
"affected_in_impacts",
1217+
queryset=ImpactedPackage.objects.select_related("advisory").prefetch_related(
1218+
"fixed_by_packages",
1219+
),
1220+
),
1221+
Prefetch(
1222+
"fixed_in_impacts",
1223+
queryset=ImpactedPackage.objects.select_related("advisory"),
1224+
),
1225+
)
1226+
.with_is_vulnerable()
1227+
)
11621228
packages = query
11631229

11641230
# Collect vulnerabilities associated with these packages
11651231
advisories = set()
11661232
for package in packages:
1167-
advisories.update(package.affected_by_advisories.all())
1168-
advisories.update(package.fixing_advisories.all())
1233+
advisories.update({impact.advisory for impact in package.affected_in_impacts.all()})
1234+
advisories.update({impact.advisory for impact in package.fixed_in_impacts.all()})
11691235

11701236
advisory_data = {adv.advisory_id: AdvisoryV2Serializer(adv).data for adv in advisories}
11711237

11721238
if not purl_only:
11731239
package_data = AdvisoryPackageV2Serializer(
1174-
packages, many=True, context={"request": request}
1240+
packages,
1241+
many=True,
1242+
context={"request": request},
11751243
).data
11761244
return Response(
11771245
{

0 commit comments

Comments
 (0)