Skip to content

Commit 06d26a8

Browse files
authored
Merge pull request #2191 from aboutcode-org/bug-fix-content-id-v2
Compute content_id from all fields of AdvisoryV2
2 parents 2ff2906 + 04326f6 commit 06d26a8

File tree

2 files changed

+119
-40
lines changed

2 files changed

+119
-40
lines changed

vulnerabilities/tests/test_utils.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,27 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10+
from datetime import datetime
11+
from datetime import timedelta
12+
13+
from django.test import TestCase
1014
from fetchcode.package_versions import PackageVersion
1115
from packageurl import PackageURL
1216
from univers.version_constraint import VersionConstraint
1317
from univers.version_range import GemVersionRange
18+
from univers.version_range import VersionRange
1419
from univers.versions import RubygemsVersion
1520

21+
from vulnerabilities import utils
22+
from vulnerabilities.importer import AdvisoryDataV2
23+
from vulnerabilities.importer import AffectedPackageV2
24+
from vulnerabilities.importer import PackageCommitPatchData
25+
from vulnerabilities.importer import PatchData
26+
from vulnerabilities.importer import VulnerabilitySeverity
27+
from vulnerabilities.models import AdvisoryV2
28+
from vulnerabilities.pipelines import insert_advisory_v2
29+
from vulnerabilities.references import XsaReferenceV2
30+
from vulnerabilities.references import ZbxReferenceV2
1631
from vulnerabilities.utils import AffectedPackage
1732
from vulnerabilities.utils import get_item
1833
from vulnerabilities.utils import get_severity_range
@@ -151,3 +166,88 @@ def test_resolve_version_range_without_ignorable_versions():
151166
def test_get_severity_range():
152167
assert get_severity_range({""}) is None
153168
assert get_severity_range({}) is None
169+
170+
171+
class TestComputeContentIdV2(TestCase):
172+
def setUp(self):
173+
self.advisory1 = AdvisoryDataV2(
174+
summary="Test advisory",
175+
aliases=["CVE-2025-0001", "CVE-2024-0001"],
176+
references=[
177+
XsaReferenceV2.from_number(248),
178+
ZbxReferenceV2.from_id("ZBX-000"),
179+
],
180+
severities=[
181+
VulnerabilitySeverity.from_dict(
182+
{
183+
"system": "cvssv4",
184+
"value": "7.5",
185+
"scoring_elements": "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
186+
}
187+
),
188+
VulnerabilitySeverity.from_dict(
189+
{
190+
"system": "cvssv3",
191+
"value": "6.5",
192+
"scoring_elements": "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
193+
}
194+
),
195+
],
196+
weaknesses=[296, 233],
197+
affected_packages=[
198+
AffectedPackageV2(
199+
package=PackageURL.from_string("pkg:npm/foobar"),
200+
affected_version_range=VersionRange.from_string("vers:npm/<=1.2.3"),
201+
fixed_version_range=VersionRange.from_string("vers:npm/1.2.4"),
202+
introduced_by_commit_patches=[],
203+
fixed_by_commit_patches=[],
204+
),
205+
AffectedPackageV2(
206+
package=PackageURL.from_string("pkg:npm/foobar"),
207+
affected_version_range=VersionRange.from_string("vers:npm/<=0.2.3"),
208+
fixed_version_range=VersionRange.from_string("vers:npm/0.2.4"),
209+
introduced_by_commit_patches=[
210+
PackageCommitPatchData(
211+
vcs_url="https://foobar.vcs/",
212+
commit_hash="662f801f",
213+
),
214+
PackageCommitPatchData(
215+
vcs_url="https://foobar.vcs/",
216+
commit_hash="001f801f",
217+
),
218+
],
219+
fixed_by_commit_patches=[
220+
PackageCommitPatchData(
221+
vcs_url="https://foobar.vcs/",
222+
commit_hash="982f801f",
223+
),
224+
PackageCommitPatchData(
225+
vcs_url="https://foobar.vcs/",
226+
commit_hash="081f801f",
227+
),
228+
],
229+
),
230+
],
231+
patches=[
232+
PatchData(patch_url="https://foo.bar/", patch_text="test patch"),
233+
PatchData(patch_url="https://yet-another-foo.bar/", patch_text="some test patch"),
234+
],
235+
advisory_id="ADV-001",
236+
date_published=datetime.now() - timedelta(days=10),
237+
url="https://example.com/advisory/1",
238+
)
239+
insert_advisory_v2(
240+
advisory=self.advisory1,
241+
pipeline_id="test_pipeline_v2",
242+
)
243+
244+
def test_compute_content_id_v2(self):
245+
result = utils.compute_content_id_v2(self.advisory1)
246+
self.assertEqual(result, "5211f1e6c3d935759fb288d79a865eeacc06e3e0e352ab7f5b4cb0e76a43a955")
247+
248+
def test_content_id_from_adv_data_and_adv_model_are_same(self):
249+
id_from_data = utils.compute_content_id_v2(self.advisory1)
250+
advisory_model = AdvisoryV2.objects.first()
251+
id_from_model = utils.compute_content_id_v2(advisory_model)
252+
253+
self.assertEqual(id_from_data, id_from_model)

