Skip to content

Commit 28111c8

Browse files
committed
models and migrations
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 17e2db0 commit 28111c8

7 files changed

Lines changed: 170 additions & 106 deletions

File tree

component_catalog/tests/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ def test_component_details_view_tab_vulnerabilities(self):
10541054
)
10551055
self.assertContains(response, expected)
10561056
self.assertContains(response, 'id="tab_vulnerabilities"')
1057-
self.assertContains(response, vulnerability1.vcid)
1057+
self.assertContains(response, vulnerability1.vulnerability_id)
10581058

10591059
def test_component_catalog_component_create_ajax_view(self):
10601060
component_create_ajax_url = reverse("component_catalog:component_add_ajax")
@@ -3020,7 +3020,7 @@ def test_package_details_view_tab_vulnerabilities(self):
30203020
)
30213021
self.assertContains(response, expected)
30223022
self.assertContains(response, 'id="tab_vulnerabilities"')
3023-
self.assertContains(response, self.vulnerability1.vcid)
3023+
self.assertContains(response, self.vulnerability1.vulnerability_id)
30243024

30253025
def test_vulnerablecode_get_plain_purls(self):
30263026
purls = get_plain_purls(packages=[])

product_portfolio/templates/product_portfolio/compliance/compliance_watchlist_card.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ <h2 class="card-header fw-bold px-2 h6 d-flex justify-content-between align-item
4444
<a href="{{ product_url }}?vulnerabilities-weighted_risk_score=low#vulnerabilities" class="badge bg-info-subtle text-info-emphasis{% if product.critical_count or product.high_count or product.medium_count %} ms-1{% endif %}">{{ product.low_count }} low</a>
4545
{% endif %}
4646
{% if not product.critical_count and not product.high_count and not product.medium_count and not product.low_count %}
47-
<span class="text-body-tertiary">None</span>
47+
<span class="text-body-tertiary small">None</span>
4848
{% endif %}
4949
</div>
5050
</div>

