77# See https://aboutcode.org for more information about nexB OSS projects.
88#
99
10+ from collections import defaultdict
1011from typing import List
1112from urllib .parse import urlencode
1213
2122from rest_framework .reverse import reverse
2223from rest_framework .throttling import AnonRateThrottle
2324
25+ from vulnerabilities .models import AdvisoryAlias
2426from vulnerabilities .models import AdvisoryReference
2527from vulnerabilities .models import AdvisorySet
2628from vulnerabilities .models import AdvisorySetMember
@@ -216,6 +218,26 @@ def get_fixing_vulnerabilities_url(self, obj):
216218
217219 def get_affected_by_vulnerabilities (self , package ):
218220 """Return a dictionary with advisory as keys and their details, including fixed_by_packages."""
221+ advisories = self .context ["advisory_map" ].get (package .id , [])
222+ impact_map = self .context ["impact_map" ].get (package .id , {})
223+
224+ if advisories :
225+ result = []
226+
227+ for adv in advisories :
228+ fixed = impact_map .get (adv ["avid" ])
229+ if not fixed :
230+ continue
231+
232+ result .append (
233+ {
234+ ** adv ,
235+ "fixed_by_packages" : fixed ,
236+ }
237+ )
238+
239+ return result
240+
219241 advisories_qs = AdvisoryV2 .objects .latest_affecting_advisories_for_purl (package .package_url )
220242
221243 advisories = []
@@ -250,56 +272,35 @@ def get_affected_by_vulnerabilities(self, package):
250272 "advisory_id" : advisory .advisory_id .split ("/" )[- 1 ],
251273 "aliases" : [alias .alias for alias in advisory .aliases .all ()],
252274 "summary" : advisory .summary ,
253- "fixed_by_packages" : [pkg .purl for pkg in impact .fixed_by_packages .all ()],
254275 "severity" : advisory .weighted_severity ,
255276 "exploitability" : advisory .exploitability ,
256277 "risk_score" : advisory .risk_score ,
278+ "fixed_by_packages" : [pkg .purl for pkg in impact .fixed_by_packages .all ()],
257279 }
258280 )
259281
260282 return result
261283
262- is_grouped = AdvisorySet .objects .filter (package = package , relation_type = "affecting" ).exists ()
263-
264- if is_grouped :
265- affected_by_advisories_qs = (
266- AdvisorySet .objects .filter (package = package , relation_type = "affecting" )
267- .select_related ("primary_advisory" )
268- .prefetch_related (
269- Prefetch (
270- "members" ,
271- queryset = AdvisorySetMember .objects .filter (is_primary = False ).select_related (
272- "advisory"
273- ),
274- to_attr = "secondary_members" ,
275- )
284+ if not advisories :
285+ if package .type in TYPES_WITH_MULTIPLE_IMPORTERS :
286+ advisories_qs = advisories_qs .prefetch_related (
287+ "aliases" ,
288+ "impacted_packages__affecting_packages" ,
289+ "impacted_packages__fixed_by_packages" ,
276290 )
277- )
278-
279- affected_groups = [
280- Group (
281- aliases = list (adv .aliases .all ()),
282- primary = adv .primary_advisory ,
283- secondaries = [member .advisory for member in adv .secondary_members ],
291+ advisories : List [GroupedAdvisory ] = merge_and_save_grouped_advisories (
292+ package , advisories_qs , "affecting"
284293 )
285- for adv in affected_by_advisories_qs
286- ]
294+ return self .return_advisories_data (package , advisories_qs , advisories )
287295
288- advisories : List [GroupedAdvisory ] = get_advisories_from_groups (affected_groups )
289- return self .return_advisories_data (package , advisories_qs , advisories )
296+ def get_fixing_vulnerabilities (self , package ):
297+ fixing_advisories = AdvisorySet .objects .filter (
298+ package = package , relation_type = "fixing"
299+ ).values_list ("primary_advisory__advisory_id" , flat = True )
290300
291- if package .type in TYPES_WITH_MULTIPLE_IMPORTERS :
292- advisories_qs = advisories_qs .prefetch_related (
293- "aliases" ,
294- "impacted_packages__affecting_packages" ,
295- "impacted_packages__fixed_by_packages" ,
296- )
297- advisories : List [GroupedAdvisory ] = merge_and_save_grouped_advisories (
298- package , advisories_qs , "affecting"
299- )
300- return self .return_advisories_data (package , advisories_qs , advisories )
301+ if fixing_advisories :
302+ return [{"advisory_id" : adv_id .split ("/" )[- 1 ]} for adv_id in fixing_advisories ]
301303
302- def get_fixing_vulnerabilities (self , package ):
303304 advisories_qs = AdvisoryV2 .objects .latest_fixed_by_advisories_for_purl (package .package_url )
304305
305306 if not package .type in TYPES_WITH_MULTIPLE_IMPORTERS :
@@ -319,37 +320,6 @@ def get_fixing_vulnerabilities(self, package):
319320 )
320321 return results
321322
322- advisories = []
323-
324- is_grouped = AdvisorySet .objects .filter (package = package , relation_type = "fixing" ).exists ()
325-
326- if is_grouped :
327- fixing_advisories_qs = (
328- AdvisorySet .objects .filter (package = package , relation_type = "fixing" )
329- .select_related ("primary_advisory" )
330- .prefetch_related (
331- Prefetch (
332- "members" ,
333- queryset = AdvisorySetMember .objects .filter (is_primary = False ).select_related (
334- "advisory"
335- ),
336- to_attr = "secondary_members" ,
337- )
338- )
339- )
340-
341- fixing_groups = [
342- Group (
343- aliases = list (adv .aliases .all ()),
344- primary = adv .primary_advisory ,
345- secondaries = [member .advisory for member in adv .secondary_members ],
346- )
347- for adv in fixing_advisories_qs
348- ]
349-
350- advisories : List [GroupedAdvisory ] = get_advisories_from_groups (fixing_groups )
351- return self .return_fixing_advisories_data (advisories )
352-
353323 if package .type in TYPES_WITH_MULTIPLE_IMPORTERS :
354324 advisories_qs = advisories_qs .prefetch_related (
355325 "aliases" ,
@@ -409,11 +379,11 @@ def return_advisories_data(self, package, advisories_qs, advisories):
409379 return result
410380
411381 def get_next_non_vulnerable_version (self , package ):
412- if next_non_vulnerable := package .get_non_vulnerable_versions ()[ 0 ] :
382+ if next_non_vulnerable := package .next_non_vulnerable_version :
413383 return next_non_vulnerable .version
414384
415385 def get_latest_non_vulnerable_version (self , package ):
416- if latest_non_vulnerable := package .get_non_vulnerable_versions ()[ - 1 ] :
386+ if latest_non_vulnerable := package .latest_non_vulnerable_version :
417387 return latest_non_vulnerable .version
418388
419389
@@ -464,13 +434,11 @@ def create(self, request, *args, **kwargs):
464434 query = (
465435 PackageV2 .objects .filter (plain_package_url__in = plain_purls )
466436 .values_list ("plain_package_url" , flat = True )
467- .distinct ()
468437 .order_by ("plain_package_url" )
469438 )
470439 else :
471440 query = (
472441 PackageV2 .objects .filter (package_url__in = purls )
473- .distinct ()
474442 .order_by ("package_url" )
475443 .values_list ("package_url" , flat = True )
476444 )
@@ -479,20 +447,20 @@ def create(self, request, *args, **kwargs):
479447 return self .get_paginated_response (page )
480448
481449 if ignore_qualifiers_subpath :
482- query = (
483- PackageV2 .objects .filter (plain_package_url__in = plain_purls )
484- .order_by ("plain_package_url" )
485- .distinct ("plain_package_url" )
450+ query = PackageV2 .objects .filter (plain_package_url__in = plain_purls ).order_by (
451+ "plain_package_url"
486452 )
487453 else :
488- query = (
489- PackageV2 .objects .filter (package_url__in = purls )
490- .order_by ("package_url" )
491- .distinct ("package_url" )
492- )
454+ query = PackageV2 .objects .filter (package_url__in = purls ).order_by ("package_url" )
493455
494456 page = self .paginate_queryset (query )
495- serializer = self .get_serializer (page , many = True , context = {"request" : request })
457+ advisory_map = get_grouped_advisories_bulk (page )
458+ impact_map = get_impacts_bulk (page )
459+ serializer = self .get_serializer (
460+ page ,
461+ many = True ,
462+ context = {"request" : request , "advisory_map" : advisory_map , "impact_map" : impact_map },
463+ )
496464 return self .get_paginated_response (serializer .data )
497465
498466
@@ -592,3 +560,124 @@ class FixingAdvisoriesViewSet(PackageAdvisoriesViewSet):
592560class AffectedByAdvisoriesViewSet (PackageAdvisoriesViewSet ):
593561 relation = "impacted_packages__affecting_packages__package_url"
594562 serializer_class = AffectedByAdvisoryV3Serializer
563+
564+
565+ def get_grouped_advisories_bulk (packages ):
566+ package_ids = [p .id for p in packages ]
567+
568+ advisory_sets = list (
569+ AdvisorySet .objects .filter (
570+ package_id__in = package_ids ,
571+ relation_type = "affecting" ,
572+ )
573+ .select_related ("primary_advisory" , "package" )
574+ .prefetch_related (
575+ Prefetch ("aliases" , queryset = AdvisoryAlias .objects .only ("alias" )),
576+ Prefetch (
577+ "members" ,
578+ queryset = AdvisorySetMember .objects .filter (is_primary = False )
579+ .select_related ("advisory" )
580+ .only (
581+ "advisory__avid" ,
582+ "advisory__weighted_severity" ,
583+ "advisory__exploitability" ,
584+ ),
585+ to_attr = "secondary_members" ,
586+ ),
587+ )
588+ .only (
589+ "id" ,
590+ "package_id" ,
591+ "primary_advisory__avid" ,
592+ "primary_advisory__summary" ,
593+ "primary_advisory__weighted_severity" ,
594+ "primary_advisory__exploitability" ,
595+ "primary_advisory__advisory_id" ,
596+ )
597+ )
598+
599+ package_map = defaultdict (list )
600+ for adv in advisory_sets :
601+ adv ._aliases_cache = [a .alias for a in adv .aliases .all ()]
602+ package_map [adv .package_id ].append (adv )
603+
604+ result = {}
605+
606+ for package in packages :
607+ groups = package_map .get (package .id , [])
608+ grouped = []
609+
610+ for adv in groups :
611+ primary = adv .primary_advisory
612+ secondaries = [m .advisory for m in adv .secondary_members ]
613+
614+ max_sev = primary .weighted_severity or 0.0
615+ max_exp = primary .exploitability or 0.0
616+
617+ for sec in secondaries :
618+ if sec .weighted_severity :
619+ max_sev = max (max_sev , sec .weighted_severity )
620+ if sec .exploitability :
621+ max_exp = max (max_exp , sec .exploitability )
622+
623+ weighted_severity = round (max_sev , 1 ) if max_sev else None
624+ exploitability = max_exp or None
625+
626+ risk_score = None
627+ if exploitability and weighted_severity :
628+ risk_score = round (min (exploitability * weighted_severity , 10.0 ), 1 )
629+
630+ identifier = primary .advisory_id .split ("/" )[- 1 ]
631+
632+ aliases = [a for a in adv ._aliases_cache if a != identifier ]
633+
634+ grouped .append (
635+ {
636+ "avid" : primary .avid ,
637+ "advisory_id" : identifier ,
638+ "aliases" : aliases ,
639+ "weighted_severity" : weighted_severity ,
640+ "exploitability" : exploitability ,
641+ "risk_score" : risk_score ,
642+ "summary" : primary .summary ,
643+ }
644+ )
645+
646+ result [package .id ] = grouped
647+
648+ return result
649+
650+
651+ def get_impacts_bulk (packages ):
652+ package_ids = [p .id for p in packages ]
653+
654+ impacts = (
655+ ImpactedPackageAffecting .objects .filter (package_id__in = package_ids )
656+ .select_related ("impacted_package__advisory" )
657+ .prefetch_related (
658+ Prefetch (
659+ "impacted_package__fixed_by_packages" ,
660+ queryset = PackageV2 .objects .only ("package_url" ),
661+ )
662+ )
663+ .only (
664+ "package_id" ,
665+ "impacted_package_id" ,
666+ "impacted_package__advisory_id" ,
667+ "impacted_package__advisory__avid" ,
668+ )
669+ )
670+
671+ impact_map = defaultdict (dict )
672+ fixed_cache = {}
673+
674+ for impact in impacts :
675+ ip = impact .impacted_package
676+ avid = ip .advisory .avid
677+
678+ if ip .id not in fixed_cache :
679+ fixed_cache [ip .id ] = list ({pkg .purl for pkg in ip .fixed_by_packages .all ()})
680+
681+ impact_map [impact .package_id ][avid ] = fixed_cache [ip .id ]
682+
683+ return impact_map
0 commit comments