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" ]:
@@ -228,11 +228,17 @@ def get_affected_by_vulnerabilities(self, package):
228228 for adv in advisories :
229229 fixed = impact_map .get (adv ["avid" ])
230230 adv .pop ("avid" , None )
231+ resource_url = None
232+
233+ if request := self .context .get ("request" , None ):
234+ resource_url = adv .pop ("resource_url" , None )
235+ resource_url = request .build_absolute_uri (location = resource_url )
231236
232237 result .append (
233238 {
234239 ** adv ,
235240 "fixed_by_packages" : fixed ,
241+ "resource_url" : resource_url ,
236242 }
237243 )
238244
@@ -246,9 +252,20 @@ def get_affected_by_vulnerabilities(self, package):
246252 advisories_ids = advisories_qs .only ("id" )
247253
248254 advisories_ids = list (advisories_ids [:101 ])
249- if len (advisories_ids ) > 100 :
255+ if len (advisories_ids ) > self . context . get ( "max_advisories" , 100 ) :
250256 return None
251257
258+ advisories_qs = advisories_qs .prefetch_related (
259+ "aliases" ,
260+ Prefetch (
261+ "related_ssvcs" ,
262+ queryset = SSVC .objects .select_related ("source_advisory" ).only (
263+ "id" , "decision" , "options" , "vector" , "source_advisory__url"
264+ ),
265+ to_attr = "prefetched_ssvc_trees" ,
266+ ),
267+ )
268+
252269 advisory_by_avid = {adv .avid : adv for adv in advisories_qs }
253270 avids = advisory_by_avid .keys ()
254271
@@ -264,8 +281,14 @@ def get_affected_by_vulnerabilities(self, package):
264281
265282 for advisory in advisories_qs :
266283 impact = impact_by_avid .get (advisory .avid )
267- if not impact :
268- continue
284+ fixed_by_packages = []
285+ if impact :
286+ fixed_by_packages = [pkg .purl for pkg in impact .fixed_by_packages .all ()]
287+
288+ resource_url = None
289+
290+ if request := self .context .get ("request" , None ):
291+ resource_url = request .build_absolute_uri (location = advisory .get_absolute_url ())
269292
270293 result .append (
271294 {
@@ -275,7 +298,17 @@ def get_affected_by_vulnerabilities(self, package):
275298 "severity" : advisory .weighted_severity ,
276299 "exploitability" : advisory .exploitability ,
277300 "risk_score" : advisory .risk_score ,
278- "fixed_by_packages" : [pkg .purl for pkg in impact .fixed_by_packages .all ()],
301+ "fixed_by_packages" : fixed_by_packages ,
302+ "resource_url" : resource_url ,
303+ "ssvc_trees" : [
304+ {
305+ "vector" : ssvc .vector ,
306+ "decision" : ssvc .decision ,
307+ "options" : ssvc .options ,
308+ "source_url" : ssvc .source_advisory .url ,
309+ }
310+ for ssvc in advisory .prefetched_ssvc_trees
311+ ],
279312 }
280313 )
281314
@@ -295,24 +328,37 @@ def get_affected_by_vulnerabilities(self, package):
295328
296329 def get_fixing_vulnerabilities (self , package ):
297330 advisories = self .context ["fixing_advisory_map" ].get (package .id , [])
298- if advisories :
299- return advisories
331+ results = []
332+ for advisory in advisories :
333+ if request := self .context .get ("request" , None ):
334+ resource_url = request .build_absolute_uri (location = advisory ["resource_url" ])
335+ results .append (
336+ {
337+ "advisory_id" : advisory ["advisory_id" ],
338+ "resource_url" : resource_url ,
339+ }
340+ )
341+ if results :
342+ return results
300343
301344 advisories_qs = AdvisoryV2 .objects .latest_fixed_by_advisories_for_purl (package .package_url )
302345
303346 if not package .type in TYPES_WITH_MULTIPLE_IMPORTERS :
304347 advisories_ids = advisories_qs .only ("id" )
305348
306349 advisories_ids = list (advisories_ids [:101 ])
307- if len (advisories_ids ) > 100 :
350+ if len (advisories_ids ) > self . context . get ( "max_advisories" , 100 ) :
308351 return None
309352
310353 results = []
311354
312355 for advisory in advisories_qs :
356+ if request := self .context .get ("request" , None ):
357+ resource_url = request .build_absolute_uri (location = advisory .get_absolute_url ())
313358 results .append (
314359 {
315360 "advisory_id" : advisory .advisory_id .split ("/" )[- 1 ],
361+ "resource_url" : resource_url ,
316362 }
317363 )
318364 return results
@@ -334,9 +380,15 @@ def return_fixing_advisories_data(self, advisories):
334380 result = []
335381 for advisory in advisories :
336382 assert isinstance (advisory , GroupedAdvisory )
383+ resource_url = None
384+ if request := self .context .get ("request" , None ):
385+ resource_url = request .build_absolute_uri (
386+ location = advisory .advisory .get_absolute_url ()
387+ )
337388 result .append (
338389 {
339390 "advisory_id" : advisory .identifier ,
391+ "resource_url" : resource_url ,
340392 }
341393 )
342394
@@ -357,9 +409,15 @@ def return_advisories_data(self, package, advisories_qs, advisories):
357409 result = []
358410 for advisory in advisories :
359411 assert isinstance (advisory , GroupedAdvisory )
412+ resource_url = None
413+ fixed_by_packages = []
414+ if request := self .context .get ("request" , None ):
415+ resource_url = request .build_absolute_uri (
416+ location = advisory .advisory .get_absolute_url ()
417+ )
360418 impact = impact_by_avid .get (advisory .advisory .avid )
361419 if not impact :
362- continue
420+ fixed_by_packages = list ( set ([ pkg . purl for pkg in impact . fixed_by_packages . all ()]))
363421
364422 result .append (
365423 {
@@ -369,9 +427,9 @@ def return_advisories_data(self, package, advisories_qs, advisories):
369427 "exploitability" : advisory .exploitability ,
370428 "risk_score" : advisory .risk_score ,
371429 "summary" : advisory .advisory .summary ,
372- "fixed_by_packages" : list (
373- set ([ pkg . purl for pkg in impact . fixed_by_packages . all ()])
374- ) ,
430+ "fixed_by_packages" : fixed_by_packages ,
431+ "resource_url" : resource_url ,
432+ "ssvc_trees" : advisory . ssvc_trees ,
375433 }
376434 )
377435
@@ -400,6 +458,7 @@ def create(self, request, *args, **kwargs):
400458 purls = serializer .validated_data ["purls" ]
401459 details = serializer .validated_data ["details" ]
402460 ignore_qualifiers_subpath = serializer .validated_data ["ignore_qualifiers_subpath" ]
461+ max_advisories = serializer .validated_data ["max_advisories" ]
403462
404463 if not purls :
405464 impacted = ImpactedPackageAffecting .objects .filter (package_id = OuterRef ("id" ))
@@ -464,6 +523,7 @@ def create(self, request, *args, **kwargs):
464523 "advisory_map" : affected_advisory_map ,
465524 "impact_map" : impact_map ,
466525 "fixing_advisory_map" : fixing_advisory_map ,
526+ "max_advisories" : max_advisories ,
467527 },
468528 )
469529 return self .get_paginated_response (serializer .data )
@@ -576,7 +636,25 @@ def get_affected_advisories_bulk(packages):
576636 relation_type = "affecting" ,
577637 )
578638 .select_related ("primary_advisory" )
579- .prefetch_related (Prefetch ("aliases" , queryset = AdvisoryAlias .objects .only ("alias" )))
639+ .prefetch_related (
640+ Prefetch ("aliases" , queryset = AdvisoryAlias .objects .only ("alias" )),
641+ Prefetch (
642+ "members" ,
643+ queryset = AdvisorySetMember .objects .select_related ("advisory" ).prefetch_related (
644+ Prefetch (
645+ "advisory__related_ssvcs" ,
646+ queryset = SSVC .objects .select_related ("source_advisory" ).only (
647+ "id" ,
648+ "options" ,
649+ "decision" ,
650+ "vector" ,
651+ "source_advisory__url" ,
652+ ),
653+ to_attr = "prefetched_ssvc_trees" ,
654+ )
655+ ),
656+ ),
657+ )
580658 .annotate (
581659 max_severity = Max (
582660 "members__advisory__weighted_severity" ,
@@ -620,6 +698,20 @@ def get_affected_advisories_bulk(packages):
620698 identifier = primary .advisory_id .split ("/" )[- 1 ]
621699
622700 aliases = [a for a in adv ._aliases_cache if a != identifier ]
701+ all_ssvc = []
702+
703+ for member in adv .members .all ():
704+ all_ssvc .extend (member .advisory .prefetched_ssvc_trees )
705+
706+ for ssvc in all_ssvc :
707+ all_ssvc .append (
708+ {
709+ "vector" : ssvc .vector ,
710+ "decision" : ssvc .decision ,
711+ "options" : ssvc .options ,
712+ "source_url" : ssvc .source_advisory .url ,
713+ }
714+ )
623715
624716 grouped .append (
625717 {
@@ -630,6 +722,8 @@ def get_affected_advisories_bulk(packages):
630722 "exploitability" : exploitability ,
631723 "risk_score" : risk_score ,
632724 "summary" : primary .summary ,
725+ "resource_url" : primary .get_absolute_url (),
726+ "ssvc_trees" : all_ssvc ,
633727 }
634728 )
635729
@@ -690,16 +784,21 @@ def get_fixing_advisories_bulk(packages):
690784 package_map = defaultdict (list )
691785
692786 for adv in advisory_sets :
693- package_map [adv .package_id ].append (adv .primary_advisory . advisory_id )
787+ package_map [adv .package_id ].append (adv .primary_advisory )
694788
695789 result = {}
696790
697791 for package in packages :
698792 groups = package_map .get (package .id , [])
699793 grouped = []
700794
701- for adv_id in groups :
702- grouped .append ({"advisory_id" : adv_id .split ("/" )[- 1 ]})
795+ for advisory in groups :
796+ grouped .append (
797+ {
798+ "advisory_id" : advisory .advisory_id .split ("/" )[- 1 ],
799+ "resource_url" : advisory .get_absolute_url (),
800+ }
801+ )
703802
704803 result [package .id ] = grouped
705804
0 commit comments