1717from django .db .models import Prefetch
1818from django .utils import timezone
1919from packageurl import PackageURL
20+ from univers .version_range import RANGE_CLASS_BY_SCHEMES
2021
2122from vulnerabilities .importer import AdvisoryDataV2
2223from vulnerabilities .models import AdvisoryAlias
@@ -281,7 +282,7 @@ def check_missing_summary(
281282 todo_to_create ,
282283 advisory_relation_to_create ,
283284):
284- alias = advisory .datasource_id .rsplit ("/" , 1 )[- 1 ]
285+ alias = advisory .advisory_id .rsplit ("/" , 1 )[- 1 ]
285286 oldest_advisory_date = advisory .date_published or advisory .date_collected
286287 if not advisory .summary :
287288 todo = AdvisoryToDoV2 (
@@ -333,7 +334,7 @@ def check_missing_affected_and_fixed_by_packages(
333334 elif not has_fixed_package :
334335 issue_type = "MISSING_FIXED_BY_PACKAGE"
335336
336- alias = advisory .datasource_id .rsplit ("/" , 1 )[- 1 ]
337+ alias = advisory .advisory_id .rsplit ("/" , 1 )[- 1 ]
337338 oldest_advisory_date = advisory .date_published or advisory .date_collected
338339 if issue_type :
339340 todo = AdvisoryToDoV2 (
@@ -360,12 +361,12 @@ def compute_version_range_disagreement(adv_map):
360361 fixed_intersection = set .intersection (* fixed_sets )
361362
362363 return {
363- "affected_union" : affected_union ,
364- "affected_intersection" : affected_intersection ,
365- "affected_disagreement" : affected_union - affected_intersection ,
366- "fixed_union" : fixed_union ,
367- "fixed_intersection" : fixed_intersection ,
368- "fixed_disagreement" : fixed_union - fixed_intersection ,
364+ "affected_union" : list ( affected_union ) ,
365+ "affected_intersection" : list ( affected_intersection ) ,
366+ "affected_disagreement" : list ( affected_union - affected_intersection ) ,
367+ "fixed_union" : list ( fixed_union ) ,
368+ "fixed_intersection" : list ( fixed_intersection ) ,
369+ "fixed_disagreement" : list ( fixed_union - fixed_intersection ) ,
369370 }
370371
371372
@@ -417,6 +418,7 @@ def check_conflicting_affected_and_fixed_by_packages_for_alias(
417418 """
418419 conflicting_package_details = {}
419420
421+ curation_items = []
420422 has_conflicting_affected_packages = False
421423 has_conflicting_fixed_package = False
422424 conflicting_advisories = set ()
@@ -433,6 +435,9 @@ def check_conflicting_affected_and_fixed_by_packages_for_alias(
433435 conflicting_package_details [purl ] = {
434436 "avids" : list (adv_map .keys ()),
435437 }
438+ curation_items .append (
439+ get_grouped_curation_advisories_for_dashboard_ui (purl , adv_map , result , advisories )
440+ )
436441 conflicting_advisories .update ([advisories [avid ] for avid in adv_map ])
437442 conflicting_package_details [purl ].update (result )
438443
@@ -462,6 +467,7 @@ def check_conflicting_affected_and_fixed_by_packages_for_alias(
462467 "conflict_checksum" : conflict_checksum ,
463468 "conflict_details" : conflicting_package_details ,
464469 "partial_curation_advisory" : partial_merged_advisory ,
470+ "curation_items" : curation_items ,
465471 }
466472
467473 todo_id = advisories_checksum (conflicting_advisories )
@@ -484,7 +490,7 @@ def check_conflicting_affected_and_fixed_by_packages_for_alias(
484490 todo = AdvisoryToDoV2 (
485491 related_advisories_id = todo_id ,
486492 issue_type = issue_type ,
487- issue_detail = json . dumps ( issue_detail , default = list ) ,
493+ issue_detail = issue_detail ,
488494 alias = alias ,
489495 advisories_count = conflicting_advisories_count ,
490496 oldest_advisory_date = date_published or date_collected ,
@@ -495,6 +501,94 @@ def check_conflicting_affected_and_fixed_by_packages_for_alias(
495501 return conflicting_package_count , conflicting_advisories_count
496502
497503
504+ def get_disagreement_message (fixed_disagreement , affected_disagreement ):
505+ messages = []
506+
507+ if affected_disagreement :
508+ affected = ", " .join (affected_disagreement )
509+ noun = "version" if len (affected_disagreement ) == 1 else "versions"
510+ verb = "is" if len (affected_disagreement ) == 1 else "are"
511+
512+ messages .append (f"Advisories do not agree whether { noun } { affected } { verb } affected." )
513+
514+ if fixed_disagreement :
515+ fixed = ", " .join (fixed_disagreement )
516+ noun = "version" if len (fixed_disagreement ) == 1 else "versions"
517+ verb = "contains" if len (fixed_disagreement ) == 1 else "contain"
518+
519+ messages .append (f"Advisories do not agree whether { noun } { fixed } { verb } the fix." )
520+
521+ return "\n " .join (messages )
522+
523+
524+ def get_grouped_curation_advisories_for_dashboard_ui (purl , adv_map , conflict_detail , advisories ):
525+ """
526+ Return curation details for the PURL, grouping advisories with similar conflicts based on precedence.
527+ """
528+ curation_item = {
529+ "purl" : purl ,
530+ "partial_curation" : {
531+ "affected" : list (conflict_detail ["affected_intersection" ]),
532+ "fixing" : list (conflict_detail ["fixed_intersection" ]),
533+ },
534+ "advisories" : [],
535+ }
536+
537+ all_versions = conflict_detail ["affected_union" ] + conflict_detail ["fixed_union" ]
538+ package_url = PackageURL .from_string (purl )
539+ range_class = RANGE_CLASS_BY_SCHEMES [package_url .type ]
540+ version_class = range_class .version_class
541+ sorted_versions = sorted ([version_class (v ) for v in all_versions ])
542+ curation_item ["all_versions" ] = [str (v ) for v in sorted_versions ]
543+ curation_item ["conflict_reason" ] = get_disagreement_message (
544+ fixed_disagreement = conflict_detail ["fixed_disagreement" ],
545+ affected_disagreement = conflict_detail ["affected_disagreement" ],
546+ )
547+ advisory_by_conflict_range = defaultdict (list )
548+ conflict_ranges = {}
549+ for avid , packages in adv_map .items ():
550+ conflict_checksum = sha256_digest (
551+ canonical_value (
552+ {
553+ "affected" : packages ["affected" ],
554+ "fixed" : packages ["fixed" ],
555+ }
556+ )
557+ )
558+ if conflict_checksum not in conflict_ranges :
559+ conflict_ranges [conflict_checksum ] = {
560+ "affected" : list (packages ["affected" ]),
561+ "fixing" : list (packages ["fixed" ]),
562+ }
563+
564+ advisory_item = {}
565+ advisory_item ["advisory_uid" ] = avid
566+ advisory_item ["vers_ranges" ] = []
567+ advisory = advisories [avid ]
568+ advisory_item ["precedence" ] = advisory .precedence
569+ advisory_item ["advisory_id" ] = advisory .advisory_id
570+ advisory_item ["datasource_id" ] = advisory .datasource_id
571+ for impact in advisory .impacted_packages .all ():
572+ if impact .base_purl != purl :
573+ continue
574+ advisory_item ["vers_ranges" ].append (
575+ {
576+ "affected_vers" : impact .affecting_vers ,
577+ "fixing_vers" : impact .fixed_vers ,
578+ }
579+ )
580+
581+ advisory_by_conflict_range [conflict_checksum ].append (advisory_item )
582+
583+ for checksum , adv_items in advisory_by_conflict_range .items ():
584+ primary , * secondaries = sorted (adv_items , key = lambda x : x ["precedence" ], reverse = True )
585+ conflict_ranges [checksum ]["primary" ] = primary
586+ conflict_ranges [checksum ]["secondaries" ] = secondaries
587+
588+ curation_item ["advisories" ] = list (conflict_ranges .values ())
589+ return curation_item
590+
591+
498592def get_advisory_with_best_impact_for_purls (purl_adv_map , conflicting_avids ):
499593 """
500594 Return PURL - AVID mapping for packages.
@@ -595,9 +689,10 @@ def merged_advisory(advisories, best_purl_avid_impact_map, conflicting_package_d
595689 )
596690
597691 for summary , avids in seen_summaries .values ():
598- merged_summary .append (f"{ tuple (sorted (avids ))} : { summary } " )
692+ avids_str = ", " .join (sorted (avids ))
693+ merged_summary .append (f"[{ avids_str } ]: { summary } " )
599694
600- merged_adv ["summary" ] = "\n " .join (merged_summary )
695+ merged_adv ["summary" ] = "\n \n " .join (merged_summary )
601696 merged_adv ["aliases" ] = list (merged_adv ["aliases" ])
602697 merged_adv ["weaknesses" ] = list (merged_adv ["weaknesses" ])
603698
@@ -624,7 +719,7 @@ def bulk_create_with_m2m(todos, advisories, logger):
624719 try :
625720 AdvisoryToDoV2 .objects .bulk_create (objs = todos , ignore_conflicts = True )
626721 except Exception as e :
627- logger (f"Error creating AdvisoryToDo : { e } " )
722+ logger (f"Error creating AdvisoryToDoV2 : { e } " )
628723
629724 new_todos = AdvisoryToDoV2 .objects .filter (created_at__gte = start_time )
630725
0 commit comments