vulnerabilities/utils.py

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -666,53 +666,32 @@ def compute_content_id_v2(advisory_data):
666666
"""
667667
Compute a unique content_id for an advisory by normalizing its data and hashing it.
668668
669-
:param advisory_data: An AdvisoryData object
669+
:param advisory_data: An AdvisoryDataV2 or AdvisoryV2 object
670670
:return: SHA-256 hash digest as content_id
671671
"""
672-
673-
# Normalize fields
674672
from vulnerabilities.importer import AdvisoryDataV2
675673
from vulnerabilities.models import AdvisoryV2
676674

677-
if isinstance(advisory_data, AdvisoryV2):
678-
normalized_data = {
679-
"aliases": normalize_list(advisory_data.aliases),
680-
"summary": normalize_text(advisory_data.summary),
681-
"impacted_packages": sorted(
682-
[impact.to_dict() for impact in advisory_data.impacted_packages.all()],
683-
key=lambda x: json.dumps(x, sort_keys=True),
684-
),
685-
"patches": sorted(
686-
[patch.to_patch_data().to_dict() for patch in advisory_data.patches.all()],
687-
key=lambda x: json.dumps(x, sort_keys=True),
688-
),
689-
"references": [ref for ref in normalize_list(advisory_data.references) if ref],
690-
"weaknesses": normalize_list(advisory_data.weaknesses),
691-
}
692-
normalized_data["url"] = advisory_data.url
693-
694-
elif isinstance(advisory_data, AdvisoryDataV2):
695-
normalized_data = {
696-
"advisory_id": normalize_text(advisory_data.advisory_id),
697-
"aliases": normalize_list(advisory_data.aliases),
698-
"summary": normalize_text(advisory_data.summary),
699-
"affected_packages": [
700-
pkg.to_dict() for pkg in normalize_list(advisory_data.affected_packages) if pkg
701-
],
702-
"references": [
703-
ref.to_dict() for ref in normalize_list(advisory_data.references) if ref
704-
],
705-
"severities": [
706-
sev.to_dict() for sev in normalize_list(advisory_data.severities) if sev
707-
],
708-
"weaknesses": normalize_list(advisory_data.weaknesses),
709-
"patches": [patch.to_dict() for patch in normalize_list(advisory_data.patches)],
710-
}
711-
normalized_data["url"] = advisory_data.url
712-
713-
else:
675+
if not isinstance(advisory_data, (AdvisoryV2, AdvisoryDataV2)):
714676
raise ValueError("Unsupported advisory data type for content ID computation")
715677

678+
if isinstance(advisory_data, AdvisoryV2):
679+
advisory_data = advisory_data.to_advisory_data()
680+
681+
normalized_data = {
682+
"advisory_id": normalize_text(advisory_data.advisory_id),
683+
"aliases": normalize_list(advisory_data.aliases),
684+
"summary": normalize_text(advisory_data.summary),
685+
"affected_packages": [
686+
pkg.to_dict() for pkg in normalize_list(advisory_data.affected_packages) if pkg
687+
],
688+
"references": [ref.to_dict() for ref in normalize_list(advisory_data.references) if ref],
689+
"severities": [sev.to_dict() for sev in normalize_list(advisory_data.severities) if sev],
690+
"weaknesses": normalize_list(advisory_data.weaknesses),
691+
"patches": [patch.to_dict() for patch in normalize_list(advisory_data.patches)],
692+
}
693+
normalized_data["url"] = advisory_data.url
694+
716695
normalized_json = json.dumps(normalized_data, separators=(",", ":"), sort_keys=True)
717696
content_id = hashlib.sha256(normalized_json.encode("utf-8")).hexdigest()
718697

0 commit comments

Comments
 (0)