Skip to content

Commit 9bcb298

Browse files
committed
Add ImpactedPackage model to track AffectedPackageV2 data
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent a5fdd6a commit 9bcb298

File tree

2 files changed

+144
-67
lines changed

2 files changed

+144
-67
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Generated by Django 4.2.22 on 2025-07-18 16:52
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("vulnerabilities", "0099_advisoryv2_original_advisory_text"),
11+
]
12+
13+
operations = [
14+
migrations.RemoveField(
15+
model_name="advisoryv2",
16+
name="affecting_packages",
17+
),
18+
migrations.RemoveField(
19+
model_name="advisoryv2",
20+
name="fixed_by_packages",
21+
),
22+
migrations.CreateModel(
23+
name="ImpactedPackage",
24+
fields=[
25+
(
26+
"id",
27+
models.AutoField(
28+
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
29+
),
30+
),
31+
(
32+
"base_purl",
33+
models.CharField(
34+
blank=True,
35+
help_text="Version less PURL related to impacted range.",
36+
max_length=500,
37+
),
38+
),
39+
(
40+
"affecting_vers",
41+
models.CharField(
42+
blank=True,
43+
help_text="VersionRange expression for package vulnerable to this impact.",
44+
max_length=500,
45+
),
46+
),
47+
(
48+
"fixed_vers",
49+
models.CharField(
50+
blank=True,
51+
help_text="VersionRange expression for packages fixing the vulnerable package in this impact.",
52+
max_length=500,
53+
),
54+
),
55+
(
56+
"advisory",
57+
models.ForeignKey(
58+
on_delete=django.db.models.deletion.CASCADE,
59+
related_name="impacted_packages",
60+
to="vulnerabilities.advisoryv2",
61+
),
62+
),
63+
(
64+
"affecting_packages",
65+
models.ManyToManyField(
66+
help_text="Packages vulnerable to this impact.",
67+
related_name="affected_in_impacts",
68+
to="vulnerabilities.packagev2",
69+
),
70+
),
71+
(
72+
"fixed_by_packages",
73+
models.ManyToManyField(
74+
help_text="Packages vulnerable to this impact.",
75+
related_name="fixed_in_impacts",
76+
to="vulnerabilities.packagev2",
77+
),
78+
),
79+
],
80+
options={
81+
"indexes": [
82+
models.Index(fields=["affecting_vers"], name="vulnerabili_affecti_0092a7_idx"),
83+
models.Index(fields=["fixed_vers"], name="vulnerabili_fixed_v_0fda9f_idx"),
84+
],
85+
},
86+
),
87+
]

vulnerabilities/models.py

Lines changed: 57 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2750,18 +2750,6 @@ class AdvisoryV2(models.Model):
27502750
help_text="Raw advisory data as collected from the upstream datasource.",
27512751
)
27522752

2753-
affecting_packages = models.ManyToManyField(
2754-
"PackageV2",
2755-
related_name="affected_by_advisories",
2756-
help_text="A list of packages that are affected by this advisory.",
2757-
)
2758-
2759-
fixed_by_packages = models.ManyToManyField(
2760-
"PackageV2",
2761-
related_name="fixing_advisories",
2762-
help_text="A list of packages that are reported by this advisory.",
2763-
)
2764-
27652753
status = models.IntegerField(
27662754
choices=AdvisoryStatusType.choices, default=AdvisoryStatusType.PUBLISHED
27672755
)
@@ -2816,18 +2804,17 @@ def get_absolute_url(self):
28162804
"""
28172805
return reverse("advisory_details", args=[self.avid])
28182806

2819-
def to_advisory_data(self) -> "AdvisoryDataV2":
2820-
from vulnerabilities.importer import AdvisoryDataV2
2821-
from vulnerabilities.importer import AffectedPackage
2807+
def to_advisory_data(self) -> "AdvisoryData":
2808+
from vulnerabilities.importer import AdvisoryData
28222809
from vulnerabilities.importer import ReferenceV2
28232810

2824-
return AdvisoryDataV2(
2811+
return AdvisoryData(
28252812
aliases=[item.alias for item in self.aliases.all()],
28262813
summary=self.summary,
28272814
affected_packages=[
2828-
AffectedPackage.from_dict(pkg) for pkg in self.affected_packages if pkg
2815+
impacted.to_affected_package() for impacted in self.impacted_packages.all()
28292816
],
2830-
references=[ReferenceV2.from_dict(ref) for ref in self.references],
2817+
references_v2=[ReferenceV2.from_dict(ref) for ref in self.references],
28312818
date_published=self.date_published,
28322819
weaknesses=self.weaknesses,
28332820
severities=self.severities,
@@ -2841,66 +2828,69 @@ def get_aliases(self):
28412828
"""
28422829
return self.aliases.all()
28432830

