3535from vulnerabilities .models import AdvisoryWeakness
3636from vulnerabilities .models import CodeFix
3737from vulnerabilities .models import CodeFixV2
38+ from vulnerabilities .models import ImpactedPackage
3839from vulnerabilities .models import Package
3940from vulnerabilities .models import PackageV2
4041from vulnerabilities .models import PipelineRun
4647from vulnerabilities .throttling import PermissionBasedUserRateThrottle
4748
4849
50+ class CharInFilter (filters .BaseInFilter , filters .CharFilter ):
51+ pass
52+
53+
4954class 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
359364class PackageurlListSerializer (serializers .Serializer ):
@@ -385,9 +390,24 @@ class PackageV2FilterSet(filters.FilterSet):
385390
386391
387392class 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
393413class PackageV2ViewSet (viewsets .ReadOnlyModelViewSet ):
@@ -972,56 +992,54 @@ def get_view_name(self):
972992
973993
974994class 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