2323from rest_framework .reverse import reverse
2424from rest_framework .throttling import AnonRateThrottle
2525
26+ from vulnerabilities .models import SSVC
2627from vulnerabilities .models import AdvisoryAlias
2728from vulnerabilities .models import AdvisoryReference
2829from vulnerabilities .models import AdvisorySet
2930from vulnerabilities .models import AdvisorySetMember
3031from vulnerabilities .models import AdvisorySeverity
3132from vulnerabilities .models import AdvisoryV2
3233from vulnerabilities .models import AdvisoryWeakness
33- from vulnerabilities .models import Group
3434from vulnerabilities .models import GroupedAdvisory
3535from vulnerabilities .models import ImpactedPackageAffecting
3636from vulnerabilities .models import PackageV2
3737from vulnerabilities .throttling import PermissionBasedUserRateThrottle
3838from vulnerabilities .utils import TYPES_WITH_MULTIPLE_IMPORTERS
39- from vulnerabilities .utils import get_advisories_from_groups
4039from vulnerabilities .utils import merge_and_save_grouped_advisories
4140
4241
@@ -48,6 +47,7 @@ class PackageQuerySerializer(serializers.Serializer):
4847 )
4948 details = serializers .BooleanField (default = False )
5049 ignore_qualifiers_subpath = serializers .BooleanField (default = False )
50+ max_advisories = serializers .IntegerField (default = 100 , min_value = 1 , max_value = 10000 )
5151
5252 def validate (self , data ):
5353 if not data ["purls" ]:
@@ -229,11 +229,17 @@ def get_affected_by_vulnerabilities(self, package):
229229 for adv in advisories :
230230 fixed = impact_map .get (adv ["avid" ])
231231 adv .pop ("avid" , None )
232+ resource_url = None
233+
234+ if request := self .context .get ("request" , None ):
235+ resource_url = adv .pop ("resource_url" , None )
236+ resource_url = request .build_absolute_uri (location = resource_url )
232237
233238 result .append (
234239 {
235240 ** adv ,
236241 "fixed_by_packages" : fixed ,
242+ "resource_url" : resource_url ,
237243 }
238244 )
239245
@@ -247,9 +253,20 @@ def get_affected_by_vulnerabilities(self, package):
247253 advisories_ids = advisories_qs .only ("id" )
248254
249255 advisories_ids = list (advisories_ids [:101 ])
250- if len (advisories_ids ) > 100 :
256+ if len (advisories_ids ) > self . context . get ( "max_advisories" , 100 ) :
251257 return None
252258
259+ advisories_qs = advisories_qs .prefetch_related (
260+ "aliases" ,
261+ Prefetch (
262+ "related_ssvcs" ,
263+ queryset = SSVC .objects .select_related ("source_advisory" ).only (
264+ "id" , "decision" , "options" , "vector" , "source_advisory__url"
265+ ),
266+ to_attr = "prefetched_ssvc_trees" ,
267+ ),
268+ )
269+
253270 advisory_by_avid = {adv .avid : adv for adv in advisories_qs }
254271 avids = advisory_by_avid .keys ()
255272
@@ -265,8 +282,14 @@ def get_affected_by_vulnerabilities(self, package):
265282
266283 for advisory in advisories_qs :
267284 impact = impact_by_avid .get (advisory .avid )
268- if not impact :
269- continue
285+ fixed_by_packages = []
286+ if impact :
287+ fixed_by_packages = [pkg .purl for pkg in impact .fixed_by_packages .all ()]
288+
289+ resource_url = None
290+
291+ if request := self .context .get ("request" , None ):
292+ resource_url = request .build_absolute_uri (location = advisory .get_absolute_url ())
270293
271294 result .append (
272295 {
@@ -277,7 +300,17 @@ def get_affected_by_vulnerabilities(self, package):
277300 "severity" : advisory .weighted_severity ,
278301 "exploitability" : advisory .exploitability ,
279302 "risk_score" : advisory .risk_score ,
280- "fixed_by_packages" : [pkg .purl for pkg in impact .fixed_by_packages .all ()],
303+ "fixed_by_packages" : fixed_by_packages ,
304+ "resource_url" : resource_url ,
305+ "ssvc_trees" : [
306+ {
307+ "vector" : ssvc .vector ,
308+ "decision" : ssvc .decision ,
309+ "options" : ssvc .options ,
310+ "source_url" : ssvc .source_advisory .url ,
311+ }
312+ for ssvc in advisory .prefetched_ssvc_trees
313+ ],
281314 }
282315 )
283316
@@ -297,25 +330,38 @@ def get_affected_by_vulnerabilities(self, package):
297330
298331 def get_fixing_vulnerabilities (self , package ):
299332 advisories = self .context ["fixing_advisory_map" ].get (package .id , [])
300- if advisories :
301- return advisories
333+ results = []
334+ for advisory in advisories :
335+ if request := self .context .get ("request" , None ):
336+ resource_url = request .build_absolute_uri (location = advisory ["resource_url" ])
337+ results .append (
338+ {
339+ "advisory_id" : advisory ["advisory_id" ],
340+ "resource_url" : resource_url ,
341+ }
342+ )
343+ if results :
344+ return results
302345
303346 advisories_qs = AdvisoryV2 .objects .latest_fixed_by_advisories_for_purl (package .package_url )
304347
305348 if not package .type in TYPES_WITH_MULTIPLE_IMPORTERS :
306349 advisories_ids = advisories_qs .only ("id" )
307350
308351 advisories_ids = list (advisories_ids [:101 ])
309- if len (advisories_ids ) > 100 :
352+ if len (advisories_ids ) > self . context . get ( "max_advisories" , 100 ) :
310353 return None
311354
312355 results = []
313356
314357 for advisory in advisories_qs :
358+ if request := self .context .get ("request" , None ):
359+ resource_url = request .build_absolute_uri (location = advisory .get_absolute_url ())
315360 results .append (
316361 {
317362 "advisory_id" : advisory .advisory_id .split ("/" )[- 1 ],
318363 "advisory_uid" : advisory .avid ,
364+ "resource_url" : resource_url ,
319365 }
320366 )
321367 return results
@@ -337,10 +383,16 @@ def return_fixing_advisories_data(self, advisories):
337383 result = []
338384 for advisory in advisories :
339385 assert isinstance (advisory , GroupedAdvisory )
386+ resource_url = None
387+ if request := self .context .get ("request" , None ):
388+ resource_url = request .build_absolute_uri (
389+ location = advisory .advisory .get_absolute_url ()
390+ )
340391 result .append (
341392 {
342393 "advisory_id" : advisory .identifier ,
343394 "advisory_uid" : advisory .advisory .avid ,
395+ "resource_url" : resource_url ,
344396 }
345397 )
346398
@@ -361,9 +413,15 @@ def return_advisories_data(self, package, advisories_qs, advisories):
361413 result = []
362414 for advisory in advisories :
363415 assert isinstance (advisory , GroupedAdvisory )
416+ resource_url = None
417+ fixed_by_packages = []
418+ if request := self .context .get ("request" , None ):
419+ resource_url = request .build_absolute_uri (
420+ location = advisory .advisory .get_absolute_url ()
421+ )
364422 impact = impact_by_avid .get (advisory .advisory .avid )
365423 if not impact :
366- continue
424+ fixed_by_packages = list ( set ([ pkg . purl for pkg in impact . fixed_by_packages . all ()]))
367425
368426 result .append (
369427 {
@@ -374,9 +432,9 @@ def return_advisories_data(self, package, advisories_qs, advisories):
374432 "exploitability" : advisory .exploitability ,
375433 "risk_score" : advisory .risk_score ,
376434 "summary" : advisory .advisory .summary ,
377- "fixed_by_packages" : list (
378- set ([ pkg . purl for pkg in impact . fixed_by_packages . all ()])
379- ) ,
435+ "fixed_by_packages" : fixed_by_packages ,
436+ "resource_url" : resource_url ,
437+ "ssvc_trees" : advisory . ssvc_trees ,
380438 }
381439 )
382440
@@ -405,6 +463,7 @@ def create(self, request, *args, **kwargs):
405463 purls = serializer .validated_data ["purls" ]
406464 details = serializer .validated_data ["details" ]
407465 ignore_qualifiers_subpath = serializer .validated_data ["ignore_qualifiers_subpath" ]
466+ max_advisories = serializer .validated_data ["max_advisories" ]
408467
409468 if not purls :
410469 impacted = ImpactedPackageAffecting .objects .filter (package_id = OuterRef ("id" ))
@@ -469,6 +528,7 @@ def create(self, request, *args, **kwargs):
469528 "advisory_map" : affected_advisory_map ,
470529 "impact_map" : impact_map ,
471530 "fixing_advisory_map" : fixing_advisory_map ,
531+ "max_advisories" : max_advisories ,
472532 },
473533 )
474534 return self .get_paginated_response (serializer .data )
@@ -583,7 +643,25 @@ def get_affected_advisories_bulk(packages):
583643 relation_type = "affecting" ,
584644 )
585645 .select_related ("primary_advisory" )
586- .prefetch_related (Prefetch ("aliases" , queryset = AdvisoryAlias .objects .only ("alias" )))
646+ .prefetch_related (
647+ Prefetch ("aliases" , queryset = AdvisoryAlias .objects .only ("alias" )),
648+ Prefetch (
649+ "members" ,
650+ queryset = AdvisorySetMember .objects .select_related ("advisory" ).prefetch_related (
651+ Prefetch (
652+ "advisory__related_ssvcs" ,
653+ queryset = SSVC .objects .select_related ("source_advisory" ).only (
654+ "id" ,
655+ "options" ,
656+ "decision" ,
657+ "vector" ,
658+ "source_advisory__url" ,
659+ ),
660+ to_attr = "prefetched_ssvc_trees" ,
661+ )
662+ ),
663+ ),
664+ )
587665 .annotate (
588666 max_severity = Max (
589667 "members__advisory__weighted_severity" ,
@@ -627,6 +705,20 @@ def get_affected_advisories_bulk(packages):
627705 identifier = primary .advisory_id .split ("/" )[- 1 ]
628706
629707 aliases = [a for a in adv ._aliases_cache if a != identifier ]
708+ all_ssvc = []
709+
710+ for member in adv .members .all ():
711+ all_ssvc .extend (member .advisory .prefetched_ssvc_trees )
712+
713+ for ssvc in all_ssvc :
714+ all_ssvc .append (
715+ {
716+ "vector" : ssvc .vector ,
717+ "decision" : ssvc .decision ,
718+ "options" : ssvc .options ,
719+ "source_url" : ssvc .source_advisory .url ,
720+ }
721+ )
630722
631723 grouped .append (
632724 {
@@ -637,6 +729,8 @@ def get_affected_advisories_bulk(packages):
637729 "exploitability" : exploitability ,
638730 "risk_score" : risk_score ,
639731 "summary" : primary .summary ,
732+ "resource_url" : primary .get_absolute_url (),
733+ "ssvc_trees" : all_ssvc ,
640734 }
641735 )
642736
@@ -697,17 +791,21 @@ def get_fixing_advisories_bulk(packages):
697791 package_map = defaultdict (list )
698792
699793 for adv in advisory_sets :
700- package_map [adv .package_id ].append (adv .primary_advisory . advisory_id )
794+ package_map [adv .package_id ].append (adv .primary_advisory )
701795
702796 result = {}
703797
704798 for package in packages :
705799 groups = package_map .get (package .id , [])
706800 grouped = []
707801
708- for adv_id in groups :
802+ for advisory in groups :
709803 grouped .append (
710- {"advisory_id" : adv_id .split ("/" )[- 1 ], "advisory_uid" : adv_id .split ("/" )[- 1 ]}
804+ {
805+ "advisory_id" : advisory .advisory_id .split ("/" )[- 1 ],
806+ "resource_url" : advisory .get_absolute_url (),
807+ "advisory_uid" : advisory .avid ,
808+ }
711809 )
712810
713811 result [package .id ] = grouped
0 commit comments