product_portfolio/tests/test_views.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ def test_product_portfolio_tab_vulnerability_view_packages_row_rendering(self):
328328
expected = f"""
329329
<span data-bs-toggle="modal"
330330
data-bs-target="#vulnerability-analysis-modal"
331-
data-vulnerability-id="{vulnerability1.vcid}"
331+
data-vulnerability-id="{vulnerability1.vulnerability_id}"
332332
data-package-identifier="{p1}"
333-
data-edit-url="/products/vulnerability_analysis/{pp1.uuid}/{vulnerability1.vcid}/"
333+
data-edit-url="/products/vulnerability_analysis/{pp1.uuid}/{vulnerability1.vulnerability_id}/"
334334
>
335335
<button type="button" data-bs-toggle="tooltip" title="Edit" class="btn btn-link p-0"
336336
aria-label="Edit">
@@ -375,15 +375,15 @@ def test_product_portfolio_tab_vulnerability_risk_threshold(self):
375375

376376
url = product1.get_url("tab_vulnerabilities")
377377
response = self.client.get(url)
378-
self.assertContains(response, vulnerability1.vcid)
379-
self.assertContains(response, vulnerability2.vcid)
378+
self.assertContains(response, vulnerability1.vulnerability_id)
379+
self.assertContains(response, vulnerability2.vulnerability_id)
380380
self.assertContains(response, "2 results")
381381
self.assertNotContains(response, "A risk threshold filter at")
382382

383383
product1.update(vulnerabilities_risk_threshold=3.0)
384384
response = self.client.get(url)
385-
self.assertNotContains(response, vulnerability1.vcid)
386-
self.assertContains(response, vulnerability2.vcid)
385+
self.assertNotContains(response, vulnerability1.vulnerability_id)
386+
self.assertContains(response, vulnerability2.vulnerability_id)
387387
self.assertContains(response, "1 results")
388388
self.assertContains(response, 'A risk threshold filter at "3.0" is currently applied.')
389389

vulnerabilities/api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ class Meta:
3636
fields = (
3737
"api_url",
3838
"uuid",
39-
"vulnerability_id",
39+
"advisory_uid",
40+
"advisory_id",
4041
"resource_url",
4142
"summary",
4243
"aliases",
43-
"references",
4444
"exploitability",
4545
"weighted_severity",
4646
"risk_score",
47+
"risk_level",
48+
"fixed_packages",
4749
"affected_packages",
4850
"affected_products",
4951
)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Generated by Django 6.0.4 on 2026-06-10
2+
3+
from django.db import migrations, models
4+
5+
6+
def purge_vulnerabilities(apps, schema_editor):
7+
"""Purge all Vulnerability rows before adding non-nullable advisory fields."""
8+
Vulnerability = apps.get_model("vulnerabilities", "Vulnerability")
9+
Vulnerability.objects.all().delete()
10+
11+
12+
class Migration(migrations.Migration):
13+
14+
dependencies = [
15+
("vulnerabilities", "0007_vulnerability_vulnerabili_exploit_f83324_idx_and_more"),
16+
]
17+
18+
operations = [
19+
migrations.RunPython(purge_vulnerabilities, migrations.RunPython.noop),
20+
# Remove old unique_together referencing vulnerability_id before dropping the field.
21+
migrations.AlterUniqueTogether(
22+
name="vulnerability",
23+
unique_together={("dataspace", "uuid")},
24+
),
25+
migrations.RemoveIndex(
26+
model_name="vulnerability",
27+
name="vulnerabili_vulnera_92f044_idx",
28+
),
29+
migrations.RemoveField(
30+
model_name="vulnerability",
31+
name="vulnerability_id",
32+
),
33+
migrations.RemoveField(
34+
model_name="vulnerability",
35+
name="references",
36+
),
37+
migrations.AddField(
38+
model_name="vulnerability",
39+
name="advisory_uid",
40+
field=models.CharField(
41+
help_text="Unique ID for the datasource used for this advisory ."
42+
"e.g.: pysec_importer_v2/PYSEC-2020-2233",
43+
max_length=250,
44+
),
45+
),
46+
migrations.AddField(
47+
model_name="vulnerability",
48+
name="advisory_id",
49+
field=models.CharField(
50+
help_text="An advisory is a vulnerability identifier in some database, "
51+
"such as PYSEC-2020-2233",
52+
max_length=200,
53+
),
54+
),
55+
# Restore unique_together with the new advisory_uid field.
56+
migrations.AlterUniqueTogether(
57+
name="vulnerability",
58+
unique_together={("dataspace", "uuid"), ("dataspace", "advisory_uid")},
59+
),
60+
migrations.AddIndex(
61+
model_name="vulnerability",
62+
index=models.Index(fields=["advisory_id"], name="vulnerabili_advisor_7de61b_idx"),
63+
),
64+
]

vulnerabilities/models.py

Lines changed: 56 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# See https://aboutcode.org for more information about AboutCode FOSS projects.
77
#
88

9-
import decimal
109
import logging
1110

1211
from django.contrib.postgres.fields import ArrayField
@@ -78,6 +77,8 @@ def added_or_updated_today(self):
7877
return self.filter(last_modified_date__gte=today)
7978

8079

80+
# AdvisoryV2
81+
# https://github.com/aboutcode-org/vulnerablecode/blob/api-integration-dejacode/vulnerabilities/models.py#L3126
8182
class Vulnerability(HistoryDateFieldsMixin, DataspacedModel):
8283
"""
8384
A software vulnerability with a unique identifier and alternate aliases.
@@ -90,32 +91,19 @@ class Vulnerability(HistoryDateFieldsMixin, DataspacedModel):
9091
automatically on object addition or during schedule tasks.
9192
"""
9293

93-
# The first set of fields are storing data as fetched from VulnerableCode
94-
vulnerability_id = models.CharField(
95-
max_length=20,
94+
advisory_uid = models.CharField(
95+
max_length=250,
9696
help_text=_(
97-
"A unique identifier for the vulnerability, prefixed with 'VCID-'. "
98-
"For example, 'VCID-2024-0001'."
97+
"Unique ID for the datasource used for this advisory ."
98+
"e.g.: pysec_importer_v2/PYSEC-2020-2233"
99+
),
100+
)
101+
advisory_id = models.CharField(
102+
max_length=200,
103+
help_text=_(
104+
"An advisory is a vulnerability identifier in some database, such as PYSEC-2020-2233"
99105
),
100106
)
101-
# avid = models.CharField(
102-
# max_length=500,
103-
# blank=False,
104-
# null=False,
105-
# help_text=_(
106-
# "Unique ID for the datasource used for this advisory ."
107-
# "e.g.: pysec_importer_v2/PYSEC-2020-2233"
108-
# ),
109-
# )
110-
# advisory_id = models.CharField(
111-
# max_length=500,
112-
# blank=False,
113-
# null=False,
114-
# help_text=_(
115-
# "An advisory is a unique vulnerability identifier in some database, "
116-
# "such as PYSEC-2020-2233"
117-
# ),
118-
# )
119107
resource_url = models.URLField(
120108
_("Resource URL"),
121109
max_length=1024,
@@ -133,13 +121,6 @@ class Vulnerability(HistoryDateFieldsMixin, DataspacedModel):
133121
"(e.g., 'CVE-2017-1000136')."
134122
),
135123
)
136-
references = JSONListField(
137-
blank=True,
138-
help_text=_(
139-
"A list of references for this vulnerability. Each reference includes a "
140-
"URL, an optional reference ID, scores, and the URL for further details. "
141-
),
142-
)
143124
fixed_packages = JSONListField(
144125
blank=True,
145126
help_text=_("A list of packages that are not affected by this vulnerability."),
@@ -206,20 +187,21 @@ class Vulnerability(HistoryDateFieldsMixin, DataspacedModel):
206187

207188
class Meta:
208189
verbose_name_plural = "Vulnerabilities"
209-
unique_together = (("dataspace", "vulnerability_id"), ("dataspace", "uuid"))
190+
unique_together = (("dataspace", "advisory_uid"), ("dataspace", "uuid"))
210191
indexes = [
211-
models.Index(fields=["vulnerability_id"]),
192+
models.Index(fields=["advisory_id"]),
212193
models.Index(fields=["exploitability"]),
213194
models.Index(fields=["weighted_severity"]),
214195
models.Index(fields=["risk_score"]),
196+
models.Index(fields=["risk_level"]),
215197
]
216198

217199
def __str__(self):
218200
return self.vulnerability_id
219201

220202
@property
221-
def vcid(self):
222-
return self.vulnerability_id
203+
def vulnerability_id(self):
204+
return self.advisory_id
223205

224206
@property
225207
def cve(self):
@@ -280,51 +262,51 @@ def as_cyclonedx(self, affected_instances, analysis=None):
280262
url=self.resource_url,
281263
)
282264

283-
references = []
284-
ratings = []
285-
for reference in self.references:
286-
reference_source = cdx_vulnerability.VulnerabilitySource(
287-
url=reference.get("reference_url"),
288-
)
289-
references.append(
290-
cdx_vulnerability.VulnerabilityReference(
291-
id=reference.get("reference_id"),
292-
source=reference_source,
293-
)
294-
)
295-
296-
for score_entry in reference.get("scores", []):
297-
# CycloneDX only support a float value for the score field,
298-
# where on the VulnerableCode data it can be either a score float value
299-
# or a severity string value.
300-
score_value = score_entry.get("value")
301-
try:
302-
score = decimal.Decimal(score_value)
303-
severity = None
304-
except decimal.DecimalException:
305-
score = None
306-
severity = getattr(
307-
cdx_vulnerability.VulnerabilitySeverity,
308-
score_value.upper(),
309-
None,
310-
)
311-
312-
ratings.append(
313-
cdx_vulnerability.VulnerabilityRating(
314-
source=reference_source,
315-
score=score,
316-
severity=severity,
317-
vector=score_entry.get("scoring_elements"),
318-
)
319-
)
265+
# references = []
266+
# ratings = []
267+
# for reference in self.references:
268+
# reference_source = cdx_vulnerability.VulnerabilitySource(
269+
# url=reference.get("reference_url"),
270+
# )
271+
# references.append(
272+
# cdx_vulnerability.VulnerabilityReference(
273+
# id=reference.get("reference_id"),
274+
# source=reference_source,
275+
# )
276+
# )
277+
278+
# for score_entry in reference.get("scores", []):
279+
# # CycloneDX only support a float value for the score field,
280+
# # where on the VulnerableCode data it can be either a score float value
281+
# # or a severity string value.
282+
# score_value = score_entry.get("value")
283+
# try:
284+
# score = decimal.Decimal(score_value)
285+
# severity = None
286+
# except decimal.DecimalException:
287+
# score = None
288+
# severity = getattr(
289+
# cdx_vulnerability.VulnerabilitySeverity,
290+
# score_value.upper(),
291+
# None,
292+
# )
293+
294+
# ratings.append(
295+
# cdx_vulnerability.VulnerabilityRating(
296+
# source=reference_source,
297+
# score=score,
298+
# severity=severity,
299+
# vector=score_entry.get("scoring_elements"),
300+
# )
301+
# )
320302

321303
return cdx_vulnerability.Vulnerability(
322304
id=self.vulnerability_id,
323305
source=source,
324306
description=self.summary,
325307
affects=affects,
326-
references=sorted(references),
327-
ratings=ratings,
308+
# references=sorted(references),
309+
# ratings=ratings,
328310
analysis=analysis,
329311
)
330312

0 commit comments

Comments
 (0)