|
12 | 12 | import logging |
13 | 13 | from contextlib import suppress |
14 | 14 | from functools import cached_property |
| 15 | +from itertools import groupby |
| 16 | +from operator import attrgetter |
15 | 17 | from typing import Union |
16 | 18 |
|
| 19 | +from cvss.exceptions import CVSS2MalformedError |
| 20 | +from cvss.exceptions import CVSS3MalformedError |
| 21 | +from cvss.exceptions import CVSS4MalformedError |
17 | 22 | from cwe2.database import Database |
18 | 23 | from django.contrib.auth import get_user_model |
19 | 24 | from django.contrib.auth.models import UserManager |
|
43 | 48 |
|
44 | 49 | from aboutcode import hashid |
45 | 50 | from vulnerabilities import utils |
| 51 | +from vulnerabilities.severity_systems import EPSS |
46 | 52 | from vulnerabilities.severity_systems import SCORING_SYSTEMS |
47 | 53 | from vulnerabilities.utils import normalize_purl |
48 | 54 | from vulnerabilities.utils import purl_to_dict |
@@ -371,6 +377,95 @@ def get_related_purls(self): |
371 | 377 | """ |
372 | 378 | return [p.package_url for p in self.packages.distinct().all()] |
373 | 379 |
|
| 380 | + def aggregate_fixed_and_affected_packages(self): |
| 381 | + from vulnerabilities.views import get_purl_version_class |
| 382 | + |
| 383 | + sorted_fixed_by_packages = self.fixed_by_packages.filter(is_ghost=False).order_by( |
| 384 | + "type", "namespace", "name", "qualifiers", "subpath" |
| 385 | + ) |
| 386 | + |
| 387 | + sorted_affected_packages = self.affected_packages.all() |
| 388 | + |
| 389 | + grouped_fixed_by_packages = { |
| 390 | + key: list(group) |
| 391 | + for key, group in groupby( |
| 392 | + sorted_fixed_by_packages, |
| 393 | + key=attrgetter("type", "namespace", "name", "qualifiers", "subpath"), |
| 394 | + ) |
| 395 | + } |
| 396 | + |
| 397 | + all_affected_fixed_by_matches = [] |
| 398 | + |
| 399 | + for sorted_affected_package in sorted_affected_packages: |
| 400 | + affected_fixed_by_matches = { |
| 401 | + "affected_package": sorted_affected_package, |
| 402 | + "matched_fixed_by_packages": [], |
| 403 | + } |
| 404 | + |
| 405 | + # Build the key to find matching group |
| 406 | + key = ( |
| 407 | + sorted_affected_package.type, |
| 408 | + sorted_affected_package.namespace, |
| 409 | + sorted_affected_package.name, |
| 410 | + sorted_affected_package.qualifiers, |
| 411 | + sorted_affected_package.subpath, |
| 412 | + ) |
| 413 | + |
| 414 | + # Get matching group from pre-grouped fixed_by_packages |
| 415 | + matching_fixed_packages = grouped_fixed_by_packages.get(key, []) |
| 416 | + |
| 417 | + # Get version classes for comparison |
| 418 | + affected_version_class = get_purl_version_class(sorted_affected_package) |
| 419 | + affected_version = affected_version_class(sorted_affected_package.version) |
| 420 | + |
| 421 | + # Compare versions and filter valid matches |
| 422 | + matched_fixed_by_packages = [ |
| 423 | + fixed_by_package.purl |
| 424 | + for fixed_by_package in matching_fixed_packages |
| 425 | + if get_purl_version_class(fixed_by_package)(fixed_by_package.version) |
| 426 | + > affected_version |
| 427 | + ] |
| 428 | + |
| 429 | + affected_fixed_by_matches["matched_fixed_by_packages"] = matched_fixed_by_packages |
| 430 | + all_affected_fixed_by_matches.append(affected_fixed_by_matches) |
| 431 | + return sorted_fixed_by_packages, sorted_affected_packages, all_affected_fixed_by_matches |
| 432 | + |
| 433 | + def get_severity_vectors_and_values(self): |
| 434 | + """ |
| 435 | + Collect severity vectors and values, excluding EPSS scoring systems and handling errors gracefully. |
| 436 | + """ |
| 437 | + severity_vectors = [] |
| 438 | + severity_values = set() |
| 439 | + |
| 440 | + # Exclude EPSS scoring system |
| 441 | + base_severities = self.severities.exclude(scoring_system=EPSS.identifier) |
| 442 | + |
| 443 | + # QuerySet for severities with valid scoring_elements and scoring_system in SCORING_SYSTEMS |
| 444 | + valid_scoring_severities = base_severities.filter( |
| 445 | + scoring_elements__isnull=False, scoring_system__in=SCORING_SYSTEMS.keys() |
| 446 | + ) |
| 447 | + |
| 448 | + for severity in valid_scoring_severities: |
| 449 | + try: |
| 450 | + vector_values = SCORING_SYSTEMS[severity.scoring_system].get( |
| 451 | + severity.scoring_elements |
| 452 | + ) |
| 453 | + if vector_values: |
| 454 | + severity_vectors.append(vector_values) |
| 455 | + except ( |
| 456 | + CVSS2MalformedError, |
| 457 | + CVSS3MalformedError, |
| 458 | + CVSS4MalformedError, |
| 459 | + NotImplementedError, |
| 460 | + ) as e: |
| 461 | + logging.error(f"CVSSMalformedError for {severity.scoring_elements}: {e}") |
| 462 | + |
| 463 | + valid_value_severities = base_severities.filter(value__isnull=False).exclude(value="") |
| 464 | + |
| 465 | + severity_values.update(valid_value_severities.values_list("value", flat=True)) |
| 466 | + |
| 467 | + return severity_vectors, severity_values |
| 468 | + |
374 | 469 |
|
375 | 470 | class Weakness(models.Model): |
376 | 471 | """ |
|
0 commit comments