2844-
def aggregate_fixed_and_affected_packages(self):
2845-
from vulnerabilities.utils import get_purl_version_class
2846-
2847-
sorted_fixed_by_packages = self.fixed_by_packages.filter(is_ghost=False).order_by(
2848-
"type", "namespace", "name", "qualifiers", "subpath"
2849-
)
2850-
2851-
if sorted_fixed_by_packages:
2852-
sorted_fixed_by_packages.first().calculate_version_rank
2831+
alias = get_aliases
28532832

2854-
sorted_affected_packages = self.affecting_packages.all()
28552833

2856-
if sorted_affected_packages:
2857-
sorted_affected_packages.first().calculate_version_rank
2834+
class ImpactedPackage(models.Model):
2835+
"""
2836+
Represents a single impact for an advisory, including affected range and fixed version and
2837+
associated package relations.
2838+
"""
28582839

2859-
grouped_fixed_by_packages = {
2860-
key: list(group)
2861-
for key, group in groupby(
2862-
sorted_fixed_by_packages,
2863-
key=attrgetter("type", "namespace", "name", "qualifiers", "subpath"),
2864-
)
2865-
}
2840+
advisory = models.ForeignKey(
2841+
AdvisoryV2,
2842+
related_name="impacted_packages",
2843+
on_delete=models.CASCADE,
2844+
)
28662845

2867-
all_affected_fixed_by_matches = []
2846+
base_purl = models.CharField(
2847+
max_length=500,
2848+
blank=True,
2849+
help_text="Version less PURL related to impacted range.",
2850+
)
28682851

2869-
for sorted_affected_package in sorted_affected_packages:
2870-
affected_fixed_by_matches = {
2871-
"affected_package": sorted_affected_package,
2872-
"matched_fixed_by_packages": [],
2873-
}
2852+
affecting_vers = models.CharField(
2853+
max_length=500,
2854+
blank=True,
2855+
help_text="VersionRange expression for package vulnerable to this impact.",
2856+
)
28742857

2875-
# Build the key to find matching group
2876-
key = (
2877-
sorted_affected_package.type,
2878-
sorted_affected_package.namespace,
2879-
sorted_affected_package.name,
2880-
sorted_affected_package.qualifiers,
2881-
sorted_affected_package.subpath,
2882-
)
2858+
fixed_vers = models.CharField(
2859+
max_length=500,
2860+
blank=True,
2861+
help_text="VersionRange expression for packages fixing the vulnerable package in this impact.",
2862+
)
28832863

2884-
# Get matching group from pre-grouped fixed_by_packages
2885-
matching_fixed_packages = grouped_fixed_by_packages.get(key, [])
2864+
affecting_packages = models.ManyToManyField(
2865+
"PackageV2",
2866+
related_name="affected_in_impacts",
2867+
help_text="Packages vulnerable to this impact.",
2868+
)
28862869

2887-
# Get version classes for comparison
2888-
affected_version_class = get_purl_version_class(sorted_affected_package)
2889-
affected_version = affected_version_class(sorted_affected_package.version)
2870+
fixed_by_packages = models.ManyToManyField(
2871+
"PackageV2",
2872+
related_name="fixed_in_impacts",
2873+
help_text="Packages vulnerable to this impact.",
2874+
)
28902875

2891-
# Compare versions and filter valid matches
2892-
matched_fixed_by_packages = [
2893-
fixed_by_package.purl
2894-
for fixed_by_package in matching_fixed_packages
2895-
if get_purl_version_class(fixed_by_package)(fixed_by_package.version)
2896-
> affected_version
2897-
]
2876+
class Meta:
2877+
indexes = [
2878+
models.Index(fields=["affecting_vers"]),
2879+
models.Index(fields=["fixed_vers"]),
2880+
]
28982881

2899-
affected_fixed_by_matches["matched_fixed_by_packages"] = matched_fixed_by_packages
2900-
all_affected_fixed_by_matches.append(affected_fixed_by_matches)
2901-
return sorted_fixed_by_packages, sorted_affected_packages, all_affected_fixed_by_matches
2882+
def to_affected_package(self):
2883+
"""Return `AffectedPackageV2` data from the impact."""
2884+
from vulnerabilities.importer import AffectedPackageV2
2885+
from vulnerabilities.utils import purl_to_dict
29022886

2903-
alias = get_aliases
2887+
return AffectedPackageV2.from_dict(
2888+
affected_pkg={
2889+
"package": purl_to_dict(self.base_purl),
2890+
"affected_version_range": self.affecting_vers,
2891+
"fixed_version_range": self.fixed_vers,
2892+
}
2893+
)
29042894

29052895

29062896
class ToDoRelatedAdvisory(models.Model):

0 commit comments

Comments
 (0)