3131from vulnerabilities .models import AdvisoryWeakness
3232from vulnerabilities .models import CodeFix
3333from vulnerabilities .models import CodeFixV2
34+ from vulnerabilities .models import ImpactedPackage
3435from vulnerabilities .models import Package
3536from vulnerabilities .models import PackageV2
3637from vulnerabilities .models import PipelineRun
4243from vulnerabilities .throttling import PermissionBasedUserRateThrottle
4344
4445
46+ class CharInFilter (filters .BaseInFilter , filters .CharFilter ):
47+ pass
48+
49+
4550class WeaknessV2Serializer (serializers .ModelSerializer ):
4651 cwe_id = serializers .CharField ()
4752 name = serializers .CharField ()
@@ -306,8 +311,8 @@ class AdvisoryPackageV2Serializer(serializers.ModelSerializer):
306311 risk_score = serializers .FloatField (read_only = True )
307312 affected_by_vulnerabilities = serializers .SerializerMethodField ()
308313 fixing_vulnerabilities = serializers .SerializerMethodField ()
309- next_non_vulnerable_version = serializers .CharField ( read_only = True )
310- latest_non_vulnerable_version = serializers .CharField ( read_only = True )
314+ next_non_vulnerable_version = serializers .SerializerMethodField ( )
315+ latest_non_vulnerable_version = serializers .SerializerMethodField ( )
311316
312317 class Meta :
313318 model = Package
@@ -320,36 +325,36 @@ class Meta:
320325 "risk_score" ,
321326 ]
322327
323- def get_affected_by_vulnerabilities (self , obj ):
324- """
325- Return a dictionary with vulnerabilities as keys and their details, including fixed_by_packages.
326- """
328+ def get_affected_by_vulnerabilities (self , package ):
329+ """Return a dictionary with advisory as keys and their details, including fixed_by_packages."""
327330 result = {}
328331 request = self .context .get ("request" )
329- for adv in getattr (obj , "prefetched_affected_advisories" , []):
330- fixed_by_package = adv .fixed_by_packages .first ()
331- purl = None
332- if fixed_by_package :
333- purl = fixed_by_package .package_url
334- # Get code fixed for a vulnerability
335- code_fixes = CodeFixV2 .objects .filter (advisory = adv ).distinct ()
332+ for impact in package .affected_in_impacts .all ():
333+ advisory = impact .advisory
334+ fixed_by_packages = [pkg .purl for pkg in impact .fixed_by_packages .all ()]
335+ code_fixes = CodeFixV2 .objects .filter (advisory = advisory ).distinct ()
336336 code_fix_urls = [
337337 reverse ("advisory-codefix-detail" , args = [code_fix .id ], request = request )
338338 for code_fix in code_fixes
339339 ]
340-
341- result [adv .avid ] = {
342- "advisory_id" : adv .avid ,
343- "fixed_by_packages" : purl ,
340+ result [advisory .avid ] = {
341+ "advisory_id" : advisory .avid ,
342+ "fixed_by_packages" : fixed_by_packages ,
344343 "code_fixes" : code_fix_urls ,
345344 }
345+
346346 return result
347347
348- def get_fixing_vulnerabilities (self , obj ):
349- # Ghost package should not fix any vulnerability.
350- if obj .is_ghost :
351- return []
352- return [adv .avid for adv in obj .fixing_advisories .all ()]
348+ def get_fixing_vulnerabilities (self , package ):
349+ return [impact .advisory .avid for impact in package .fixed_in_impacts .all ()]
350+
351+ def get_next_non_vulnerable_version (self , package ):
352+ if next_non_vulnerable := package .get_non_vulnerable_versions ()[0 ]:
353+ return next_non_vulnerable .version
354+
355+ def get_latest_non_vulnerable_version (self , package ):
356+ if latest_non_vulnerable := package .get_non_vulnerable_versions ()[- 1 ]:
357+ return latest_non_vulnerable .version
353358
354359
355360class PackageurlListSerializer (serializers .Serializer ):
@@ -381,9 +386,24 @@ class PackageV2FilterSet(filters.FilterSet):
381386
382387
383388class AdvisoryPackageV2FilterSet (filters .FilterSet ):
384- affected_by_vulnerability = filters .CharFilter (field_name = "affected_by_advisory__advisory_id" )
385- fixing_vulnerability = filters .CharFilter (field_name = "fixing_advisories__advisory_id" )
386- purl = filters .CharFilter (field_name = "package_url" )
389+ affected_by_advisory = filters .CharFilter (
390+ field_name = "affected_in_impacts__advisory__avid" ,
391+ label = "Affected By Advisory ID" ,
392+ help_text = "Filter packages affected by a specific Advisory ID." ,
393+ )
394+
395+ fixing_advisory = filters .CharFilter (
396+ field_name = "fixed_in_impacts__advisory__avid" ,
397+ label = "Fixed By Advisory ID" ,
398+ help_text = "Filter packages fixed by a specific Advisory ID." ,
399+ )
400+
401+ purls = CharInFilter (
402+ field_name = "package_url" ,
403+ lookup_expr = "in" ,
404+ label = "Package URL" ,
405+ help_text = "Filter by one or more Package URLs. Multi-value supported (comma-separated)." ,
406+ )
387407
388408
389409class PackageV2ViewSet (viewsets .ReadOnlyModelViewSet ):
@@ -968,56 +988,54 @@ def get_view_name(self):
968988
969989
970990class AdvisoriesPackageV2ViewSet (viewsets .ReadOnlyModelViewSet ):
971- queryset = PackageV2 .objects .all ().prefetch_related (
972- Prefetch (
973- "affected_by_advisories" ,
974- queryset = AdvisoryV2 .objects .prefetch_related ("fixed_by_packages" ),
975- to_attr = "prefetched_affected_advisories" ,
976- )
977- )
991+ queryset = PackageV2 .objects .all ()
978992 serializer_class = AdvisoryPackageV2Serializer
979- filter_backends = ( filters .DjangoFilterBackend ,)
993+ filter_backends = [ filters .DjangoFilterBackend ]
980994 filterset_class = AdvisoryPackageV2FilterSet
981995
982996 def get_queryset (self ):
983- queryset = super ().get_queryset ()
984- package_purls = self .request .query_params .getlist ("purl" )
985- affected_by_advisory = self .request .query_params .get ("affected_by_advisory" )
986- fixing_advisory = self .request .query_params .get ("fixing_advisory" )
987- if package_purls :
988- queryset = queryset .filter (package_url__in = package_purls )
989- if affected_by_advisory :
990- queryset = queryset .filter (affected_by_advisories__advisory_id = affected_by_advisory )
991- if fixing_advisory :
992- queryset = queryset .filter (fixing_advisories__advisory = fixing_advisory )
993- return queryset .with_is_vulnerable ()
997+ return (
998+ super ()
999+ .get_queryset ()
1000+ .prefetch_related (
1001+ Prefetch (
1002+ "affected_in_impacts" ,
1003+ queryset = ImpactedPackage .objects .select_related ("advisory" ).prefetch_related (
1004+ "fixed_by_packages" ,
1005+ ),
1006+ ),
1007+ Prefetch (
1008+ "fixed_in_impacts" ,
1009+ queryset = ImpactedPackage .objects .select_related ("advisory" ),
1010+ ),
1011+ )
1012+ .with_is_vulnerable ()
1013+ )
9941014
9951015 def list (self , request , * args , ** kwargs ):
996- queryset = self .get_queryset ()
997- # Apply pagination
998- page = self .paginate_queryset (queryset )
1016+ filtered_queryset = self .filter_queryset (self .get_queryset ())
1017+ page = self .paginate_queryset (filtered_queryset )
1018+
1019+ advisories = set ()
9991020 if page is not None :
1000- # Collect only vulnerabilities for packages in the current page
1001- advisories = set ()
10021021 for package in page :
1003- advisories .update (package .affected_by_advisories .all ())
1004- advisories .update (package .fixing_advisories .all ())
1022+ advisories .update ({ impact . advisory for impact in package .affected_in_impacts .all ()} )
1023+ advisories .update ({ impact . advisory for impact in package .fixed_in_impacts .all ()} )
10051024
10061025 # Serialize the vulnerabilities with advisory_id and advisory label as keys
10071026 advisory_data = {f"{ adv .avid } " : AdvisoryV2Serializer (adv ).data for adv in advisories }
10081027
10091028 # Serialize the current page of packages
10101029 serializer = self .get_serializer (page , many = True )
10111030 data = serializer .data
1012- print ( data )
1031+
10131032 # Use 'self.get_paginated_response' to include pagination data
10141033 return self .get_paginated_response ({"advisories" : advisory_data , "packages" : data })
10151034
10161035 # If pagination is not applied, collect vulnerabilities for all packages
1017- advisories = set ()
10181036 for package in queryset :
1019- advisories .update (package .affected_by_vulnerabilities .all ())
1020- advisories .update (package .fixing_vulnerabilities .all ())
1037+ advisories .update ({ impact . advisory for impact in package .affected_in_impacts .all ()} )
1038+ advisories .update ({ impact . advisory for impact in package .fixed_in_impacts .all ()} )
10211039
10221040 advisory_data = {f"{ adv .avid } " : AdvisoryV2Serializer (adv ).data for adv in advisories }
10231041
@@ -1053,13 +1071,28 @@ def bulk_lookup(self, request):
10531071 purls = validated_data .get ("purls" )
10541072
10551073 # Fetch packages matching the provided purls
1056- packages = PackageV2 .objects .for_purls (purls ).with_is_vulnerable ()
1074+ packages = (
1075+ PackageV2 .objects .for_purls (purls )
1076+ .prefetch_related (
1077+ Prefetch (
1078+ "affected_in_impacts" ,
1079+ queryset = ImpactedPackage .objects .select_related ("advisory" ).prefetch_related (
1080+ "fixed_by_packages" ,
1081+ ),
1082+ ),
1083+ Prefetch (
1084+ "fixed_in_impacts" ,
1085+ queryset = ImpactedPackage .objects .select_related ("advisory" ),
1086+ ),
1087+ )
1088+ .with_is_vulnerable ()
1089+ )
10571090
10581091 # Collect vulnerabilities associated with these packages
10591092 advisories = set ()
10601093 for package in packages :
1061- advisories .update (package .affected_by_advisories .all ())
1062- advisories .update (package .fixing_advisories .all ())
1094+ advisories .update ({ impact . advisory for impact in package .affected_in_impacts .all ()} )
1095+ advisories .update ({ impact . advisory for impact in package .fixed_in_impacts .all ()} )
10631096
10641097 # Serialize vulnerabilities with vulnerability_id as keys
10651098 advisory_data = {adv .avid : AdvisoryV2Serializer (adv ).data for adv in advisories }
@@ -1124,6 +1157,20 @@ def bulk_search(self, request):
11241157 PackageV2 .objects .filter (plain_package_url__in = plain_purls )
11251158 .order_by ("plain_package_url" )
11261159 .distinct ("plain_package_url" )
1160+ .prefetch_related (
1161+ Prefetch (
1162+ "affected_in_impacts" ,
1163+ queryset = ImpactedPackage .objects .select_related (
1164+ "advisory"
1165+ ).prefetch_related (
1166+ "fixed_by_packages" ,
1167+ ),
1168+ ),
1169+ Prefetch (
1170+ "fixed_in_impacts" ,
1171+ queryset = ImpactedPackage .objects .select_related ("advisory" ),
1172+ ),
1173+ )
11271174 .with_is_vulnerable ()
11281175 )
11291176
@@ -1132,14 +1179,16 @@ def bulk_search(self, request):
11321179 # Collect vulnerabilities associated with these packages
11331180 advisories = set ()
11341181 for package in packages :
1135- advisories .update (package .affected_by_advisories .all ())
1136- advisories .update (package .fixing_advisories .all ())
1182+ advisories .update ({ impact . advisory for impact in package .affected_in_impacts .all ()} )
1183+ advisories .update ({ impact . advisory for impact in package .fixed_in_impacts .all ()} )
11371184
11381185 advisory_data = {adv .avid : AdvisoryV2Serializer (adv ).data for adv in advisories }
11391186
11401187 if not purl_only :
11411188 package_data = AdvisoryPackageV2Serializer (
1142- packages , many = True , context = {"request" : request }
1189+ packages ,
1190+ many = True ,
1191+ context = {"request" : request },
11431192 ).data
11441193 return Response (
11451194 {
@@ -1154,20 +1203,39 @@ def bulk_search(self, request):
11541203 vulnerable_purls = [str (package .plain_package_url ) for package in vulnerable_purls ]
11551204 return Response (data = vulnerable_purls )
11561205
1157- query = PackageV2 .objects .filter (package_url__in = purls ).distinct ().with_is_vulnerable ()
1206+ query = (
1207+ PackageV2 .objects .filter (package_url__in = purls )
1208+ .order_by ("plain_package_url" )
1209+ .distinct ("plain_package_url" )
1210+ .prefetch_related (
1211+ Prefetch (
1212+ "affected_in_impacts" ,
1213+ queryset = ImpactedPackage .objects .select_related ("advisory" ).prefetch_related (
1214+ "fixed_by_packages" ,
1215+ ),
1216+ ),
1217+ Prefetch (
1218+ "fixed_in_impacts" ,
1219+ queryset = ImpactedPackage .objects .select_related ("advisory" ),
1220+ ),
1221+ )
1222+ .with_is_vulnerable ()
1223+ )
11581224 packages = query
11591225
11601226 # Collect vulnerabilities associated with these packages
11611227 advisories = set ()
11621228 for package in packages :
1163- advisories .update (package .affected_by_advisories .all ())
1164- advisories .update (package .fixing_advisories .all ())
1229+ advisories .update ({ impact . advisory for impact in package .affected_in_impacts .all ()} )
1230+ advisories .update ({ impact . advisory for impact in package .fixed_in_impacts .all ()} )
11651231
11661232 advisory_data = {adv .advisory_id : AdvisoryV2Serializer (adv ).data for adv in advisories }
11671233
11681234 if not purl_only :
11691235 package_data = AdvisoryPackageV2Serializer (
1170- packages , many = True , context = {"request" : request }
1236+ packages ,
1237+ many = True ,
1238+ context = {"request" : request },
11711239 ).data
11721240 return Response (
11731241 {
0 commit comments