Skip to content

Commit 4dc91d7

Browse files
committed
feat: precompute curation items for the package curation dashboard
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent 3e9d8c0 commit 4dc91d7

1 file changed

Lines changed: 107 additions & 12 deletions

File tree

vulnerabilities/pipelines/v2_improvers/compute_advisory_todo.py

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from django.db.models import Prefetch
1818
from django.utils import timezone
1919
from packageurl import PackageURL
20+
from univers.version_range import RANGE_CLASS_BY_SCHEMES
2021

2122
from vulnerabilities.importer import AdvisoryDataV2
2223
from 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 " ".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+
498592
def 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

Comments
 (0)